Skip to content

Conversation

@staabm
Copy link
Contributor

@staabm staabm commented Jan 5, 2026

@staabm
Copy link
Contributor Author

staabm commented Jan 5, 2026

@iluuu1994 could you have another look at this PR. intention is, that php-src will be able to cache the closure, while it could not before.

does this make sense like that and will it have the expected behaviour?

@staabm
Copy link
Contributor Author

staabm commented Jan 5, 2026

regarding other examples:

$classType = TypeTraverser::map($classType, static function (Type $type, callable $traverse) use (&$uncertainty): Type {
					if ($type instanceof UnionType || $type instanceof IntersectionType) {
						return $traverse($type);
					}
					if ($type->getObjectClassNames() !== []) {
						$uncertainty = true;
						return $type;
					}
					if ($type instanceof GenericClassStringType) {
						$uncertainty = true;
						return $type->getGenericType();
					}
					if ($type instanceof ConstantStringType) {
						return new ObjectType($type->getValue());
					}
					return new MixedType();
				});

do you think it would make things faster if we transform such closures into e.g. __invoke classes or similar?

Some callsites of TypeTraverser::map are easily invoked multi-million times in hot paths

@dktapps
Copy link
Contributor

dktapps commented Jan 5, 2026

Basically the requirement for being cached is that it has to be stateless. So:

  • no $this
  • no used variables
  • no local static variables

In some cases it might be worth considering if a plain old function makes more sense

Copy link
Contributor

@dktapps dktapps left a comment

Choose a reason for hiding this comment

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

PR changes look good to me wrt. cacheability, although it might be worth considering if a private static function would make more sense than a closure for larger functions

@staabm
Copy link
Contributor Author

staabm commented Jan 5, 2026

@dktapps thank you

Basically the requirement for being cached is that it has to be stateless

can this be measured somehow?

@staabm staabm marked this pull request as ready for review January 5, 2026 21:03
@phpstan-bot
Copy link
Collaborator

This pull request has been marked as ready for review.

Copy link
Contributor

@iluuu1994 iluuu1994 left a comment

Choose a reason for hiding this comment

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

do you think it would make things faster if we transform such closures into e.g. __invoke classes or similar?

The static closure patch avoids an allocation each time the closure is used (not called). Allocating an object rather than a closure shouldn't make a significant difference, unless you can avoid allocating the object each time. For closures that have usees, that might be easier to achieve with __invokeable objects with properties I suppose.

$stdOutput = new SymfonyOutput($output, new SymfonyStyle(new ErrorsConsoleStyle($input, $output)));

$errorOutput = (static function () use ($input, $output): Output {
$errorOutput = (static function ($input, $output): Output {
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the purpose of this static closure in the first place? The other case has an include (so possibly there to hide local vars?) and does some control flow. Why not just drop the closure entirely?

Copy link
Contributor Author

@staabm staabm Jan 6, 2026

Choose a reason for hiding this comment

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

good point. removed this closure.

thank you.

@dktapps
Copy link
Contributor

dktapps commented Jan 5, 2026

@dktapps thank you

Basically the requirement for being cached is that it has to be stateless

can this be measured somehow?

Not sure what you mean by this, but for an explicitly static closure it just can't have used vars or local static vars.

The PR you mentioned on php-src is mostly focused on inferring static when it's not explicitly specified.
Inferring static won't guarantee cacheability, because a static closure could still use local scope vars or have local static variables.
But inferring static does potentially make it possible to cache closures which aren't explicitly marked with static, if they by chance have no uses and no local static vars (a fairly common case seen a lot with array_map callbacks and such).

@staabm
Copy link
Contributor Author

staabm commented Jan 6, 2026

The static closure patch avoids an allocation each time the closure is used (not called).

does this means, after making a closure cachable we might see a runtime improvement or memory_get_peak improvement?
or is this such a minor thing that changing code to be a cachable closure will kindly be noticable in perf metrics?
(making this more efficient is my primary motivation for such changes atm)

path: src/Analyser/RuleErrorTransformer.php

-
rawMessage: Doing instanceof PHPStan\Type\IntersectionType is error-prone and deprecated.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@ondrejmirtes I wonder why this errors show up now, but it seems they were not reported when 1:1 the same code was part of MutatingScope class. I could not find ignore-error rules or something which would swallow them either.


besides that, I think this PR is ready to merge.
I would do more *Traverser-extractions in separate PRs, as we have too many of them.

@staabm staabm mentioned this pull request Jan 6, 2026
@dktapps
Copy link
Contributor

dktapps commented Jan 6, 2026

The static closure patch avoids an allocation each time the closure is used (not called).

does this means, after making a closure cachable we might see a runtime improvement or memory_get_peak improvement? or is this such a minor thing that changing code to be a cachable closure will kindly be noticable in perf metrics? (making this more efficient is my primary motivation for such changes atm)

It depends on how hot the code is, you might see a performance improvement. I wouldn't expect to see much difference in memory usage

@iluuu1994
Copy link
Contributor

does this means, after making a closure cachable we might see a runtime improvement or memory_get_peak improvement?

That's the idea. It should be beneficial for code that creates closures in a loop. Of course, you could already avoid this performance hit by moving the closure out of the loop, but the idea here is of course is that it happens automatically.

Regarding __invoke: Make sure to benchmark. Performance can be unpredictable.

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.

4 participants