Conversation
At levels 9 and 10, `RuleLevelHelper::transformCommonType()` makes the mixed types of a parameter type strict before each `accepts()` check, for both the accepting type and the accepted type. A type alias that many keys of an array shape refer to is one shared `Type` object, but the walk transformed it again from every key that referenced it and built a separate copy for each one, so the result ended up as large as the fully expanded shape, rebuilt for every argument checked. The callback only depends on the type it receives, so each object is now transformed once per walk, and later visits reuse that same result object, which keeps the transformed type's sharing intact. Objects are keyed with `spl_object_id()` and kept in a cache for the duration of the walk, so an id can't be reused while the walk is in progress. This is independent from phpstan#6643, which does the same thing for `MissingTypehintCheck`. Wall time below, 3 runs after a discarded warm-up. The first two rows use [sf-ux-css-phpstan-reproducer](https://github.com/Kocal/sf-ux-css-phpstan-reproducer), a small Symfony app whose `phpstan-nested.neon` analyses one method taking the symfony/ux-css `CssStyles` shape (847 keys, 144 of them referring to the nested copy) and passes it to one `css()` call. That config defaults to level 6, where this change does nothing, so the numbers come from `vendor/bin/phpstan analyse -c phpstan-nested.neon -l 9`, with the same errors reported before and after (none). | Case | Before | After | | --- | --- | --- | | Reproducer, level 9, no turbo | 29.84-29.96 s, 429-445 MB | 23.12-23.31 s, 95 MB | | Reproducer, level 9, turbo extension loaded | 8.82-8.85 s | 8.05-8.07 s | | Script below (300 keys, 120 nested), 10 calls, level 9, no turbo | 1.83-1.88 s | 1.70-1.72 s | As a regression check, analysing PHPStan's own `src/Rules` at level 9 in a single process (`--debug`) over 3 alternating rounds stayed at 26.19-26.71 s CPU before and 26.15-26.24 s after, with the same errors reported, and the `tests/PHPStan/Rules` suite passes. There's no phpbench scenario for this because the regression benchmark runs with the default configuration, where `checkExplicitMixed` is off and this walk never runs. <details> <summary>Reproduction script</summary> Run with `php generate.php > args.php && bin/phpstan analyse -l 9 args.php`. ```php <?php $keys = static fn (int $count, string $prefix, string $type): string => implode(', ', array_map( static fn (int $i): string => "{$prefix}{$i}?: {$type}", range(0, $count - 1), )); $calls = ''; for ($i = 0; $i < 10; $i++) { $calls .= sprintf("\techo \$styles->css(['p%d' => 'a', 'c%d' => ['p%d' => 'b']]);\n", $i, $i, $i + 1); } printf(<<<'PHP' <?php /** * @phpstan-type Value = 'a'|'b'|'c'|'d'|string * @phpstan-type Inner = array{%s, extra?: array<string, mixed>} * @phpstan-type Outer = array{%s, %s} */ final class Styles { /** @PARAM Outer|null $styles */ public function css(?array $styles): string { return ''; } } function render(Styles $styles): void { %s} PHP, $keys(300, 'p', 'Value'), $keys(300, 'p', 'Value'), $keys(120, 'c', 'Inner'), $calls); ``` </details>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
At levels 9 and 10,
RuleLevelHelper::transformCommonType()makes the mixed types of a parameter type strict before eachaccepts()check, for both the accepting type and the accepted type. A type alias that many keys of an array shape refer to is one sharedTypeobject, but the walk transformed it again from every key that referenced it and built a separate copy for each one, so the result ended up as large as the fully expanded shape, rebuilt for every argument checked.The callback only depends on the type it receives, so each object is now transformed once per walk, and later visits reuse that same result object, which keeps the transformed type's sharing intact. Objects are keyed with
spl_object_id()and kept in a cache for the duration of the walk, so an id can't be reused while the walk is in progress. This is independent from #6643, which does the same thing forMissingTypehintCheck.Wall time below, 3 runs after a discarded warm-up. The first two rows use sf-ux-css-phpstan-reproducer, a small Symfony app whose
phpstan-nested.neonanalyses one method taking the symfony/ux-cssCssStylesshape (847 keys, 144 of them referring to the nested copy) and passes it to onecss()call. That config defaults to level 6, where this change does nothing, so the numbers come fromvendor/bin/phpstan analyse -c phpstan-nested.neon -l 9, with the same errors reported before and after (none).As a regression check, analysing PHPStan's own
src/Rulesat level 9 in a single process (--debug) over 3 alternating rounds stayed at 26.19-26.71 s CPU before and 26.15-26.24 s after, with the same errors reported, and thetests/PHPStan/Rulessuite passes.There's no phpbench scenario for this because the regression benchmark runs with the default configuration, where
checkExplicitMixedis off and this walk never runs.Reproduction script
Run with
php generate.php > args.php && bin/phpstan analyse -l 9 args.php.