Implement @pure-unless-callable-is-impure#3482
Conversation
There was a problem hiding this comment.
Will be replaced by phpstan/phpdoc-parser#253
There was a problem hiding this comment.
WIP. = false will be removed.
|
Just updated phpdoc-parser here so you can use that. |
eddd22a to
f207c72
Compare
f207c72 to
8c756cc
Compare
|
@zonuexe any plans to move this forward? |
|
@staabm Hi, I know this branch is important but it was blocked due to my motivation. I'm going to rebase it on 2.0.x and do the necessary work. If I miss it tonight, I won't be able to start the rest of the work until next week, so it would be quicker for you to take over then. |
|
I don't want to put pressure on you. It was not clear to me whether you stopped working on it or not. I am totally fine if this takes a bit more time - thank you |
8c756cc to
389c7f0
Compare
|
No problem, this is a very interesting subject for me too, so I'm happy to work on it because I realized that there are other people who are interested in it. More importantly, I'm always helped by your hard work. |
2146ae0 to
1cc1fad
Compare
|
Friendly ping @zonuexe, still interested in this PR ? Do you have time to finish it ? |
|
@VincentLanglet |
adb02ca to
abc8cd8
Compare
abc8cd8 to
c4999be
Compare
|
Friendly ping @zonuexe I don't want to put pressure on you. It's just not clear to me whether you stopped working on it or not. It will unblock the phpstan/phpdoc-parser#259 pr :) |
|
Maybe I'm wrong, but I start to think this annotation is useless PHPStan already handle cb with immediately/later invoked callable, even for a pure function/method. So we just need to declare array_map and so as pure. |
|
i'm happy to help contribute towards a solution if it's decided which direction is best. |
|
Hi @devbanana feel free to work on this. Laybe @zonuexe could explain what he had in mind ; or @ondrejmirtes will have some guidelines |
|
This pull request has been marked as ready for review. |
|
|
||
| public function getPureUnlessCallableIsImpureParameters(): array | ||
| { | ||
| return []; |
There was a problem hiding this comment.
| return []; | |
| return $this->reflection->getPureUnlessCallableIsImpureParameters(); |
(seems we miss a test)
|
|
||
| public function getPureUnlessCallableIsImpureParameters(): array | ||
| { | ||
| return []; |
There was a problem hiding this comment.
| return []; | |
| return $this->nativeMethodReflection->getPureUnlessCallableIsImpureParameters(); |
|
|
||
| public function getPureUnlessCallableIsImpureParameters(): array | ||
| { | ||
| return []; |
There was a problem hiding this comment.
hmm does this miss the impl?
|
|
||
| public function getPureUnlessCallableIsImpureParameters(): array | ||
| { | ||
| return []; |
|
|
||
| public function getPureUnlessCallableIsImpureParameters(): array | ||
| { | ||
| return []; |
| return TrinaryLogic::createYes(); | ||
| } | ||
|
|
||
| public function getPureUnlessCallableIsImpureParameters(): array |
There was a problem hiding this comment.
also needs tests with constructor
There was a problem hiding this comment.
Good catch. You were right that method support was entirely non-functional, not only those stubs. Two gaps caused it.
PhpClassReflectionExtensionread the pure-unless map only from built-in method metadata, never from a userland method's own@pure-unless-callable-is-impuretag.PhpMethodReflection::getParameters()did not pass the flag on toPhpParameterReflection, while the function path already did.
I fixed both and made the wrappers you pointed at delegate. I also added a method regression test with pure, impure and opaque callbacks, including a union-typed receiver.
|
Follow-up on method support.
|
|
This pull request has been marked as ready for review. |
Resolve the tag into per-parameter flags and merge them across parent PHPDocs.
Add isPureUnlessCallableIsImpureParameter() on parameter reflections and getPureUnlessCallableIsImpureParameters() on function/method reflections, populated from PHPDoc and function metadata.
Return the parameter flags from getPhpDocs(), thread them through enterClassMethod(), and consult them when collecting impure points for calls.
array_filter/array_map/array_reduce, the array_u*/array_*_u* family, call_user_func(_array), forward_static_call(_array) and preg_replace_callback(_array) are pure unless their callback argument is impure.
Type-inference fixture plus function-metadata expectations.
The flag was silently dropped wherever parameters are re-wrapped (combineAcceptors, overrideParameterType, resolved template variants, unresolved method prototypes) and the BetterReflection function path never read the PHPDoc tag. Also honor the @phpstan- prefixed alias, strip the leading $ from the tag's parameter name, read the tag from stub PHPDocs, and annotate array_reduce's stub.
…acks are pure SimpleImpurePoint::createFromVariant() now evaluates the purity of arguments passed to flagged parameters: provably pure (or omitted) callbacks produce no impure point, provably impure ones make the point certain, unknown callables keep the current possibly-impure behavior. This fixes the false positive for array_map() with a pure callback in @phpstan-pure functions and enables the no-effect report for standalone statements. Closes phpstan/phpstan#11100 Closes phpstan/phpstan#11101
Named arguments are not supported by the PHP 7.4 downgrade, so pass the enterClassMethod() parameters positionally. The file-without-errors.php fixture contained a standalone array_filter() call that is now correctly reported as having no effect - wrap it in echo count().
…-callable arguments Kills the two escaped Infection mutants in SimpleImpurePoint: a (callable|null) callback exercises the isNull() branch and a (pure-callable|int) callback exercises the isCallable() branch - both keep the call possibly impure.
Address review nits: use null coalescing for the array<string, bool> parameter flags, a ternary for the hasSideEffects TrinaryLogic (keeping the absent -> Maybe branch), and split the negated method hasSideEffects read into named steps matching the ancestors loop below.
PHP 8.4's array_all() and array_any() are pure unless their callback is impure, like array_filter()/array_map().
…tions Generic builtins like array_find()/array_find_key() are reflected via BetterReflection (PhpFunctionReflection), whose getCustomFunction() only read the @pure-unless-callable-is-impure PHPDoc tag and ignored functionMetadata.php - unlike the native-signature path. Merge the metadata's pureUnlessCallableIsImpureParameters in there too (PHPDoc tag wins). This makes array_find() actually honor its existing metadata entry and lets array_find_key() be added.
RequiredPhpVersionCommentTest requires fixtures using version-specific features to declare a '// lint >= X.Y' comment so they are skipped on older PHP in CI.
Method support was entirely non-functional: PhpClassReflectionExtension only read the pure-unless map from built-in method metadata (never from a userland method's own PHPDoc tag), and PhpMethodReflection::getParameters() never passed the flag on to its PhpParameterReflection (the function path already did). Read the tag from the resolved PHPDoc and thread the flag through, and make the method-reflection wrappers (changed-type, closure-call, intersection, union, parser-node function) delegate getPureUnlessCallableIsImpureParameters() instead of returning [].
createNativeMethodVariant() (no built-in method carries the metadata) and Closure::call()'s parameters (a closure cannot carry the tag) leave the flag at its default; document why so a future maintainer knows it is deliberate, not an oversight.
0188553 to
c45ca89
Compare
Implements the
@pure-unless-callable-is-impurePHPDoc tag (parser support: phpstan/phpdoc-parser#253, merged): a parameter-level annotation declaring that the function/method is pure unless the callable passed to that parameter is impure. Rebuilt on top of2.2.x.How it works
@phpstan-prefixed alias) is resolved into per-parameter flags, threaded through the reflection layer (ExtendedParameterReflection::isPureUnlessCallableIsImpureParameter()), and populated for builtins viafunctionMetadata(array_filter,array_map,array_reduce, thearray_*u*comparator family,call_user_func(_array),forward_static_call(_array),preg_replace_callback(_array), …) and forarray_reducevia its stub.SimpleImpurePoint::createFromVariant()evaluates the purity of arguments passed to flagged parameters:null) → no impure point — the call is treated as pure;callabletype) → unchanged possibly-impure behavior.NoopExpressionNode) is driven by "the statement has zero impure points", standalonearray_map($pureCb, $arr);statements are now reported byCallToFunctionStatementWithoutSideEffectsRulewith no rule changes.PureFunctionRule/PureMethodRuleregressions,CallToFunctionStatementWithoutSideEffectsRulecases incl. impure/opaque/by-ref non-regressions)refs #3106, #5912
Closes phpstan/phpstan#11101
Closes phpstan/phpstan#11100