Fix phpstan/phpstan#14319: PHPStan takes extremely long analyzing if-offset checks on array|object#5242
Conversation
|
before this PR with this PR |
array|object
|
I confirm that this fixes the issue. When using the new phar the analyse run completes in reasonable time. |
204ad22 to
718cca9
Compare
…im fetch narrowing - Added ARRAY_DIM_FETCH_UNION_TYPE_LIMIT (8) to MutatingScope to cap parent type narrowing - When array dim fetch narrows a parent variable type that is already a UnionType with more than 8 members, skip propagating HasOffsetValueType intersections to the parent - This prevents 2^N exponential growth when N consecutive if-blocks check different array offsets on an array|object typed variable - New regression test in tests/PHPStan/Analyser/nsrt/bug-14319.php - New benchmark test in tests/bench/data/bug-14319.php
Instead of counting all union type members, only count union members that contain HasOffsetValueType accessories. This more precisely targets the source of exponential growth while avoiding false positives on unions that are large for other reasons. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
0cda4c2 to
87f29af
Compare
src/Analyser/MutatingScope.php
Outdated
| $exprVarType = $scope->getType($expr->var); | ||
| $isArray = $exprVarType->isArray(); | ||
| if (!$exprVarType instanceof MixedType && !$isArray->no()) { | ||
| $tooComplex = false; |
There was a problem hiding this comment.
The var name si too vague.
There was a problem hiding this comment.
Done. Renamed $tooComplex to $tooManyHasOffsetValueTypes to better describe what the variable tracks — whether the union type has exceeded the limit of HasOffsetValueType accessory types. All tests pass (11602 tests) and static analysis reports no errors.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
The branch is clean and up to date. The PR is approved with no outstanding change requests. There's nothing to address — all previous feedback has been implemented and the reviewer has approved the PR. |
|
The review from VincentLanglet is an APPROVED review with no new comments or change requests. All previous feedback has already been addressed:
There's nothing new to address — the PR is approved and ready to merge. |
Summary
PHPStan hangs or takes extremely long when analyzing code with multiple consecutive
if ($rows['key'])blocks where$rowshas typearray|object. The analysis time grows exponentially with the number of if-blocks (6 blocks → 12+ seconds, effectively hanging for real-world code with more blocks).Changes
ARRAY_DIM_FETCH_UNION_TYPE_LIMIT = 8constant tosrc/Analyser/MutatingScope.phpspecifyExpressionType(), when processingArrayDimFetchexpressions, skip propagatingHasOffsetValueTypeintersections andgeneralOffsetAccessibleTypeintersections to the parent variable when the parent type is already aUnionTypewith more than 8 memberstests/PHPStan/Analyser/nsrt/bug-14319.phpwith assertType checkstests/bench/data/bug-14319.phpfor performance regression trackingRoot cause
When processing
if ($rows['key'])where$rowsisarray|object,specifyExpressionTypenarrows the parent variable$rowsby intersecting it withgeneralOffsetAccessibleType(convertingobjecttoArrayAccess) andHasOffsetValueType('key', <truthy/falsy type>). This creates an intersection type for each union member.After merging the truthy and falsy branches of each if-block, the type of
$rowsbecomes a union of both branch types, doubling the number of union members. With N consecutive if-blocks checking different keys, the type grows to O(2^N) union members. Each subsequentTypeCombinator::intersectcall on this growing union becomes exponentially slower.The fix introduces a complexity limit: when the parent variable's type already has more than 8 union members, the narrowing propagation to the parent is skipped. The dim fetch expression itself (
$rows['key']) is still narrowed correctly, so analysis precision is maintained for the accessed value. Performance improves from 12+ seconds to ~1 second for the reproduction case.Test
The regression test
bug-14319.phpreproduces the exact scenario from the issue: a function returningarray|object, followed by 6 consecutiveif ($rows['key'])blocks with.=concatenation. It includesassertTypechecks to verify that type inference still works correctly (variable types are properly inferred asstringafter the operations).Fixes phpstan/phpstan#14319