Skip to content

Compute the verbosity level of an array shape key only when reporting an error - #6651

Closed
Kocal wants to merge 2 commits into
phpstan:2.3.xfrom
Kocal:perf/constant-array-lazy-offset-verbosity
Closed

Kocal wants to merge 2 commits into
phpstan:2.3.xfrom
Kocal:perf/constant-array-lazy-offset-verbosity

Conversation

@Kocal

@Kocal Kocal commented Oct 1, 2026

Copy link
Copy Markdown

ConstantArrayType::checkOurKeys() (used by ConstantArrayType::accepts()) calls VerbosityLevel::getRecommendedLevelByType($valueType, $otherValueType) for every key it checks. That call walks both value types completely, but its result is only used to describe the two types in the error message of a key whose value is rejected. For every accepted key the walk is wasted, and when a key's value is itself an array shape, the walk covers that whole nested shape too.

This PR moves the computation so it only runs when a message is actually built. In PHP it becomes a closure called from the two places that build the message. The C++ twin in turbo-ext (turbo-ext/src/ConstantArrayType.cpp) gets the same treatment, with the call moved into offsetReason(). Error messages are unchanged.

I added a phpbench scenario, tests/bench/data/nested-array-shape-accepts.php, with a 150-key shape where 40 keys refer to a nested 150-key shape, passed to a parameter of the same shape.

Results (wall time, 3 alternating runs after a discarded warm-up):

Benchmark Before After
phpbench nested-array-shape-accepts.php 231.2-231.7 ms 207.6-208.8 ms
reproducer app, phpstan-nested.neon, without turbo extension 23.03-23.25 s 18.80-19.09 s
same app, with turbo extension loaded 8.16-8.24 s 7.12-7.15 s

The reproducer is a small Symfony app that analyses, at level 6, one method taking the symfony/ux-css CssStyles shape (847 keys, 144 of them referring to a nested copy of the same shape) and passing it to one css() call. Same errors before and after (none).

The rest of the phpbench suite stays within its thresholds, the largest slowdown being +1.3% (bug-14869.php, 259 ms vs 256 ms). tests/PHPStan/Rules and tests/PHPStan/Type pass with and without the turbo extension loaded, and turbo-ext/tests/smoke.php plus turbo-ext/tests/signature-parity.php pass too.

This is independent from #6643 and #6645, which fix other redundant walks over the same kind of shape.

XDEBUG_MODE=off tests/vendor/bin/phpbench run --variant=nested-array-shape-accepts.php --report=aggregate

Kocal added 2 commits October 1, 2026 13:43
… an error

`ConstantArrayType::checkOurKeys()` (used by `ConstantArrayType::accepts()`) calls `VerbosityLevel::getRecommendedLevelByType($valueType, $otherValueType)` for every key it checks. That call walks both value types completely, but its result is only used to describe the two types in the error message of a key whose value is rejected. For every accepted key the walk is wasted, and when a key's value is itself an array shape, the walk covers that whole nested shape too.

This PR moves the computation so it only runs when a message is actually built. In PHP it becomes a closure called from the two places that build the message. The C++ twin in turbo-ext (`turbo-ext/src/ConstantArrayType.cpp`) gets the same treatment, with the call moved into `offsetReason()`. Error messages are unchanged.

I added a phpbench scenario, `tests/bench/data/nested-array-shape-accepts.php`, with a 150-key shape where 40 keys refer to a nested 150-key shape, passed to a parameter of the same shape.

Results (wall time, 3 alternating runs after a discarded warm-up):

| Benchmark | Before | After |
|---|---|---|
| phpbench `nested-array-shape-accepts.php` | 231.2-231.7 ms | 207.6-208.8 ms |
| [reproducer](https://github.com/Kocal/sf-ux-css-phpstan-reproducer) app, `phpstan-nested.neon`, without turbo extension | 23.03-23.25 s | 18.80-19.09 s |
| same app, with turbo extension loaded | 8.16-8.24 s | 7.12-7.15 s |

The reproducer is a small Symfony app that analyses, at level 6, one method taking the symfony/ux-css `CssStyles` shape (847 keys, 144 of them referring to a nested copy of the same shape) and passing it to one `css()` call. Same errors before and after (none).

The rest of the phpbench suite stays within its thresholds, the largest slowdown being +1.3% (bug-14869.php, 259 ms vs 256 ms). `tests/PHPStan/Rules` and `tests/PHPStan/Type` pass with and without the turbo extension loaded, and `turbo-ext/tests/smoke.php` plus `turbo-ext/tests/signature-parity.php` pass too.

This is independent from phpstan#6643 and phpstan#6645, which fix other redundant walks over the same kind of shape.

```
XDEBUG_MODE=off tests/vendor/bin/phpbench run --variant=nested-array-shape-accepts.php --report=aggregate
```
@Kocal

Kocal commented Oct 1, 2026

Copy link
Copy Markdown
Author

#6649 seems to be a better version

@Kocal Kocal closed this Oct 1, 2026
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.

1 participant