RFC: Marking return values as important (#[\NoDiscard])

php.internals

Tim Düsterhus

1 year ago
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

Rob Landers

1 year ago
On Wed, Jan 29, 2025, at 16:12, Tim Düsterhus wrote:
> 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

Tim Düsterhus

1 year ago
Hi On 1/29/25 20:31, Rob Landers wrote:
> 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

Rob Landers

1 year ago
On Wed, Jan 29, 2025, at 20:50, Tim Düsterhus wrote:
> Hi > > On 1/29/25 20:31, Rob Landers wrote: > > 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 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

Christian Schneider

1 year ago
Am 29.01.2025 um 21:16 schrieb Rob Landers <rob@bottled.codes>:
> On Wed, Jan 29, 2025, at 20:50, Tim Düsterhus wrote: >> 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

Rowan Tommins [IMSoP]

1 year ago
On 29 January 2025 22:48:03 GMT, Christian Schneider <cschneid@cschneid.com> wrote:
>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]

Tim Düsterhus

1 year ago
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 into `null`. 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 the `void` 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

Claude Pache

1 year ago
> 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 into `null`. 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 the `void` 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

Tim Düsterhus

1 year ago
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

Tim Düsterhus

1 year ago
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

Tim Düsterhus

1 year ago
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

Rob Landers

1 year ago
On Thu, Jan 30, 2025, at 16:50, Tim Düsterhus wrote:
> 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).
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

Matteo Beccati

1 year ago
Hi Tim, On 29/01/2025 16:12, Tim Düsterhus wrote:
> 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
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/

Volker Dusch

1 year ago
Hi Matteo, On Thu, Jan 30, 2025 at 1:07 PM Matteo Beccati <php@beccati.com> wrote:
> > 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

Derick Rethans

1 year ago
On Wed, 29 Jan 2025, Tim Düsterhus wrote:
> 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
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

Volker Dusch

1 year ago
On Tue, Feb 4, 2025 at 12:10 PM Derick Rethans <derick@php.net> wrote:
> 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 Dusch

1 year ago
On Wed, Jan 29, 2025 at 4:12 PM Tim Düsterhus <tim@bastelstu.be> wrote:
> 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. 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

Larry Garfield

1 year ago
On Wed, Feb 12, 2025, at 5:52 AM, Volker Dusch wrote:
> On Wed, Jan 29, 2025 at 4:12 PM Tim Düsterhus <tim@bastelstu.be> wrote: >> 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.
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

Tim Düsterhus

1 year ago
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

Larry Garfield

1 year ago
On Thu, Feb 13, 2025, at 8:16 AM, Tim Düsterhus wrote:
> 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
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

Derick Rethans

1 year ago
On Thu, 13 Feb 2025, Larry Garfield wrote:
> On Thu, Feb 13, 2025, at 8:16 AM, Tim Düsterhus wrote: > > 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. > > 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

Weedpacket

1 year ago
On 2025-02-13 00:52, Volker Dusch wrote:
> > 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.

Jakob Givoni

1 year ago
My thoughts overall on this: 0. 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....) 1. 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). 2. 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. 3. Naming of the attribute: I think the most precise name (if I understand the purpose correctly) is: #[ReturnValueMayContainCriticalInformation] 4. 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

[ ]

1 year ago
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?

Tim Düsterhus

1 year ago
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

Tim Düsterhus

1 year ago
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.
> 3. 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

Jakob Givoni

1 year ago
On Thu, Feb 13, 2025 at 3:17 PM Tim Düsterhus <tim@bastelstu.be> wrote:
> 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

Tim Düsterhus

1 year ago
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
> 2. 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

Derick Rethans

1 year ago
On Thu, 13 Feb 2025, Tim Düsterhus wrote:
> 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

A.L.E.C

1 year ago
On 14.02.2025 12:57, Derick Rethans wrote:
> 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

Jakob Givoni

1 year ago
On Fri, Feb 14, 2025 at 1:10 PM Aleksander Machniak <alec@alec.pl> wrote:
> On 14.02.2025 12:57, Derick Rethans wrote: > > 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

Derick Rethans

1 year ago
On Wed, 12 Feb 2025, Volker Dusch wrote:
> On Wed, Jan 29, 2025 at 4:12 PM Tim Düsterhus <tim@bastelstu.be> wrote: > > > 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

Bilge

1 year ago
On 29/01/2025 15:12, Tim Düsterhus wrote:
> 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

Tim Düsterhus

1 year ago
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 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.
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

Kamil Tekiela

1 year ago
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

Tim Düsterhus

1 year ago
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

Rob Landers

1 year ago
On Wed, Feb 12, 2025, at 17:43, Tim Düsterhus wrote:
> 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.
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 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 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 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.
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 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 >
— Rob

Volker Dusch

1 year ago
> On Wed, Feb 12, 2025 at 9:57 PM Rob Landers <rob@bottled.codes> wrote:
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

Rob Landers

1 year ago
On Thu, Feb 20, 2025, at 16:54, Volker Dusch wrote:
> > On Wed, Feb 12, 2025 at 9:57 PM Rob Landers <rob@bottled.codes> wrote: > 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=code > > Regards, > 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

Joerg Sowa

1 year ago
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

Volker Dusch

1 year ago
On Wed, Feb 12, 2025 at 10:44 PM Jorg Sowa <jorg.sowa@gmail.com> wrote:
> 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 Jorg, To be clear: There is no BC break with $_. Opcache doesn't discard variables containing objects, so there is no observable change in behavior from what we're proposing. It doesn't change the variable semantics at all. Or am I misunderstanding what you're referring to? In addition, I reached out to see if Ondřej (Creator of PHPStan) had an opinion: https://phpc.social/@OndrejMirtes/114040888791921128 - So on top of our previous arguments that PHP shouldn't depend on 3rd party tools for everything, there is also at least interest in having this in core from the tool creators.
> @edorian It already exists for PHPStan as a 3rd party extension:
https://github.com/DaveLiddament/phpstan-php-language-extensions (MustUseResult)
> Additionally, PHPStan itself reports this if you call a @phpstan-pure
method or function and not use its result.
> This attribute would be valuable for functions that aren’t pure but
you’re still supposed to read the returned value. 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

Lynn

1 year ago
On Wed, Jan 29, 2025 at 4:14 PM Tim Düsterhus <tim@bastelstu.be> wrote:
> 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. https://3v4l.org/L4bOc <?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'; }

Volker Dusch

1 year ago
On Wed, Jan 29, 2025 at 4:12 PM Tim Düsterhus <tim@bastelstu.be> wrote:
> 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 >
Hi, Thank you to everyone who responded to our call for comments on the list here, and to Ondřej who replied on Mastodon to the question if the property would be useful for Static Analysis tools: https://phpc.social/@OndrejMirtes/114040888791921128 Given that the thread has died down, and all points have been discussed, we plan to start the voting later this week unless any important point comes up. We have also updated the RFC to note that, if (void) is not voted for in the secondary vote, we'd special case `$_` as a discard outlet such that OPcache will not optimize the unused variable. 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

Marc

1 year ago

Tim Düsterhus

1 year ago
Hi Marc On 3/3/25 17:14, Marc B. wrote:
> In this thread, I only found the information that currently OPcache does not > discard such unused assignments. It would be good to know if such optimization > could be considered to not end up in a situation that such optimization would be > useful but can't be applied as of this feature.
Unless it knows for sure that the function is unable to return an object (this includes arrays, which could contain objects), OPcache is already disallowed to elide the dead store. Otherwise this would result in an observable behavioral change, since destructors are called immediately rather than when the variable goes out of scope / is overwritten. For practically all modern code this means that this optimization only affects internal functions, since OPcache only looks at a single file. Thus it does not know about the return type of userland functions defined in a different file. In fact, if you call `get_defined_vars()` or similar functions the dead store elimination is also disabled, since that would result in an observable behavioral change. Should the `(void)` cast not be accepted, we will only special case the assignment to `$_` to not be elided, even if OPcache knows that the function will never return an object. The behavior for other variables will remain unchanged. Best regards Tim Düsterhus

Alexandru Pătrănescu

1 year ago
Hi Tim, On Tue, Mar 4, 2025, 22:16 Tim Düsterhus <tim@bastelstu.be> wrote:
> Hi Marc > > Should the `(void)` cast not be accepted, we will only special case the > assignment to `$_` to not be elided, even if OPcache knows that the > function will never return an object. The behavior for other variables > will remain unchanged.
X Just asking, as my engine knowledge is a bit limited, Wouldn't it be possible that when OPcache would optimize away the unused variable assigned to a function, it could detect that that function have the NoDiscard attribute and also remove the attribute effect (warning triggering). Or identify that the function has the NoDiscard attribute and based on that do not optimize away the variable?
-- Alex

Tim Düsterhus

1 year ago
Hi On 3/4/25 23:08, Alexandru Pătrănescu wrote:
> Just asking, as my engine knowledge is a bit limited, > Wouldn't it be possible that when OPcache would optimize away the unused > variable assigned to a function, it could detect that that function have > the NoDiscard attribute and also remove the attribute effect (warning > triggering).
Not with how the implementation currently works: It's checking whether or not the FCALL opcode’s result is used or not. If the assignment is removed, the FCALL result will be unused.
> Or identify that the function has the NoDiscard attribute and based on that > do not optimize away the variable?
First things first: It's not a super important optimization to have, the assignment to a variable is one of the cheaper operations - and given that OPcache needs to know about the function it's also pretty limited in application. Of course you can also always just remove the assignment yourself (or ask your favorite IDE or static analyzer to point it out for you). That said: Checking for the attribute and performing the optimization is likely possible, though likely of limited cost/benefit. In any case that would be an implementation detail that does not change observable behavior. The RFC defines that `(void)` or alternatively `$_` will be the solution that guarantees that the warning will be suppressed (which will then also be part of the documentation), how exactly that will work internally is something that can and will change when refactoring and evolving the engine and OPcache. Best regards Tim Düsterhus

Alexandru Pătrănescu

1 year ago
On Tue, Mar 4, 2025, 23:22 Tim Düsterhus <tim@bastelstu.be> wrote:
> > > Or identify that the function has the NoDiscard attribute and based on > that > > do not optimize away the variable? > > First things first: It's not a super important optimization to have, the > assignment to a variable is one of the cheaper operations - and given > that OPcache needs to know about the function it's also pretty limited > in application. Of course you can also always just remove the assignment > yourself (or ask your favorite IDE or static analyzer to point it out > for you). > > That said: Checking for the attribute and performing the optimization is > likely possible, though likely of limited cost/benefit. In any case that > would be an implementation detail that does not change observable > behavior. The RFC defines that `(void)` or alternatively `$_` will be > the solution that guarantees that the warning will be suppressed (which > will then also be part of the documentation), how exactly that will work > internally is something that can and will change when refactoring and > evolving the engine and OPcache. > > Thank you for explaining.
My motivation for asking is because I believe that special casing $_ variable might not be a desirable solution, and I personally find it worse than (void) casting. And so, people might vote "No" for the first vote because they are not happy with either results of the second option. And maybe we can find a technical solution that doesn't require a behavior/documentation change if second vote fails. I agree that this optimization is not really important, so finding a way to avoid it would be nice.
-- Alex