Skip to content

Check original type for unresolvable types only when resolved type has one - #6675

Merged
staabm merged 1 commit into
phpstan:2.3.xfrom
Kocal:function-call-parameters-check-skip-original-unresolvable
Oct 6, 2026
Merged

staabm merged 1 commit into
phpstan:2.3.xfrom
Kocal:function-call-parameters-check-skip-original-unresolvable

Conversation

@Kocal

@Kocal Kocal commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

In FunctionCallParametersCheck::check(), each argument's resolved parameter type and original parameter type were both passed to UnresolvableTypeHelper::getUnresolvableType(), and only afterwards did the code check whether the resolved type actually had an unresolvable type. The original type is only reported as an error when the resolved type has an unresolvable type and the original one does not, so walking the original type was wasted work whenever the resolved type was fine. This reorders the check to walk the resolved type first and only then the original type, with the same reordering applied to the return type check a few lines below. Both calls are pure, so the reported errors are unchanged.

getUnresolvableType() walks the whole type, which gets expensive on large array shapes. The CssStyles shape from symfony/ux-css has 847 keys, 144 of them a nested shape alias; every call site using it as a parameter type paid for two full walks of that shape, and the second one was almost never needed. TwigStan, which compiles templates to PHP, would produce such calls for every css() in a template.

Benchmark: 50 calls to CssRuntime::css() with literal arrays (3 to 8 keys, up to two nested conditions like _hover or md) spread over 10 classes, analysed against the reproducer's nested shape (sf-ux-css-phpstan-reproducer, phpstan-nested.neon), --debug, interleaved runs:

Scenario Before After
2.3.x, no turbo 1 min 12 s 48.5-49.1 s
2.3.x, turbo 21.99-22.16 s 14.43-14.54 s
2.3.x + #6652 + #6671 + #6672 + #6673, no turbo 2.05-2.11 s 1.74-1.75 s

make tests passes with and without the turbo extension, make phpstan and make cs pass.

Refs phpstan/phpstan#15348

…s one

In `FunctionCallParametersCheck::check()`, each argument's resolved parameter type and original parameter type were both passed to `UnresolvableTypeHelper::getUnresolvableType()`, and only afterwards did the code check whether the resolved type actually had an unresolvable type. The original type is only reported as an error when the resolved type has an unresolvable type and the original one does not, so walking the original type was wasted work whenever the resolved type was fine. This reorders the check to walk the resolved type first and only then the original type, with the same reordering applied to the return type check a few lines below. Both calls are pure, so the reported errors are unchanged.

`getUnresolvableType()` walks the whole type, which gets expensive on large array shapes. The `CssStyles` shape from symfony/ux-css has 847 keys, 144 of them a nested shape alias; every call site using it as a parameter type paid for two full walks of that shape, and the second one was almost never needed. TwigStan, which compiles templates to PHP, would produce such calls for every `css()` in a template.

Benchmark: 50 calls to `CssRuntime::css()` with literal arrays (3 to 8 keys, up to two nested conditions like `_hover` or `md`) spread over 10 classes, analysed against the reproducer's nested shape ([sf-ux-css-phpstan-reproducer](https://github.com/Kocal/sf-ux-css-phpstan-reproducer), `phpstan-nested.neon`), `--debug`, interleaved runs:

| Scenario | Before | After |
|---|---|---|
| 2.3.x, no turbo | 1 min 12 s | 48.5-49.1 s |
| 2.3.x, turbo | 21.99-22.16 s | 14.43-14.54 s |
| 2.3.x + phpstan#6652 + phpstan#6671 + phpstan#6672 + phpstan#6673, no turbo | 2.05-2.11 s | 1.74-1.75 s |

`make tests` passes with and without the turbo extension, `make phpstan` and `make cs` pass.

Refs phpstan/phpstan#15348
@Kocal
Kocal force-pushed the function-call-parameters-check-skip-original-unresolvable branch from fd101fc to 7a512e0 Compare October 6, 2026 07:55
@staabm

staabm commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

did you modify the reproducer of sf-ux-css-phpstan-reproducer to get this measurable?
I tried reproducing how this makes it faster, but I was not able to reproduce though.

I would love to reproduce it.


will merge now, as the PR itself is a obvious no-brainer.

@staabm
staabm merged commit f9a9159 into phpstan:2.3.x Oct 6, 2026
909 of 923 checks passed
@staabm

staabm commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

thank you!

@Kocal
Kocal deleted the function-call-parameters-check-skip-original-unresolvable branch October 6, 2026 14:29
@Kocal

Kocal commented Oct 6, 2026

Copy link
Copy Markdown
Contributor Author

No I didn't modify the reproducer (yet), I believe Claude ran PHPStan on the config/reference_css_nested.php, or on a file created on-the-fly which calls the css() function.

I will tell it to commit the files used for benchmarks :)

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.

2 participants