Skip to content

Implement @pure-unless-callable-is-impure#3482

Open
zonuexe wants to merge 9 commits into
phpstan:2.2.xfrom
zonuexe:feature/pure-unless-callable-is-impure
Open

Implement @pure-unless-callable-is-impure#3482
zonuexe wants to merge 9 commits into
phpstan:2.2.xfrom
zonuexe:feature/pure-unless-callable-is-impure

Conversation

@zonuexe

@zonuexe zonuexe commented Sep 25, 2024

Copy link
Copy Markdown
Contributor

Implements the @pure-unless-callable-is-impure PHPDoc 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 of 2.2.x.

How it works

  • The tag (and its @phpstan- prefixed alias) is resolved into per-parameter flags, threaded through the reflection layer (ExtendedParameterReflection::isPureUnlessCallableIsImpureParameter()), and populated for builtins via functionMetadata (array_filter, array_map, array_reduce, the array_*u* comparator family, call_user_func(_array), forward_static_call(_array), preg_replace_callback(_array), …) and for array_reduce via its stub.
  • SimpleImpurePoint::createFromVariant() evaluates the purity of arguments passed to flagged parameters:
    • provably pure callback (or omitted / null) → no impure point — the call is treated as pure;
    • provably impure callback → the impure point becomes certain;
    • unknown callable (opaque callable type) → unchanged possibly-impure behavior.
  • Since the no-effect detection (NoopExpressionNode) is driven by "the statement has zero impure points", standalone array_map($pureCb, $arr); statements are now reported by CallToFunctionStatementWithoutSideEffectsRule with no rule changes.
/** @phpstan-pure */
function doubled(array $arr): array
{
    return array_map(static fn (int $x): int => $x * 2, $arr); // no longer "Possibly impure"
}

array_map('is_string', $arr); // now: "Call to function array_map() on a separate line has no effect."
array_map(static function ($x) { echo $x; }, $arr); // still allowed as a statement (impure callback)
  • Add function map
  • Add tests (fail-first: PureFunctionRule/PureMethodRule regressions, CallToFunctionStatementWithoutSideEffectsRule cases incl. impure/opaque/by-ref non-regressions)
  • Add rules (no new rule needed — the impure-point suppression feeds both the pure-context check and the existing no-effect rule)

refs #3106, #5912

Closes phpstan/phpstan#11101
Closes phpstan/phpstan#11100

@zonuexe zonuexe marked this pull request as draft September 25, 2024 17:46
Comment thread src/PhpDoc/PhpDocNodeResolver.php Outdated

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Will be replaced by phpstan/phpdoc-parser#253

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

WIP. = false will be removed.

@ondrejmirtes

Copy link
Copy Markdown
Member

Just updated phpdoc-parser here so you can use that.

@zonuexe zonuexe force-pushed the feature/pure-unless-callable-is-impure branch 4 times, most recently from eddd22a to f207c72 Compare September 30, 2024 18:09
@zonuexe zonuexe force-pushed the feature/pure-unless-callable-is-impure branch from f207c72 to 8c756cc Compare November 3, 2024 17:57
@staabm

staabm commented Nov 18, 2024

Copy link
Copy Markdown
Contributor

@zonuexe any plans to move this forward?

@zonuexe

zonuexe commented Nov 19, 2024

Copy link
Copy Markdown
Contributor Author

@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.

@staabm

staabm commented Nov 19, 2024

Copy link
Copy Markdown
Contributor

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

@zonuexe zonuexe force-pushed the feature/pure-unless-callable-is-impure branch from 8c756cc to 389c7f0 Compare November 19, 2024 16:58
@zonuexe

zonuexe commented Nov 19, 2024

Copy link
Copy Markdown
Contributor Author

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.

@zonuexe zonuexe changed the base branch from 1.12.x to 2.0.x November 19, 2024 17:11
@zonuexe zonuexe force-pushed the feature/pure-unless-callable-is-impure branch 4 times, most recently from 2146ae0 to 1cc1fad Compare November 19, 2024 18:32
@VincentLanglet

Copy link
Copy Markdown
Contributor

Friendly ping @zonuexe, still interested in this PR ? Do you have time to finish it ?
Do you look for help ? What is missing ?

@zonuexe

zonuexe commented Jul 20, 2025

Copy link
Copy Markdown
Contributor Author

@VincentLanglet
Thank you for the suggestion, sorry for making you wait so long.
I've been working on PHPStan 2.1.18 a few times over the last few weeks, and just finished a PHPStan workshop at a local PHP conference, so I'll get back to that tomorrow.

@zonuexe zonuexe force-pushed the feature/pure-unless-callable-is-impure branch from adb02ca to abc8cd8 Compare August 1, 2025 12:45
@zonuexe zonuexe changed the base branch from 2.0.x to 2.1.x August 1, 2025 12:46
@zonuexe zonuexe force-pushed the feature/pure-unless-callable-is-impure branch from abc8cd8 to c4999be Compare August 1, 2025 13:09
@VincentLanglet

Copy link
Copy Markdown
Contributor

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.
What is missing ?

It will unblock the phpstan/phpdoc-parser#259 pr :)

@VincentLanglet

Copy link
Copy Markdown
Contributor

Maybe I'm wrong, but I start to think this annotation is useless
https://phpstan.org/r/d3af976b-17ef-4ca6-87cb-4a1325622cee

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.

@devbanana

Copy link
Copy Markdown
Contributor

i'm happy to help contribute towards a solution if it's decided which direction is best.

@VincentLanglet

Copy link
Copy Markdown
Contributor

Hi @devbanana feel free to work on this.

Laybe @zonuexe could explain what he had in mind ; or @ondrejmirtes will have some guidelines

@zonuexe zonuexe force-pushed the feature/pure-unless-callable-is-impure branch from e4e54cb to ce90339 Compare July 2, 2026 16:45
@zonuexe zonuexe changed the base branch from 2.1.x to 2.2.x July 2, 2026 16:46
@zonuexe zonuexe changed the title Implement @pure-unless-callable-impure Implement @pure-unless-callable-is-impure Jul 2, 2026
zonuexe added 8 commits July 3, 2026 02:19
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().
@zonuexe zonuexe marked this pull request as ready for review July 2, 2026 17:21
@phpstan-bot

Copy link
Copy Markdown
Collaborator

This pull request has been marked as ready for review.

@zonuexe

zonuexe commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

Sorry for keeping you all waiting so long. I have rebased onto the latest 2.2.x version and added the wiring that was missing from the previous changes.

…-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.
@zonuexe zonuexe force-pushed the feature/pure-unless-callable-is-impure branch from b512831 to ebfd937 Compare July 3, 2026 02:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

6 participants