Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Add support for PHP 8.5 #[\NoDiscard] #4253

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
DanielEScherzer wants to merge 11 commits into phpstan:2.1.x
base: 2.1.x
Choose a base branch
Loading
from DanielEScherzer:polyfill-NoDiscard-function

Conversation

Copy link

@DanielEScherzer DanielEScherzer commented Aug 21, 2025

No description provided.

Copy link
Author

Wasn't able to figure out instance methods or static methods, I'll work on those separately later

Copy link
Author

DanielEScherzer commented Aug 21, 2025
edited
Loading

PHP 8.5 linting failure is unrelated, caused by yours truly in php/php-src@c416191 // php/php-src#19154

Copy link
Member

This is a wrong approach. We don't need a new rule but instead:

  1. Add a new method on FunctionReflection and ExtendedMethodReflection that would reflect this. Maybe something like mustUseReturnValue(): TrinaryLogic or mustNotDiscardReturnValue(): TrinaryLogic although I don't like double negatives.
  2. Add a check in FunctionCallParametersCheck that would ask about this. There's already a similar check for pure functions.
  3. Do not forget about (void) and add proper support for this. This means support in MutatingScope::resolveType and also NodeScopeResolver (if not already covered by the common Cast parent node type).
  4. Flag NoDiscard attribute as an error above a void function.

Copy link
Author

This is a wrong approach. We don't need a new rule but instead:

  1. Add a new method on FunctionReflection and ExtendedMethodReflection that would reflect this. Maybe something like mustUseReturnValue(): TrinaryLogic or mustNotDiscardReturnValue(): TrinaryLogic although I don't like double negatives.

Per the docs https://phpstan.org/developing-extensions/backward-compatibility-promise

Interfaces with @api in their PHPDoc can be implemented and extended

so can this be added to FunctionReflection?

  1. Add a check in FunctionCallParametersCheck that would ask about this. There's already a similar check for pure functions.

FunctionCallParametersCheck doesn't include pure anywhere in the file?

  1. Do not forget about (void) and add proper support for this. This means support in MutatingScope::resolveType and also NodeScopeResolver (if not already covered by the common Cast parent node type).

Is this necessary? The (void) is for runtime suppression, and a comment can be used for suppressing the phpstan issue

  1. Flag NoDiscard attribute as an error above a void function.

Is that necessary? That means a whole other rule for something that PHP would complain about at compile time anyway

Copy link
Member

so can this be added to FunctionReflection?

Yes, some interfaces cannot be extended. If you try that you get an error: https://phpstan.org/r/847a20b4-ce41-4763-b7f4-aa088ea30bca

FunctionCallParametersCheck doesn't include pure anywhere in the file?

I'm sorry, I was mistaken. I had these lines in mind (

if (
!$funcCall instanceof Node\Expr\New_
&& !$scope->isInFirstLevelStatement()
&& $scope->getKeepVoidType($funcCall)->isVoid()->yes()
) {
$errors[] = RuleErrorBuilder::message($voidReturnTypeUsed)
->identifier(sprintf('%s.void', $nodeType))
->line($funcCall->getStartLine())
->build();
}
). They produce errors like "Result of method X (void) is used." We actually need to check the opposite here.

Call to pure functions without using their result is covered by these rules:

  • CallToFunctionStatementWithoutSideEffectsRule
  • CallToMethodStatementWithoutSideEffectsRule
  • CallToStaticMethodStatementWithoutSideEffectsRule

But I think we first should try to report this in FunctionCallParametersCheck. $scope->isInFirstLevelStatement() is great for that.

Is this necessary? The (void) is for runtime suppression

The user already expresses their intention with (void). So PHPStan should not produce this message. If you'd ask $scope->isInFirstLevelStatement() it'd be taken care of automatically.

Is that necessary? That means a whole other rule for something that PHP would complain about at compile time anyway

Yes. PHPStan users expect this to be reported. We should report all the situations from the "Constraints" section in the RFC (https://wiki.php.net/rfc/marking_return_value_as_important).

Copy link
Author

So PHPStan should not produce this message. If you'd ask $scope->isInFirstLevelStatement() it'd be taken care of automatically.

I couldn't figure out using the scope, so I added a visitor to indicate method calls that use (void)
A few e2e tests are now failing, I think because of #[\NoDiscard] attributes that are now being complained about, I should be able to address that tomorrow

Yes. PHPStan users expect this to be reported. We should report all the situations from the "Constraints" section in the RFC (https://wiki.php.net/rfc/marking_return_value_as_important).

What I mean is, is this necessary for this patch? Errors about when the attribute is applied are different from errors about when the methods with the attribute are called

Copy link
Member

What I mean is, is this necessary for this patch?

@DanielEScherzer My process for adding support for new PHP features is that I read the whole RFC and I update PHPStan with everything that should be added about the feature.

If you don't do that, then I'd have to keep in mind to do it myself.

DanielEScherzer reacted with thumbs up emoji

Copy link
Member

Another important thing from the RFC:

As the result of a (void) cast is not defined, the (void) cast is a statement, not an expression. Using it within an expression will result in a syntax error.

Another rule checking the (void) cast with isInFirstLevelStatement would be great for that.

DanielEScherzer reacted with thumbs up emoji

@DanielEScherzer DanielEScherzer deleted the polyfill-NoDiscard-function branch August 30, 2025 15:13
@DanielEScherzer DanielEScherzer restored the polyfill-NoDiscard-function branch August 30, 2025 15:14
Copy link
Author

Sorry, tried to rename the branch but didn't realize that that closes the PR :(

Copy link
Author

Okay, I have

  • added a rule to complain about (void) in expressions
  • added a case to the existing attribute.target issues for using NoDiscard on property hooks
  • fixed the handling of instance and method calls to address the false positives

@DanielEScherzer DanielEScherzer changed the title (削除) Add support for PHP 8.5 #[\NoDiscard] on functions (削除ここまで) (追記) Add support for PHP 8.5 #[\NoDiscard] (追記ここまで) Aug 30, 2025
Copy link
Author

@ondrejmirtes my understanding is that this is awaiting review, is there something else I should do? (Don't want to nag, just want to make sure you aren't waiting for me to do something while I'm waiting for you or another reviewer)

Copy link
Member

Please wait for the next review, I'm working through my queue 😊

DanielEScherzer reacted with thumbs up emoji

Copy link
Author

Okay, just wanted to make sure you were not waiting for something from me, take your time

Copy link
Member

@ondrejmirtes ondrejmirtes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is starting to get in a good shape 👍

I'm missing checking whether #[NoDiscard] is above a void-returning function/method. FunctionDefinitionCheck would be a natural place for that.

public function hasNoDiscardAttribute(): TrinaryLogic
{
foreach ($this->attributes as $attrib) {
if ($attrib->getName() === 'NoDiscard') {
Copy link
Member

@ondrejmirtes ondrejmirtes Sep 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Has to work even with wrong case: https://3v4l.org/WGdBI/rfc#vgit.master

public function hasNoDiscardAttribute(): TrinaryLogic
{
foreach ($this->attributes as $attrib) {
if ($attrib->getName() === 'NoDiscard') {
Copy link
Member

@ondrejmirtes ondrejmirtes Sep 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dtto

@@ -161,4 +161,9 @@ public function getAttributes(): array
return [];
}

public function hasNoDiscardAttribute(): TrinaryLogic
{
return TrinaryLogic::createYes();
Copy link
Member

@ondrejmirtes ondrejmirtes Sep 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't seem like that: https://3v4l.org/X1Lhl/rfc#vgit.master

public function hasNoDiscardAttribute(): TrinaryLogic
{
foreach ($this->attributes as $attrib) {
if ($attrib->getName() === 'NoDiscard') {
Copy link
Member

@ondrejmirtes ondrejmirtes Sep 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dtto

public function hasNoDiscardAttribute(): TrinaryLogic
{
foreach ($this->attributes as $attrib) {
if ($attrib->getName() === 'NoDiscard') {
Copy link
Member

@ondrejmirtes ondrejmirtes Sep 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dtto

public function hasNoDiscardAttribute(): TrinaryLogic
{
foreach ($this->attributes as $attrib) {
if ($attrib->getName() === 'NoDiscard') {
Copy link
Member

@ondrejmirtes ondrejmirtes Sep 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dtto

This is probably worth creating a NoDiscardHelper class

@@ -40,6 +40,7 @@ public function check(
array $attrGroups,
int $requiredTarget,
string $targetName,
bool $isPropertyHook = false,
Copy link
Member

@ondrejmirtes ondrejmirtes Sep 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't have to be optional.

public function enterNode(Node $node): ?Node
{
if ($node instanceof Void_) {
$this->pendingVoidCast = true;
Copy link
Member

@ondrejmirtes ondrejmirtes Sep 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get the need for this visitor, can you elaborate please?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Reviewers

@ondrejmirtes ondrejmirtes ondrejmirtes requested changes

Assignees
No one assigned
Labels
None yet
Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

AltStyle によって変換されたページ (->オリジナル) /