Skip to content
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

Skip function-like and anonymous class nodes when checking if a function or method is a generator #3794

Open
wants to merge 2 commits into
base: 2.1.x
Choose a base branch
from

Conversation

sebkehr
Copy link

@sebkehr sebkehr commented Jan 23, 2025

Fixes: phpstan/phpstan#12462.

As is, when checking if a function (or method) is a generator (via PhpFunctionFromParserNodeReflection::isGenerator()) all subnodes are recursively checked for containment of a yield expression. Hence any function or method itself returning a yielding closure or arrow function is falsely determined to be a generator which in turn leads to incorrect return type checks as demonstrated in https://phpstan.org/r/5970e2e2-0aad-4927-8938-3f7d83e73a95.

The proposed fix therefore simply skips FunctionLike nodes in the above mentiond recursive check.

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.

@@ -281,7 +281,10 @@ private function nodeIsOrContainsYield(Node $node): bool
foreach ($node->getSubNodeNames() as $nodeName) {
$nodeProperty = $node->$nodeName;

if ($nodeProperty instanceof Node && $this->nodeIsOrContainsYield($nodeProperty)) {
if ($nodeProperty instanceof Node &&
!$nodeProperty instanceof FunctionLike &&
Copy link
Member

Choose a reason for hiding this comment

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

Would be great to also not descend into anonymous classes.

Copy link
Author

Choose a reason for hiding this comment

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

@ondrejmirtes Anonymous class nodes should now be skipped as well. I submitted the fix via Roave/BetterReflection#1479.

@sebkehr sebkehr force-pushed the improved_return_type_checks_wrt_yielding_closures branch from 710646b to e75270a Compare January 25, 2025 08:55
@sebkehr sebkehr force-pushed the improved_return_type_checks_wrt_yielding_closures branch from e75270a to 13d7837 Compare January 25, 2025 09:49
@sebkehr sebkehr changed the title Skip function-like nodes when checking if a function or method is a generator Skip function-like and anonymous class nodes when checking if a function or method is a generator Jan 25, 2025
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.

I just realized there's one more place that needs fixing. Check out CleaningParserTest. The job of CleaningParser is to erase function bodies of not-analyzed code (like in vendor/) before the AST is cached into memory.

I worry the code is faulty and it might do something like this:

Before:

function foo() {
    $fn = fn () => yield;
};

After:

function foo() {
    yield;
};

Changing function foo() from non-generator to a generator.

Thank you!

@sebkehr
Copy link
Author

sebkehr commented Jan 26, 2025

I just realized there's one more place that needs fixing. Check out CleaningParserTest. The job of CleaningParser is to erase function bodies of not-analyzed code (like in vendor/) before the AST is cached into memory.

Ah ok, I'll look into it.

@sebkehr sebkehr force-pushed the improved_return_type_checks_wrt_yielding_closures branch from 1025a9c to 32cf692 Compare January 30, 2025 06:42
@sebkehr sebkehr force-pushed the improved_return_type_checks_wrt_yielding_closures branch 3 times, most recently from 1b277d0 to d446d6c Compare January 30, 2025 07:07
@sebkehr sebkehr force-pushed the improved_return_type_checks_wrt_yielding_closures branch from d446d6c to e046593 Compare January 30, 2025 07:11
@sebkehr
Copy link
Author

sebkehr commented Jan 30, 2025

@ondrejmirtes

I added two more examples to tests/PHPStan/Parser/data/cleaning-1-before.php. Please also have a look on the expected outcome after cleaning in tests/PHPStan/Parser/data/cleaning-1-after.php.

With the proposed changes to src/Parser/CleaningVisitor.php the closure in ContainsClosure::doFoo() will now be erased not leading to any left-over yield statements just like the yielding array-function in ContainsClosure::doBar(). Is this the intended behavior?

I also added Baz::someGenerator3() containing yield as a subexpression which will now lead to a yield expression after cleaning.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Incorrect return type checks with yielding closures
2 participants