Hi
Volker and I would like to start discussion on our RFC to allow "Marking
return values as important (#[\NoDiscard])".
Please find the following resources for your reference:
- RFC: https://wiki.php.net/rfc/marking_return_value_as_important
- Implementation: https://github.com/php/php-src/pull/17599
Best regards
Tim Düsterhus
Hi
Volker and I would like to start discussion on our RFC to allow "Marking
return values as important (#[\NoDiscard])".Please find the following resources for your reference:
- RFC: https://wiki.php.net/rfc/marking_return_value_as_important
- Implementation: https://github.com/php/php-src/pull/17599
Best regards
Tim Düsterhus
Hi Tim,
This looks promising!
Why do we need the (void) cast? Wouldn't "@" work and avoid a BC issue?
— Rob
Hi
This looks promising!
Why do we need the (void) cast? Wouldn't "@" work and avoid a BC issue?
The @
operator also suppresses any other warning or notice within the
call chain. That is likely undesirable because it might hide issues one
is interested in. See this example (https://3v4l.org/NDtR7):
<?php
function inner() {
var_dump($undefined);
}
function outer() {
inner();
}
outer();
outer();
@outer();
The (void)
cast makes the intent of not being interested in the return
value clear. In C casting to (void)
is an established pattern of
"using" a value that the compiler would otherwise consider unused.
Likewise will the void
operator suppress the TypeScript-ESLint warning
of not using returned Promise
:
https://typescript-eslint.io/rules/no-floating-promises/#ignorevoid
Best regards
Tim Düsterhus
Hi
This looks promising!
Why do we need the (void) cast? Wouldn't "@" work and avoid a BC issue?
The
@
operator also suppresses any other warning or notice within the
call chain. That is likely undesirable because it might hide issues one
is interested in. See this example (https://3v4l.org/NDtR7):<?php function inner() { var_dump($undefined); } function outer() { inner(); } outer(); outer(); @outer();
The
(void)
cast makes the intent of not being interested in the return
value clear. In C casting to(void)
is an established pattern of
"using" a value that the compiler would otherwise consider unused.
Likewise will thevoid
operator suppress the TypeScript-ESLint warning
of not using returnedPromise
:
https://typescript-eslint.io/rules/no-floating-promises/#ignorevoidBest regards
Tim Düsterhus
Hi Tim,
I understand what you are saying, but I can also just remove the warning via:
$_ = outer;
Which, to me, is clearer on the intent than (void)outer(); It also happens to make the diffs more natural looking when and if the return value gets used during a code review. I'll also note that having (void) doesn't preclude me from using $_ either.
I was mostly just curious as to the reasoning; I don't like it, but it makes sense.
Best of luck!
— Rob
Am 29.01.2025 um 21:16 schrieb Rob Landers rob@bottled.codes:
The
(void)
cast makes the intent of not being interested in the return
value clear. In C casting to(void)
is an established pattern of
"using" a value that the compiler would otherwise consider unused.I understand what you are saying, but I can also just remove the warning via:
$_ = outer();
Which, to me, is clearer on the intent than (void)outer(); It also happens to make the diffs more natural looking when and if the return value gets used during a code review. I'll also note that having (void) doesn't preclude me from using $_ either.
I think the (void) cast is familiar for people from C or the like, the only caveat is that it is not backward compatible, i.e. code using it won't work on older versions of PHP so migrating to it is not seamless, so it probably should not be used for a while in public libraries / packages.
I guess another option would be to define a
function nop($) {}
and then use
nop(outer());
but my question is whether $ = outer() and nop(outer()) will be guaranteed to not being optimized in a way which causes the warning to reappear. I don't know much about the optimizations being done but this could be an issue in the future, no?
Regards,
- Chris
I think the (void) cast is familiar for people from C or the like, the only caveat is that it is not backward compatible, i.e. code using it won't work on older versions of PHP so migrating to it is not seamless, so it probably should not be used for a while in public libraries / packages.
I suspect that a majority of PHP users will probably have had no experience in C, which is easy to forget on this list where we're working on a C codebase. In C, "void" has various uses in the type system, but in PHP this would effectively just be a specific keyword for this case.
I think it would be good to explore alternatives - for instance, I think C# has a reserved _ variable to assign discarded results to, but may be misremembering.
but my question is whether $_ = outer() and nop(outer()) will be guaranteed to not being optimized in a way which causes the warning to reappear. I don't know much about the optimizations being done but this could be an issue in the future, no?
I second this concern: having code that you think has suppressed the warning later be optimized and start warning could be very confusing, and given the backwards compatibility issue, users will need something other than "(void)" which is reliable.
My other thought reading the proposal is that if this can be done efficiently, can we use the same approach to warn when a "void" function's return is used?
Regards,
Rowan Tommins
[IMSoP]
Hi
Am 2025-01-30 10:02, schrieb Rowan Tommins [IMSoP]:
I think it would be good to explore alternatives - for instance, I
think C# has a reserved _ variable to assign discarded results to, but
may be misremembering.
A quick search confirms that C# supports the _
variable. It's also
supported with Rust.
We are open to possible alternatives to the (void)
cast, this was also
something we discussed while drafting the RFC. However we opted for the
(void)
with out proposal for the following reasons:
-
$_ = func();
is already working as a regular variable (see my reply
to Rob regarding lifetimes), thus repurposing it as a “discard pile”
would cause backwards compatibility issues. -
_ = func();
would introduce new syntax (just like(void)
), but
does not look and feel like PHP to us. -
(void)func();
has precedent in other languages (just like_ =
) and
looks and feels like PHP. In fact in earlier PHP versions there already
was an(unset)
cast to unconditionally turn an expression intonull
.
We opted for(void)
instead of bringing back(unset)
for the
following reasons: (1) Possible confusion about a deprecated + removed
feature coming back (e.g. avoiding old blog articles). (2) More cleanly
enforcing that(void)
is a statement, rather than an expression. (3)
The similarity with thevoid
return type to indicate that “nothing” is
returned.
but my question is whether $_ = outer() and nop(outer()) will be
guaranteed to not being optimized in a way which causes the warning to
reappear. I don't know much about the optimizations being done but
this could be an issue in the future, no?I second this concern: having code that you think has suppressed the
warning later be optimized and start warning could be very confusing,
and given the backwards compatibility issue, users will need something
other than "(void)" which is reliable.
See my replies to Rob and Christian.
My other thought reading the proposal is that if this can be done
efficiently, can we use the same approach to warn when a "void"
function's return is used?
Performing this check would be possible, but it would likely be less
efficient.
The current implementation of #[\NoDiscard]
optimizes based on the
assumption that the attribute is going to be comparatively rarely used
(i.e. only when it actually matters, as per the “Recommended Usage”
section of the RFC). The “Implementation Details” section already
mentions this, but basically what's happening is that the checks for
#[\Deprecated]
and #[\NoDiscard]
are happening at the same time
(it's a bitflag check) and you are only paying the cost of the more
detailed checks if the function is Deprecated or NoDiscard (or both).
Then there's also the “OPcode specialization” to remove #[\NoDiscard]
from the bitflag check when the return value is known to be used.
And finally functions that are known to neither be #[\Deprecated]
nor
#[\NoDiscard]
at compile time (this is mostly fully-qualified native
functions) are going through an even faster OPcode that completely omits
several safety checks. You can check this for yourself by passing a
script through php -d opcache.enable_cli=1 -d opcache.opt_debug_level=0x20000 script.php
. If you see DO_ICALL
(native functions) or DO_UCALL
(userland functions) then that's the
optimized version omitting the checks.
Here's an example:
<?php
namespace Foo;
strrev('test');
results in:
$_main:
; (lines=4, args=0, vars=0, tmps=0)
; (after optimizer)
; test.php:1-6
0000 INIT_NS_FCALL_BY_NAME 1 string("Foo\\strrev")
0001 SEND_VAL_EX string("test") 1
0002 DO_FCALL_BY_NAME
0003 RETURN int(1)
whereas
<?php
namespace Foo;
\strrev('test');
results in
$_main:
; (lines=4, args=0, vars=0, tmps=0)
; (after optimizer)
; test.php:1-6
0000 INIT_FCALL 1 96 string("strrev")
0001 SEND_VAL string("test") 1
0002 DO_ICALL
0003 RETURN int(1)
Note the difference for 0000 and 0002.
Best regards
Tim Düsterhus
Le 30 janv. 2025 à 16:51, Tim Düsterhus tim@bastelstu.be a écrit :
Hi
Am 2025-01-30 10:02, schrieb Rowan Tommins [IMSoP]:
I think it would be good to explore alternatives - for instance, I think C# has a reserved _ variable to assign discarded results to, but may be misremembering.
A quick search confirms that C# supports the
_
variable. It's also supported with Rust.We are open to possible alternatives to the
(void)
cast, this was also something we discussed while drafting the RFC. However we opted for the(void)
with out proposal for the following reasons:
$_ = func();
is already working as a regular variable (see my reply to Rob regarding lifetimes), thus repurposing it as a “discard pile” would cause backwards compatibility issues._ = func();
would introduce new syntax (just like(void)
), but does not look and feel like PHP to us.(void)func();
has precedent in other languages (just like_ =
) and looks and feels like PHP. In fact in earlier PHP versions there already was an(unset)
cast to unconditionally turn an expression intonull
. We opted for(void)
instead of bringing back(unset)
for the following reasons: (1) Possible confusion about a deprecated + removed feature coming back (e.g. avoiding old blog articles). (2) More cleanly enforcing that(void)
is a statement, rather than an expression. (3) The similarity with thevoid
return type to indicate that “nothing” is returned.
A possible alternative is: ! func();
, that already “works” as of today, modulo possible optimisation.
In any case, I think that (void) func();
is not self-explanatory, and in the event I want to ignore a result I am not supposed to ignore, I will probably write something more explicit like, $unused = func();
(possibly decorated with an annotation telling static analysers not to complain that $unused is unused), or forget(func())
BTW, the RFC says that introducing the (void)
cast is a backward compatible change, which is incorrect; see: https://3v4l.org/iN7OY
—Claude
Hi
Am 2025-01-30 18:48, schrieb Claude Pache:
BTW, the RFC says that introducing the
(void)
cast is a backward
compatible change, which is incorrect; see: https://3v4l.org/iN7OY
:-| Thank you for pointing that out. We've adjusted the “Backward
Incompatible Changes” section accordingly and also changed threshold for
the separate vote for the inclusion of (void)
from 50%+1 to 2/3 to
more accurately reflect the possible impact.
Best regards
Tim Düsterhus
Hi
Am 2025-01-29 23:48, schrieb Christian Schneider:
I guess another option would be to define a
function nop($) {}
and then use
nop(outer());
but my question is whether $ = outer() and nop(outer()) will be
guaranteed to not being optimized in a way which causes the warning to
reappear. I don't know much about the optimizations being done but this
could be an issue in the future, no?
For $_
see my reply to Rob. For the function: OPcache will attempt to
inline functions that only return a (implicit) return statement. However
this only works if the function is defined in the same file, as OPcache
currently cannot perform cross-file optimizations. I'd say that defining
the nop()
function in a dedicated file is currently the most reliable
solution to prevent OPcache from optimizing away the usage in a
backwards-compatible way.
Of course the best solution is actually doing something useful with the
return value, as the attribute is intended to point out when not using
the return value is very likely to be wrong. We would expect it to be
rarely necessary to just suppress the warning.
Best regards
Tim Düsterhus
Hi
Am 2025-01-29 21:16, schrieb Rob Landers:
I understand what you are saying, but I can also just remove the
warning via:$_ = outer;
Please note that $_
is a regular variable and thus will increase the
lifetimes of returned objects (and arrays that can contain objects),
which might or might not be observable when implementing __destruct()
.
Also note that OPcache will actually optimize away dead stores if it can
guarantee that the difference is not observable (i.e. if it knows that
the function never returns an object and if get_defined_vars()
is not
called).
Of course it would be possible to exclude $_
from this dead-store
optimization with PHP 8.5+ to allow for a smoother migration for
libraries that require cross-version compatibility.
We nevertheless wanted to offer an alternative that might be less
confusing than a “special variable” that might also conflict with
diagnostics like unused variable detection.
[…] It also happens to make the diffs more natural looking when and if
the return value gets used during a code review. […]
Can you clarify what you mean by “more natural looking”?
Best regards
Tim Düsterhus
Hi
Am 2025-01-29 21:16, schrieb Rob Landers:
I understand what you are saying, but I can also just remove the
warning via:$_ = outer;
Please note that
$_
is a regular variable and thus will increase the
lifetimes of returned objects (and arrays that can contain objects),
which might or might not be observable when implementing__destruct()
.
Also note that OPcache will actually optimize away dead stores if it can
guarantee that the difference is not observable (i.e. if it knows that
the function never returns an object and ifget_defined_vars()
is not
called).
If $_ gets optimized away and causes the warning, then this seems like a (future) bug with opcache (it optimized something away and it was observable). It also shouldn't matter if you use $_, $unused, or $whatever; I just chose that as an example as someone who came from C# + Scala + Go lineage.
Some might say that is a feature, though... and the inconsistency (whether opcache is used or not) may not be ideal.
For example:
$lock = flock($fp);
-if ($lock) {
+if(true) {
// do stuff
This diff is rather obvious something went wrong here; but use your imagination to imagine a diff where this is not so clear. In this case $lock accidentally becomes unused. If opcache is enabled and optimizes this code, we get the warning we probably desire, but without opcache, we do not get the warning (tests usually have opcache disabled)!
Of course it would be possible to exclude
$_
from this dead-store
optimization with PHP 8.5+ to allow for a smoother migration for
libraries that require cross-version compatibility.
I believe the draft pattern matching RFC uses this variable name. It is probably good to align there.
We nevertheless wanted to offer an alternative that might be less
confusing than a “special variable” that might also conflict with
diagnostics like unused variable detection.[…] It also happens to make the diffs more natural looking when and if
the return value gets used during a code review. […]Can you clarify what you mean by “more natural looking”?
Here are two diffs:
-(void)doSomething();
+$var = doSomething();
+somethingElse($var);
or
-$_ = doSomething();
+$var = doSomething();
+somethingElse($var);
At the expense of adding some html... I've added syntax highlighting for those using html readers to show how it might look in something like github. It is immediately clear what has changed: only the variable name is highlighted as a change on that line, letting me know what to look for. Though, as I mentioned, I come from a lineage of languages where _ is a thing, so it looks more natural to me (other languages using _ are Rust, Swift and Zig). For someone coming from more C/Typescript backgrounds, they may disagree.
— Rob
Hi Tim,
Hi
Volker and I would like to start discussion on our RFC to allow "Marking
return values as important (#[\NoDiscard])".Please find the following resources for your reference:
I reckon it's a nice thing to have, but to be honest I think handling it
just during static analysis would be the best approach. Also there would
be no need for a (void) cast to suppress the warning.
Cheers
Matteo Beccati
Development & Consulting - http://www.beccati.com/
Hi Matteo,
I reckon it's a nice thing to have, but to be honest I think handling it
just during static analysis would be the best approach. Also there would
be no need for a (void) cast to suppress the warning.
We also discussed if this should be part of the core language or something
that is only supported by tooling. During the discussion we looked for
existing context and at what was said around #[Override] [1] at the time.
I won't repeat Tim's point from last year here for brevity, but I think the
same ideas apply.
Having the core language offer a way to tag a function as “return value
matters” would also allow library and tool authors like Psalm and PHPStan
(free & pro versions) to add and work with these tags.
Not having each tool to curate its lists of functions where the return
value is considered “important”, I think, would be an improvement overall.
It also enables library authors to suggest it to these tools in a
standardized way.
PHPStorm (and EA-Extended inspections plugin) also ship with lists of
functions they warn about that are more extensive than what we propose
here, but they also only work for core as there is so no standardized way
yet, that I'm aware of, to tell PHPStorm (or any LSP) about this type of
requirement.
The void
cast or the $_
discussed currently would be a /** @phpstan-suppress UnusedReturnValue */
(or something like that) in
PHPStan. So there are various ways of expressing f this concept in syntax,
and we tried to find something that doesn't add BC issues to PHP in any
way.
There has always been overlap between what SA is doing for its users and
what PHP is doing for all users of the language. From type checking (static
vs. runtime) to warnings and deprecations, things are both checked at
runtime and at CI time. PHP its self is more conservative and has more BC
focus than the tools, of course, but I think this is a case where the
attribute would help with creating a common language.
Kind Regards,
Volker
[1] https://externals.io/message/120233#120432
--
Volker Dusch
Head of Engineering
Tideways GmbH
Königswinterer Str. 116
53227 Bonn
https://tideways.io/imprint
Sitz der Gesellschaft: Bonn
Geschäftsführer: Benjamin Außenhofer (geb. Eberlei)
Registergericht: Amtsgericht Bonn, HRB 22127
Hi
Volker and I would like to start discussion on our RFC to allow "Marking
return values as important (#[\NoDiscard])".Please find the following resources for your reference:
Looks reasonable to me.
What's the reason why this emits a warning though, instead of throwing
an Exception? I'm sure you thought of it, but it would be nice to have
that consideration mentioned in the RFC.
cheers,
Derick
What's the reason why this emits a warning though, instead of throwing
an Exception? I'm sure you thought of it, but it would be nice to have
that consideration mentioned in the RFC.
Thank you for the question, I now added the missing reasoning to the RFC
text.
A <php>E_(USER_)WARNING</php> was chosen over an exception to avoid BC
concerns, as throwing would potentially break existing code that "works as
implemented" and because this is only a warning in the literal sense of the
word, given that the function executes successfully.
Given that PHP uses Exceptions only in hard failure cases, we found it to
be more prudent to go this way. If users really want, they can
suppress/ignore this warning without adapting existing code, e.g., using
the proposed (void) cast. But the main reason was to not cause hard
failures through exceptions.
Kind Regards,
Volker
--
Volker Dusch
Head of Engineering
Tideways GmbH
Königswinterer Str. 116
53227 Bonn
https://tideways.io/imprint
Sitz der Gesellschaft: Bonn
Geschäftsführer: Benjamin Außenhofer (geb. Eberlei)
Registergericht: Amtsgericht Bonn, HRB 22127
Volker and I would like to start discussion on our RFC to allow "Marking
return values as important (#[\NoDiscard])".Please find the following resources for your reference:
Hello everyone,
it's been two weeks, but given the feedback we received, we don't feel the
discussion didn't reach a conclusive resolution yet.
So we wanted to solicit additional opinions. In an attempt to summarize the
discussion, how we see it, there are two main points of contention we'd
like to discuss further.
a) Intentionally discarding the return value
The (void)flock($fp);
syntax was the main thing discussed on the list.
With $_ = flock($fp);
as the most suggested alternative.
The issue with using $_
as it currently stands, is that the unused
variable would be cleaned up by OPcache.
Should we instead have a special case in OPcache ensuring it does not apply
its optimization to a variable if it's named $_
instead? The semantic
difference would be that $_ would keep returned values (and objects) alive
and in memory, whereas (void) would discard them immediately. Leading to
different times when, for example, destructors would be called.
The consequence of this would be that 3rd party tools like IDEs and Static
and Dynamic Code Analyzers would also have to build that special case in to
not produce "unused variable" warnings.
We're also happy to change the secondary vote to "(void) vs $_" as we feel
there needs to be a way to discard the return value consistently for this
to be a complete and usable feature.
b) Naming of the attribute
Nobody mentioned this on the list, but before opening a vote we'd like to
heard if the attribute name makes sense to you.
We've chosen #[NoDiscard] as it's also used in C and C++. See [1] for the
full list of references. If you feel this doesn't fit with PHP, we welcome
other suggestions.
[1] https://wiki.php.net/rfc/marking_return_value_as_important#precedent
Kind Regards,
Volker
--
Volker Dusch
Head of Engineering
Tideways GmbH
Königswinterer Str. 116
53227 Bonn
https://tideways.io/imprint
Sitz der Gesellschaft: Bonn
Geschäftsführer: Benjamin Außenhofer (geb. Eberlei)
Registergericht: Amtsgericht Bonn, HRB 22127
Volker and I would like to start discussion on our RFC to allow "Marking
return values as important (#[\NoDiscard])".Please find the following resources for your reference:
Hello everyone,
it's been two weeks, but given the feedback we received, we don't feel
the discussion didn't reach a conclusive resolution yet.So we wanted to solicit additional opinions. In an attempt to summarize
the discussion, how we see it, there are two main points of contention
we'd like to discuss further.
I'm still undecided on the RFC overall, but one thing that is problematic is the phrasing of the messages. Currently, the messages in the attribute are fragments of an English sentence, seemingly designed to fit grammatically with a sentence fragment that is coded into the engine somewhere but not readily available to developers.
From my phrasing I think you can guess my opinion of that.
That is impossible to document cleanly for English speakers. It will not translate at all for anyone who is writing in a non-English language (which people do). People are going to get it wrong more than they get it right, in any language.
Instead, the wording should be structured to be a complete sentence, and the built-in message updated to make that logical. That gives the developer much more freedom to write a meaningful, contextually-relevant message in the language of their choice.
--Larry Garfield
Hi
Am 2025-02-12 22:31, schrieb Larry Garfield:
I'm still undecided on the RFC overall, but one thing that is
problematic is the phrasing of the messages. Currently, the messages
in the attribute are fragments of an English sentence, seemingly
designed to fit grammatically with a sentence fragment that is coded
into the engine somewhere but not readily available to developers.
Yes, the implementation of the error message very closely matches that
of #[\Deprecated] (except that there is no since
bit to insert).
From my phrasing I think you can guess my opinion of that.
That is impossible to document cleanly for English speakers. It will
not translate at all for anyone who is writing in a non-English
language (which people do). People are going to get it wrong more than
they get it right, in any language.Instead, the wording should be structured to be a complete sentence,
and the built-in message updated to make that logical. That gives the
developer much more freedom to write a meaningful,
contextually-relevant message in the language of their choice.
We're open to adjusting that. Do you have any suggestions? The
implementation of #[\Deprecated] works like it does, because PHP itself
already doesn't end the error messages with a .
, as it appends in file.php on line 42
. This makes it inconvenient to have more than one
sentence in an error message, which is why we're struggling with coming
up with something better.
Best regards
Tim Düsterhus
Hi
Am 2025-02-12 22:31, schrieb Larry Garfield:
I'm still undecided on the RFC overall, but one thing that is
problematic is the phrasing of the messages. Currently, the messages
in the attribute are fragments of an English sentence, seemingly
designed to fit grammatically with a sentence fragment that is coded
into the engine somewhere but not readily available to developers.Yes, the implementation of the error message very closely matches that
of #[\Deprecated] (except that there is nosince
bit to insert).From my phrasing I think you can guess my opinion of that.
That is impossible to document cleanly for English speakers. It will
not translate at all for anyone who is writing in a non-English
language (which people do). People are going to get it wrong more than
they get it right, in any language.Instead, the wording should be structured to be a complete sentence,
and the built-in message updated to make that logical. That gives the
developer much more freedom to write a meaningful,
contextually-relevant message in the language of their choice.We're open to adjusting that. Do you have any suggestions? The
implementation of #[\Deprecated] works like it does, because PHP itself
already doesn't end the error messages with a.
, as it appendsin file.php on line 42
. This makes it inconvenient to have more than one
sentence in an error message, which is why we're struggling with coming
up with something better.Best regards
Tim Düsterhus
Just spitballing:
"Return value of foo() is not used, on foo.php line 5: <user text here>"
Fiddle with the wording as needed, but by having a colon and then the user text, it makes it clear it's a separate statement, and can be as flexible as a statement in your chosen language wants to be.
--Larry Garfield
Hi
Am 2025-02-12 22:31, schrieb Larry Garfield:
I'm still undecided on the RFC overall, but one thing that is
problematic is the phrasing of the messages. Currently, the messages
in the attribute are fragments of an English sentence, seemingly
designed to fit grammatically with a sentence fragment that is coded
into the engine somewhere but not readily available to developers.Yes, the implementation of the error message very closely matches that
of #[\Deprecated] (except that there is nosince
bit to insert).From my phrasing I think you can guess my opinion of that.
That is impossible to document cleanly for English speakers. It will
not translate at all for anyone who is writing in a non-English
language (which people do). People are going to get it wrong more than
they get it right, in any language.Instead, the wording should be structured to be a complete sentence,
and the built-in message updated to make that logical. That gives the
developer much more freedom to write a meaningful,
contextually-relevant message in the language of their choice.We're open to adjusting that. Do you have any suggestions? The
implementation of #[\Deprecated] works like it does, because PHP itself
already doesn't end the error messages with a.
, as it appendsin file.php on line 42
. This makes it inconvenient to have more than one
sentence in an error message, which is why we're struggling with coming
up with something better.Just spitballing:
"Return value of foo() is not used, on foo.php line 5: <user text here>"
Fiddle with the wording as needed, but by having a colon and then the
user text, it makes it clear it's a separate statement, and can be as
flexible as a statement in your chosen language wants to be.
PHP always adds "in file.php on line 42" to the end, so that wouldn't
work.
cheers,
Derick
b) Naming of the attribute
Nobody mentioned this on the list, but before opening a vote we'd like
to heard if the attribute name makes sense to you.
On this, it could be spelled #[\MustUse], rather than phrase it as a
negative.
My thoughts overall on this:
-
I'm not against introducing the attribute, only how it's used/enforced
etc. (And, incidentally, what it actually MEANS, see if you can spot that
from the rest of my comments....) -
Static analysers and IDEs: I agree with everyone who's said that whether
or not the return value is "used" should be a concern for tooling like
static analysers and the IDE, and ideally not for PHP runtime.
Attributes were added as a structured replacement for docblock props and I
don't like it when they affect how a program actually runs (as long as
you're not using reflection). -
If it must be a concern for PHP at runtime, a warning is preferred over
an exception. It only makes sense – it's in the name: Exceptions are for
exceptional situations, warnings are for warning you. -
Naming of the attribute: I think the most precise name (if I understand
the purpose correctly) is: #[ReturnValueMayContainCriticalInformation] -
Should all internal functions, such as
fopen()
(which may return false
on error instead of throwing an exception), have this attribute?
I understand that ideally we only want to add it where the failure to
inspect the return value could result in something outright dangerous, but
I think that's a really hard call to make.
If we scale it back to mean "Look, this function may not behave the way
you'd expect it to, - the return value may actually contain some
information you might be interested in, so you better check it man", then I
think it could be a small but useful addition.
Best,
Jakob
Hello, everyone
I'm just wondering how the new attribute that defines behavior (not just
additional metadata) will fit into the rest of the system.
Right now, return type hints are not implemented just as an attribute, but
as "native" type declaration.
I mean, what we have right now:
function foo(): int {}
could as well have been implemented as:
#[ReturnType('int')]
function foo() {}
Yet, the native type declarations that we have is more concise and fits
better.
Do you think it's reasonable to implement "non-discardability of the
returned value" as the attribute? Maybe new keyword would be better
solution?
Hi
Am 2025-02-13 09:49, schrieb Eugene Sidelnyk:
I'm just wondering how the new attribute that defines behavior (not
just
additional metadata) will fit into the rest of the system.
See my reply to Jakob: There are already several attributes that define
behavior.
Do you think it's reasonable to implement "non-discardability of the
returned value" as the attribute? Maybe new keyword would be better
solution?
Yes, we believe that implementing this as an attribute rather than a
keyword is the right choice and the reasoning is similar to the
#[\Override]
attribute that was added in PHP 8.3:
https://wiki.php.net/rfc/marking_overriden_methods#why_an_attribute_and_not_a_keyword
With the exception of error handlers, applying (or unapplying) the
#[\NoDiscard]
attribute will not affect how a PHP program works. It
will just emit a new warning which when suppressed, will not affect the
program’s behavior at all. This is different to return types, which are
relevant due to the Liskov substitution principle and where changing the
return type would actually result in a breaking change to the program’s
public API.
Best regards
Tim Düsterhus
Hi
Am 2025-02-13 09:16, schrieb Jakob Givoni:
Attributes were added as a structured replacement for docblock props
and I
don't like it when they affect how a program actually runs (as long as
you're not using reflection).
Excluding the #[\Attribute]
attribute, PHP currently has 5 native
attributes and they all affect how the program runs. The initial
accepted Attribute RFC even lists several “behavior-affecting”
attributes in the “Use Cases” section:
https://wiki.php.net/rfc/attributes_v2#use_cases. It is probably fair to
say that use-cases like #[\NoDiscard]
do not go against the vision
intended by the Attribute RFC.
You could think of it as the PHP engine using Reflection internally to
do something differently.
- Naming of the attribute: I think the most precise name (if I
understand
the purpose correctly) is: #[ReturnValueMayContainCriticalInformation]
Yes. Or perhaps #[\ReturnValueImportant]
as used by the RFC title to
keep it a little more succinct.
If we scale it back to mean "Look, this function may not behave the way
you'd expect it to, - the return value may actually contain some
information you might be interested in, so you better check it man",
then I
think it could be a small but useful addition.
Let me refer to the “Recommended Usage” section of the RFC:
https://wiki.php.net/rfc/marking_return_value_as_important#recommended_usage
My rule of thumb would be that if you have trouble writing a good custom
explanation in the attribute, then it's likely not a good fit.
Best regards
Tim Düsterhus
Hi
Am 2025-02-13 09:16, schrieb Jakob Givoni:
Attributes were added as a structured replacement for docblock props
and I
don't like it when they affect how a program actually runs (as long as
you're not using reflection).Excluding the
#[\Attribute]
attribute, PHP currently has 5 native
attributes and they all affect how the program runs. The initial
accepted Attribute RFC even lists several “behavior-affecting”
attributes in the “Use Cases” section:
https://wiki.php.net/rfc/attributes_v2#use_cases. It is probably fair to
say that use-cases like#[\NoDiscard]
do not go against the vision
intended by the Attribute RFC.You could think of it as the PHP engine using Reflection internally to
do something differently.
I think all the native attributes so far, with the exception of
#[\AllowDynamicProperties],
only affect the program at compile-time, or by emitting errors.
They don't affect the program otherwise by changing behavior,
throwing exceptions etc. (As long as you don't use reflection, nor promote
errors to exceptions.)
AllowDynamicProperties is the only one that may change the program behavior
at run-time by throwing an exception if the property is removed from a
class while running PHP versions 9.0+.
So this is not even a reality yet, since PHP 9 is not yet released.
And I think it sets a bad precedent. Perhaps AllowDynamicProperties should
have been a
language construct instead. Or only fail at compile time.
The original attributes RFC does mention "runtime behavior" in the
introduction,
but none of the use-cases proposed showcase it without using reflection or
promoting errors to exceptions (as far as I can see).
I don't mean to be rude and use your own words against you, but I found
this quote in the "Why an attribute and not a keyword?" section of
your #[\Override] RFC:
This RFC proposes an attribute instead of a keyword, because contrary to
other modifiers (e.g. visibility) that are part of the method signature,
the attribute does not affect behavior or compatibility for users that
further extend a given class and neither does it affect users that call the
method. It is purely an assistance to the author of a given class.
(and I apologize if I misunderstand your intention or take it out of
context completely), but
I like the sentiment that "the attribute does not affect behavior or
compatibility for users"
and that it's part of the argument for an attribute instead of a keyword
(language construct).
I feel like #[NoDiscard] is getting dangerously close to being part of a
method signature,
especially if it throws exceptions at run-time (which I know it doesn't do
in your RFC, so I'm less against it).
I might be the only "purist" here, but just thought I would highlight that
at least one php developer
is hesitant with the direction attributes might be taking :-)
Best,
Jakob
Hi
Am 2025-02-14 10:10, schrieb Jakob Givoni:
I think all the native attributes so far, with the exception of
#[\AllowDynamicProperties],
only affect the program at compile-time, or by emitting errors.
They don't affect the program otherwise by changing behavior,
throwing exceptions etc. (As long as you don't use reflection, nor
promote
errors to exceptions.)AllowDynamicProperties is the only one that may change the program
behavior
at run-time by throwing an exception if the property is removed from a
class while running PHP versions 9.0+.
This is false. The #[\SensitiveParameter]
attribute changes the
behavior of argument capturing whenever PHP creates a backtrace. This
includes the backtrace added to Exception objects, the
debug_backtrace()
function and the fatal error backtraces added in PHP
8.5 with https://wiki.php.net/rfc/error_backtraces_v2. This is also
listed in the backwards compatible changes section of the
#[\SensitiveParameter]
RFC:
https://wiki.php.net/rfc/redact_parameters_in_back_traces#backward_incompatible_changes
- Custom exception handlers might see objects of class
\SensitiveParameterValue, despite the parameter having a different type
within the method's signature.
You mentioned “by emitting errors”, but I don't think that this is
applicable here, since #[\SensitiveParameter]
does not emit an error
by itself and because it also affects the debug_backtrace()
function,
which is sometimes used for meta-programming.
I don't mean to be rude and use your own words against you, but I found
this quote in the "Why an attribute and not a keyword?" section of
your #[\Override] RFC:This RFC proposes an attribute instead of a keyword, because contrary
to
other modifiers (e.g. visibility) that are part of the method
signature,
the attribute does not affect behavior or compatibility for users that
further extend a given class and neither does it affect users that
call the
method. It is purely an assistance to the author of a given class.(and I apologize if I misunderstand your intention or take it out of
context completely), but
I like the sentiment that "the attribute does not affect behavior or
compatibility for users"
and that it's part of the argument for an attribute instead of a
keyword
(language construct).I feel like #[NoDiscard] is getting dangerously close to being part of
a
method signature,
especially if it throws exceptions at run-time (which I know it doesn't
do
in your RFC, so I'm less against it).
The situation with #[\NoDiscard]
is indeed different to
#[\Override]
, because it affects the user of the API rather than the
creator of it. But as you said, it doesn't throw an Exception by
itself, but emits a warning which like other warnings only affects
behavior if you specifically decide to do so via a custom error handler.
And even if it would throw an Exception, having the Attribute would be
“more visible” than an Exception that the function itself throws, since
PHP does not implement “checked Exceptions” as in Java and unless the
author adds a /** @throws SomeException */
PHPDoc it's not clear what
Exceptions might be thrown (or if any Exceptions are thrown at all).
Given that other languages also implement it as Attribute + Warning, I'd
say we're in good company here. In case of C, using the `-Werror' flag
for your compiler would then be equivalent to a throwing error handler
:-)
Best regards
Tim Düsterhus
Am 2025-02-13 09:16, schrieb Jakob Givoni:
Attributes were added as a structured replacement for docblock props
and I don't like it when they affect how a program actually runs (as
long as you're not using reflection).Excluding the
#[\Attribute]
attribute, PHP currently has 5 native
attributes and they all affect how the program runs. The initial
accepted Attribute RFC even lists several “behavior-affecting”
attributes in the “Use Cases” section:
https://wiki.php.net/rfc/attributes_v2#use_cases. It is probably fair
to say that use-cases like#[\NoDiscard]
do not go against the
vision intended by the Attribute RFC.You could think of it as the PHP engine using Reflection internally to
do something differently.
I don't agree, but for a different reason.
None of the current attributes (ReturnTypeWillChange,
AllowDynamicProperties, SensitiveParameter, Override, and Deprecated)
change the behaviour of how a program runs. They only add warnings. with
the exception of AllowDynamicProperties to be an actual 'feature' in PHP
9.0 (now it's only a deprecation warning silencer).
That's the same with this suggested NoDiscard, it doesn't change how a
program is run — it merely adds a warning.
cheers,
Derick
None of the current attributes (ReturnTypeWillChange,
AllowDynamicProperties, SensitiveParameter, Override, and Deprecated)
change the behaviour of how a program runs. They only add warnings. with
the exception of AllowDynamicProperties to be an actual 'feature' in PHP
9.0 (now it's only a deprecation warning silencer).
For clarity, it's not AllowDynamicProperties attribute throwing an
exception in PHP9, it's use of a "dynamic" property on an object not
marked with this attribute. So, I'm not sure it's a valid exception to
the rule.
--
Aleksander Machniak
Kolab Groupware Developer [https://kolab.org]
Roundcube Webmail Developer [https://roundcube.net]
PGP: 19359DC1 # Blog: https://kolabian.wordpress.com
None of the current attributes (ReturnTypeWillChange,
AllowDynamicProperties, SensitiveParameter, Override, and Deprecated)
change the behaviour of how a program runs. They only add warnings. with
the exception of AllowDynamicProperties to be an actual 'feature' in PHP
9.0 (now it's only a deprecation warning silencer).For clarity, it's not AllowDynamicProperties attribute throwing an
exception in PHP9, it's use of a "dynamic" property on an object not
marked with this attribute. So, I'm not sure it's a valid exception to
the rule.
You're absolutely right that it's not the use of the property that causes
exceptions
to be thrown, but it's still a valid exception to the rule, since the use
of the property
changes how a program runs (which was the point that was being made).
Best,
Jakob
Volker and I would like to start discussion on our RFC to allow "Marking
return values as important (#[\NoDiscard])".Please find the following resources for your reference:
- RFC: https://wiki.php.net/rfc/marking_return_value_as_important
- Implementation: https://github.com/php/php-src/pull/17599
Hello everyone,it's been two weeks, but given the feedback we received, we don't feel
the discussion didn't reach a conclusive resolution yet.So we wanted to solicit additional opinions. In an attempt to
summarize the discussion, how we see it, there are two main points of
contention we'd like to discuss further.a) Intentionally discarding the return value
The
(void)flock($fp);
syntax was the main thing discussed on the list.
With$_ = flock($fp);
as the most suggested alternative.The issue with using
$_
as it currently stands, is that the unused
variable would be cleaned up by OPcache. Should we instead have a
special case in OPcache ensuring it does not apply its optimization to
a variable if it's named$_
instead? The semantic difference would
be that $_ would keep returned values (and objects) alive and in
memory, whereas (void) would discard them immediately. Leading to
different times when, for example, destructors would be called.The consequence of this would be that 3rd party tools like IDEs and
Static and Dynamic Code Analyzers would also have to build that
special case in to not produce "unused variable" warnings.We're also happy to change the secondary vote to "(void) vs $_" as we
feel there needs to be a way to discard the return value consistently
for this to be a complete and usable feature.
I would not be in favour of special casing this, and henceforth the
(void) cast makes more sense to me. Mostly, because the special casing
has to be done in many places, and in many tools.
b) Naming of the attribute
Nobody mentioned this on the list, but before opening a vote we'd like
to heard if the attribute name makes sense to you.We've chosen #[NoDiscard] as it's also used in C and C++. See [1] for
the full list of references. If you feel this doesn't fit with PHP, we
welcome other suggestions.
I think it fits, and there is already precedence.
cheers,
Derick
--
https://derickrethans.nl | https://xdebug.org | https://dram.io
Author of Xdebug. Like it? Consider supporting me: https://xdebug.org/support
mastodon: @derickr@phpc.social @xdebug@phpc.social
Hi
Volker and I would like to start discussion on our RFC to allow
"Marking return values as important (#[\NoDiscard])".Please find the following resources for your reference:
- RFC: https://wiki.php.net/rfc/marking_return_value_as_important
- Implementation: https://github.com/php/php-src/pull/17599
Best regards
Tim Düsterhus
Apologies if this has already been brought up; I haven't read the entire
thread, but isn't the entire premise of this RFC based on a falsehood?
This kind of “partial success” can only be reliably communicated by
means of a return value
Exceptions are objects, so you can attach whatever additional
information you wish to that object.
Perhaps the word "reliably" is doing a lot of heavy lifting in that
sentence, but if you would refute me (which presumably you will), then I
think this needs to be explained in better detail in the RFC, because it
looks to me that exploiting object properties is just as viable as
returning something, and probably preferred, since this solves your
problem of the user not handling the failure.
Cheers,
Bilge
Hi
Apologies for the delay in getting back to you. Volker was out of office
and I try to coordinate the replies with him to not misrepresent his
opinion.
Am 2025-02-12 14:17, schrieb Bilge:
Apologies if this has already been brought up; I haven't read the
entire thread, but isn't the entire premise of this RFC based on a
falsehood?
We do not believe so.
This kind of “partial success” can only be reliably communicated by
means of a return valueExceptions are objects, so you can attach whatever additional
information you wish to that object.Perhaps the word "reliably" is doing a lot of heavy lifting in that
sentence, but if you would refute me (which presumably you will), then
I think this needs to be explained in better detail in the RFC, because
it looks to me that exploiting object properties is just as viable as
returning something, and probably preferred, since this solves your
problem of the user not handling the failure.
Perhaps one can find a better word than “reliably” there, nevertheless:
We disagree that throwing an Exception is an appropriate solution to
communicate partial success. While it certainly would ensure that
developers can't forget to handle the (partial) failure case, it would
also be a very non-idiomatic use of Exceptions. Instead of being able to
check ->getMessage()
or ->getCode()
to find out the cause, which is
automatically supported by generic Exception-handling mechanisms as
provided by every framework, one needs to specifically call a
->getResults()
methods to find out the individual results.
And when it's a generic function where depending on the type of item
processed, one is also interested in the “return value” of the
successful cases, instead of the binary success/failure option, it would
become very unwieldy, requiring the use of a single-statement try-block
to handle both the “all successful” and the “at least one failure” case,
ending up in a “return value with extra steps” situation.
try {
$results = bulk_process($items);
/* All successful. */
} catch (BulkProcessingFailures $e) {
$results = $e->getResults();
/* At least one failed. */
}
foreach ($results as $item => $result) {
if ($result instanceof SuccessResult) {
echo "Created item ", $item, " with ID: ", $result->getId(),
PHP_EOL;
} else {
echo "Failed to create item ", $item, PHP_EOL;
}
}
Best regards
Tim Düsterhus
Hi Tim and Volker,
I'd be honest, I have a very negative attitude towards this proposal
and I'd be voting against it. It seems to me like it's creating a
problem and then trying to find a solution for it.
A return value is always supposed to be used. If some API is returning
a value that can be safely ignored, it's a badly designed API. If a
developer forgets to use a return value, a static analysis tool should
inform the developer of the mistake. I understand that not all
functions are pure like in your example, but adding an attribute
doesn't feel like the solution. In fact, it's creating a problem for
users who want to ignore the value, which you then propose to solve
with (void) cast.
My second gripe is the use of E_WARNING
and E_USER_WARNING. I am of
the opinion that warnings in PHP are a really bad idea. PHP isn't a
language like C or C++ where the warnings would show up only during
the compilation phase. In PHP warnings are runtime errors. The code
should emit an exception instead of a warning. It would also make it
much easier to handle and you wouldn't need any special construct to
allow users to ignore the new attribute. And I am really not a fan of
the PHP engine generating E_USER_WARNING
which should be reserved only
for warnings triggered by trigger_error.
The examples you used don't support the need for the new attribute.
Regarding the DateTimeImmutable methods, you said yourself: "The
methods are badly named, because they do not actually set the updated
value". So your proposal suggests adding a brand new thing to PHP to
deal with bad method names?
This problem should be solved using static analysers, IDE, and proper
code refactoring.
Regards,
Kamil
Hi
Am 2025-02-12 15:07, schrieb Kamil Tekiela:
I'd be honest, I have a very negative attitude towards this proposal
and I'd be voting against it. It seems to me like it's creating a
problem and then trying to find a solution for it.
Given that even Rust with a modern stdlib and ergonomic language
features has such an attribute, we do not believe it's an invented
problem. With the bulk_process()
example, we also tried to provide
coherent user-story with a use-case that the readers might've already
encountered themselves in the same or a similar fashion. Personally I
encountered situations where I wished such an attribute would've warned
me both in PHP and JavaScript/TypeScript. Also in C of course, but given
that C doesn't have Exceptions, the situation is somewhat different.
For an additional example use-case, consider a function returning a
resource object with side-effects which will automatically clean up the
resource when being destructed. Specifically a function to spawn a new
process or thread:
class Process {
public function __destruct() { /* kill the process */ }
}
#[NoDiscard("the process will be terminated when the Process object
goes out of scope")]
function start_process(string $executable): Process {
return new Process($executable);
}
start_process('/my/background/process');
Depending on timing, the process might or might not run to completion
before PHP gets around to kill it, for example when a step debugger is
attached to PHP, making the bug disappear when debugging.
Related to the above example and also to the flock()
function: A
developer might want to write a locking function that returns a lock
resource that will automatically unlock when it goes out of scope,
preventing the unlock from being forgotten. It might not be immediately
obvious that the locking function returns a lock resource, especially
when it's associated with another object:
$cacheDriver->lock('someCacheEntry');
Is there a $cacheDriver->unlock('someCacheEntry')
or will ->lock()
return a lock resource? Having the attribute on the lock()
method can
help.
Searching for language:rust "must_use ="
on GitHub reveals that a
common use-case in Rust is lazy iterators, where you can call
higher-order functions (e.g. map() or filter()), but they will not do
anything unless you explicitly “poll” them at least once. As far as I
know, Java has lazy streams that behave identically.
A return value is always supposed to be used. If some API is returning
a value that can be safely ignored, it's a badly designed API. If a
We agree that in a greenfield ecosystem, we would make ignoring the
return value of a function a warning by default (with an opt-out
mechanism), as done in Swift. However in PHP we do not have a greenfield
situation, there a number of functions where the return value is useless
and only exists for historical reasons. This includes all functions
which nowadays have a return-type of just true
, which was only added
for this singular purpose.
There's cases where ignoring the return value is safe and reasonable,
without being a badly designed API. array_pop()
would come to mind: If
one is only interested in the side-effect of removing the last element
in the array, one does not need the return value without necessarily
doing something unsafe.
doesn't feel like the solution. In fact, it's creating a problem for
users who want to ignore the value, which you then propose to solve
with (void) cast.
Ignoring the return value of functions having the attribute should be a
rare occurrence given the intended use-case of pointing out unsafe
situations. But as with static analyzers there needs to solution to
suppress warnings, after carefully verifying that the warning is not
applicable in a given situation.
the compilation phase. In PHP warnings are runtime errors. The code
should emit an exception instead of a warning. It would also make it
See Volker's reply to Derick.
much easier to handle and you wouldn't need any special construct to
allow users to ignore the new attribute. And I am really not a fan of
Even if an Exception would be thrown, there would be a mechanism to
suppress the Exception. A catch-block wouldn't work, because if the
Exception is thrown, the function is not executed / control flow is
interrupted.
the PHP engine generating
E_USER_WARNING
which should be reserved only
for warnings triggered by trigger_error.
This follows the lead of the #[\Deprecated]
attribute, which emits
E_USER_DEPRECATED
for userland functions and E_DEPRECATED
for native
functions, despite being triggered by the engine.
The examples you used don't support the need for the new attribute.
Regarding the DateTimeImmutable methods, you said yourself: "The
methods are badly named, because they do not actually set the updated
value". So your proposal suggests adding a brand new thing to PHP to
deal with bad method names?
DateTimeImmutable
is not part of the “Use Cases” section: Our intended
use-case is the kind of bulk_process()
functionality that is used for
all the code snippets. But given that the attribute is also useful for
DateTimeImmutable
, we made it part of the proposal, without it being
part of the intended “user-story”.
This problem should be solved using static analysers, IDE, and proper
code refactoring.
See Volker's reply to Matteo.
Best regards
Tim Düsterhus
Hi
Am 2025-02-12 15:07, schrieb Kamil Tekiela:
I'd be honest, I have a very negative attitude towards this proposal
and I'd be voting against it. It seems to me like it's creating a
problem and then trying to find a solution for it.Given that even Rust with a modern stdlib and ergonomic language
features has such an attribute, we do not believe it's an invented
problem. With thebulk_process()
example, we also tried to provide
coherent user-story with a use-case that the readers might've already
encountered themselves in the same or a similar fashion. Personally I
encountered situations where I wished such an attribute would've warned
me both in PHP and JavaScript/TypeScript. Also in C of course, but given
that C doesn't have Exceptions, the situation is somewhat different.For an additional example use-case, consider a function returning a
resource object with side-effects which will automatically clean up the
resource when being destructed. Specifically a function to spawn a new
process or thread:class Process { public function __destruct() { /* kill the process */ } } #[NoDiscard("the process will be terminated when the Process object
goes out of scope")]
function start_process(string $executable): Process {
return new Process($executable);
}start_process('/my/background/process');
Depending on timing, the process might or might not run to completion
before PHP gets around to kill it, for example when a step debugger is
attached to PHP, making the bug disappear when debugging.
You yourself has said this will behave differently depending on whether or not opcache is available. Would that not make the warning disappear in dev (where opcache and xdebug are not usually compatible) only to show up in production, potentially causing a worse situation for those throwing warnings as exceptions?
A return value is always supposed to be used. If some API is returning
a value that can be safely ignored, it's a badly designed API. If aWe agree that in a greenfield ecosystem, we would make ignoring the
return value of a function a warning by default (with an opt-out
mechanism), as done in Swift. However in PHP we do not have a greenfield
situation, there a number of functions where the return value is useless
and only exists for historical reasons. This includes all functions
which nowadays have a return-type of justtrue
, which was only added
for this singular purpose.
There will eventually be a php 9, where BC changes will be possible.
There's cases where ignoring the return value is safe and reasonable,
without being a badly designed API.array_pop()
would come to mind: If
one is only interested in the side-effect of removing the last element
in the array, one does not need the return value without necessarily
doing something unsafe.
I have to stop you here. It is absolutely NOT safe and reasonable to ignore the output of array_pop. You popped it for a reason. If you just care about removing the last element, then you should use unset. Unset gives the intention. If I review code with array_pop, I’ll spend quite a bit of time checking that it was intentionally ignoring the return value (and leave a comment of the same, asking to use unset instead).
doesn't feel like the solution. In fact, it's creating a problem for
users who want to ignore the value, which you then propose to solve
with (void) cast.Ignoring the return value of functions having the attribute should be a
rare occurrence given the intended use-case of pointing out unsafe
situations. But as with static analyzers there needs to solution to
suppress warnings, after carefully verifying that the warning is not
applicable in a given situation.
And what if it isn’t? There’s a non-zero possibility we will be seeing RFCs for years arguing over whether one function or another should have this attribute (like array_pop), not to mention libraries using it as well.
the compilation phase. In PHP warnings are runtime errors. The code
should emit an exception instead of a warning. It would also make itSee Volker's reply to Derick.
much easier to handle and you wouldn't need any special construct to
allow users to ignore the new attribute. And I am really not a fan ofEven if an Exception would be thrown, there would be a mechanism to
suppress the Exception. A catch-block wouldn't work, because if the
Exception is thrown, the function is not executed / control flow is
interrupted.
Many people turn warnings into exceptions. I’m not a fan, personally, but the codebase I work in daily does this.
the PHP engine generating
E_USER_WARNING
which should be reserved only
for warnings triggered by trigger_error.This follows the lead of the
#[\Deprecated]
attribute, which emits
E_USER_DEPRECATED
for userland functions andE_DEPRECATED
for native
functions, despite being triggered by the engine.The examples you used don't support the need for the new attribute.
Regarding the DateTimeImmutable methods, you said yourself: "The
methods are badly named, because they do not actually set the updated
value". So your proposal suggests adding a brand new thing to PHP to
deal with bad method names?
DateTimeImmutable
is not part of the “Use Cases” section: Our intended
use-case is the kind ofbulk_process()
functionality that is used for
all the code snippets. But given that the attribute is also useful for
DateTimeImmutable
, we made it part of the proposal, without it being
part of the intended “user-story”.This problem should be solved using static analysers, IDE, and proper
code refactoring.See Volker's reply to Matteo.
Best regards
Tim Düsterhus
— Rob
Hey Rob,
Sorry for writing the mail again, I just noticed I forgot to include the
list on my first reply to you, also corrected a mistake in the second
paragraph.
There will eventually be a php 9, where BC changes will be possible.
I don't assume PHP will change the behavior of widely used core functions
to throw exceptions in PHP 9 as the BC impact will be colossal, hurting
adoption. Or am I misunderstanding you here?
The point that there might be arguments over usage in internal code is why
we went with an "only if it is a clear problem, especially when the
function has important side effects" policy to avoid this. There are not a
too many examples in core, more in userland.
I have to stop you here. It is absolutely NOT safe and reasonable to
ignore the output of array_pop. You popped it for a reason. If you just
care about removing the last element, then you should use unset. Unset
gives the intention. If I review code with array_pop, I’ll spend quite a
bit of time checking that it was intentionally ignoring the return value
(and leave a comment of the same, asking to use unset instead).
There are plenty of codebases where array_pop is used for this reason.
Identifying the last element of a php array.
unset($foo[array_key_last($foo)]);
is only possible since 7.3 and not
widely used. array_pop is also shorter and faster for the same effect (when
used on unknown array shapes ofc). The examples are plenty
https://github.com/search?q=repo%3AWordPress%2FWordPress%20array_pop&type=code
Regards,
Volker
Hey Rob,
Sorry for writing the mail again, I just noticed I forgot to include the list on my first reply to you, also corrected a mistake in the second paragraph.
There will eventually be a php 9, where BC changes will be possible.
I don't assume PHP will change the behavior of widely used core functions to throw exceptions in PHP 9 as the BC impact will be colossal, hurting adoption. Or am I misunderstanding you here?
The point that there might be arguments over usage in internal code is why we went with an "only if it is a clear problem, especially when the function has important side effects" policy to avoid this. There are not a too many examples in core, more in userland.
I was merely pointing out that if you were to want to make BC breaks, you can. You just won’t get it :soon:
I have to stop you here. It is absolutely NOT safe and reasonable to ignore the output of array_pop. You popped it for a reason. If you just care about removing the last element, then you should use unset. Unset gives the intention. If I review code with array_pop, I’ll spend quite a bit of time checking that it was intentionally ignoring the return value (and leave a comment of the same, asking to use unset instead).
There are plenty of codebases where array_pop is used for this reason. Identifying the last element of a php array.
unset($foo[array_key_last($foo)]);
is only possible since 7.3 and not widely used. array_pop is also shorter and faster for the same effect (when used on unknown array shapes ofc). The examples are plenty https://github.com/search?q=repo%3AWordPress%2FWordPress%20array_pop&type=codeRegards,
Volker
This feels like a rationalization, and not a reason. It has a return value, and there are more expressive ways to remove a value from an array that makes the intention clearer. If you aren’t using the return value, you should make it clear that is the intention. That’s why I said it should be included in this, for exactly the rationale you gave. It IS faster, but it clearly has a side effect that should be used or intentionally discarded.
— Rob
I must agree with Kamil. I don't see practical benefits of this feature
that would surpass the implications it has for the language. We already
have static analysis handling such cases and it can be extended even to
non-pure functions. Moreover, syntax (void) adds additional complexity to
beginners' understanding of the typing system. The second option with $_ is
BC on the other hand, so both options are unsatisfying. And honestly, I
don't have good ideas for it.
Kind regards,
Jorg Sowa
Hi
Volker and I would like to start discussion on our RFC to allow "Marking
return values as important (#[\NoDiscard])".Please find the following resources for your reference:
- RFC: https://wiki.php.net/rfc/marking_return_value_as_important
- Implementation: https://github.com/php/php-src/pull/17599
Best regards
Tim Düsterhus
I don't see this as something useful when you want to force interactions
with variables. I do see value in using this for the keeping and closing of
resources, but the only error you should ever get is when you use
#[NoDiscard]
without a return value.
<?php
#[NoDiscard]
function lockFile(string $path) {
$handle = fopen($path, 'r+');
if (! $handle || ! flock($handle, LOCK_EX)) {
throw new RuntimeException('Failed locking ' . $path);
}
return $handle;
}
// what we write
function main() {
lockFile($path);
doSomething();
echo 'hi';
}
// gets rewritten internally to something like
function main() {
$hidden = lockFile($path);
doSomething();
echo 'hi';
unset($hidden);
}
// if we do assign it, we omit it all
// what we write here would not get rewritten
function main() {
$handle = lockFile($path);
doSomething();
echo 'hi';
}
// if we were to ever get a "using"-like
// what we write
function main() {
using {
lockFile($path);
doSomething();
}
echo 'hi';
}
// gets rewritten internally to something like
function main() {
$hidden = lockFile($path);
doSomething();
unset($hidden);
echo 'hi';
}