From f116827f2cfb7f91262480c7d435034516034d02 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 4 May 2026 09:06:04 +0200 Subject: [PATCH 1/6] Try --- src/Analyser/NodeScopeResolver.php | 7 ++-- src/Analyser/TypeSpecifier.php | 57 ++++++++++-------------------- 2 files changed, 21 insertions(+), 43 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index cbed5105ae..0bad74200b 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -3917,7 +3917,7 @@ private function tryProcessUnrolledConstantArrayForeach( $matchedNativeArrays = count($nativeConstantArrays) === count($constantArrays) ? $nativeConstantArrays : null; $valueVarName = $stmt->valueVar->name; - $keyVarName = $stmt->keyVar instanceof Variable && is_string($stmt->keyVar->name) ? $stmt->keyVar->name : null; + $keyVarName = $stmt->keyVar instanceof Variable ? $stmt->keyVar->name : null; $allBodyScopes = []; $allChainScopes = []; @@ -4051,10 +4051,7 @@ private function enterForeach(MutatingScope $scope, ExpressionResultStorage $sto ($stmt->valueVar instanceof Variable && is_string($stmt->valueVar->name)) && ($stmt->keyVar === null || ($stmt->keyVar instanceof Variable && is_string($stmt->keyVar->name))) ) { - $keyVarName = null; - if ($stmt->keyVar instanceof Variable && is_string($stmt->keyVar->name)) { - $keyVarName = $stmt->keyVar->name; - } + $keyVarName = $stmt->keyVar instanceof Variable ? $stmt->keyVar->name : null; $scope = $scope->enterForeach( $originalScope, $stmt->expr, diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 9f7a86eb4a..f7ff4b0f2f 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -2083,10 +2083,7 @@ private function processBooleanSureConditionalTypes(Scope $scope, SpecifiedTypes { $conditionExpressionTypes = []; foreach ($leftTypes->getSureTypes() as $exprString => [$expr, $type]) { - if (!$expr instanceof Expr\Variable) { - continue; - } - if (!is_string($expr->name)) { + if (!$this->isTrackableExpression($expr)) { continue; } @@ -2105,10 +2102,7 @@ private function processBooleanSureConditionalTypes(Scope $scope, SpecifiedTypes if (count($conditionExpressionTypes) > 0) { $holders = []; foreach ($rightTypes->getSureTypes() as $exprString => [$expr, $type]) { - if (!$expr instanceof Expr\Variable) { - continue; - } - if (!is_string($expr->name)) { + if (!$this->isTrackableExpression($expr)) { continue; } @@ -2118,18 +2112,9 @@ private function processBooleanSureConditionalTypes(Scope $scope, SpecifiedTypes $conditions = $conditionExpressionTypes; foreach ($conditions as $conditionExprString => $conditionExprTypeHolder) { - $conditionExpr = $conditionExprTypeHolder->getExpr(); - if (!$conditionExpr instanceof Expr\Variable) { - continue; - } - if (!is_string($conditionExpr->name)) { - continue; + if ($conditionExprString === $exprString) { + unset($conditions[$conditionExprString]); } - if ($conditionExpr->name !== $expr->name) { - continue; - } - - unset($conditions[$conditionExprString]); } if (count($conditions) === 0) { @@ -2149,6 +2134,17 @@ private function processBooleanSureConditionalTypes(Scope $scope, SpecifiedTypes return []; } + private function isTrackableExpression(Expr $expr): bool + { + if ($expr instanceof Expr\Variable) { + return is_string($expr->name); + } + + return $expr instanceof Expr\PropertyFetch + || $expr instanceof Expr\ArrayDimFetch + || $expr instanceof Expr\StaticPropertyFetch; + } + /** * Flatten a deep BooleanOr chain into leaf expressions and process them * without recursive filterByFalseyValue calls. This reduces O(n^2) to O(n) @@ -2283,10 +2279,7 @@ private function processBooleanNotSureConditionalTypes(Scope $scope, SpecifiedTy { $conditionExpressionTypes = []; foreach ($leftTypes->getSureNotTypes() as $exprString => [$expr, $type]) { - if (!$expr instanceof Expr\Variable) { - continue; - } - if (!is_string($expr->name)) { + if (!$this->isTrackableExpression($expr)) { continue; } @@ -2299,10 +2292,7 @@ private function processBooleanNotSureConditionalTypes(Scope $scope, SpecifiedTy if (count($conditionExpressionTypes) > 0) { $holders = []; foreach ($rightTypes->getSureNotTypes() as $exprString => [$expr, $type]) { - if (!$expr instanceof Expr\Variable) { - continue; - } - if (!is_string($expr->name)) { + if (!$this->isTrackableExpression($expr)) { continue; } @@ -2312,18 +2302,9 @@ private function processBooleanNotSureConditionalTypes(Scope $scope, SpecifiedTy $conditions = $conditionExpressionTypes; foreach ($conditions as $conditionExprString => $conditionExprTypeHolder) { - $conditionExpr = $conditionExprTypeHolder->getExpr(); - if (!$conditionExpr instanceof Expr\Variable) { - continue; - } - if (!is_string($conditionExpr->name)) { - continue; + if ($conditionExprString === $exprString) { + unset($conditions[$conditionExprString]); } - if ($conditionExpr->name !== $expr->name) { - continue; - } - - unset($conditions[$conditionExprString]); } if (count($conditions) === 0) { From 0ba2ac32ed5408132b1580158a7e54b267832b1c Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 4 May 2026 09:24:39 +0200 Subject: [PATCH 2/6] Add tests --- src/Analyser/TypeSpecifier.php | 14 +++--- .../Constant/ConstantArrayTypeBuilder.php | 4 -- tests/PHPStan/Analyser/nsrt/bug-12517.php | 27 ++++++++++ .../Rules/Methods/CallMethodsRuleTest.php | 16 ++++++ .../Methods/CallStaticMethodsRuleTest.php | 6 +++ .../PHPStan/Rules/Methods/data/bug-13446.php | 50 +++++++++++++++++++ tests/PHPStan/Rules/Methods/data/bug-6486.php | 44 ++++++++++++++++ tests/PHPStan/Rules/Methods/data/bug-9155.php | 36 +++++++++++++ 8 files changed, 187 insertions(+), 10 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12517.php create mode 100644 tests/PHPStan/Rules/Methods/data/bug-13446.php create mode 100644 tests/PHPStan/Rules/Methods/data/bug-6486.php create mode 100644 tests/PHPStan/Rules/Methods/data/bug-9155.php diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index f7ff4b0f2f..d3bd29f429 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -2111,10 +2111,11 @@ private function processBooleanSureConditionalTypes(Scope $scope, SpecifiedTypes } $conditions = $conditionExpressionTypes; - foreach ($conditions as $conditionExprString => $conditionExprTypeHolder) { - if ($conditionExprString === $exprString) { - unset($conditions[$conditionExprString]); + foreach (array_keys($conditions) as $conditionExprString) { + if ($conditionExprString !== $exprString) { + continue; } + unset($conditions[$conditionExprString]); } if (count($conditions) === 0) { @@ -2301,10 +2302,11 @@ private function processBooleanNotSureConditionalTypes(Scope $scope, SpecifiedTy } $conditions = $conditionExpressionTypes; - foreach ($conditions as $conditionExprString => $conditionExprTypeHolder) { - if ($conditionExprString === $exprString) { - unset($conditions[$conditionExprString]); + foreach (array_keys($conditions) as $conditionExprString) { + if ($conditionExprString !== $exprString) { + continue; } + unset($conditions[$conditionExprString]); } if (count($conditions) === 0) { diff --git a/src/Type/Constant/ConstantArrayTypeBuilder.php b/src/Type/Constant/ConstantArrayTypeBuilder.php index 894fbfc4f1..cd7f5aa026 100644 --- a/src/Type/Constant/ConstantArrayTypeBuilder.php +++ b/src/Type/Constant/ConstantArrayTypeBuilder.php @@ -120,10 +120,6 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $opt } if ($offsetType === null) { - if (count($this->nextAutoIndexes) === 0) { - return; - } - $newAutoIndexes = $optional ? $this->nextAutoIndexes : []; $hasOptional = false; foreach ($this->keyTypes as $i => $keyType) { diff --git a/tests/PHPStan/Analyser/nsrt/bug-12517.php b/tests/PHPStan/Analyser/nsrt/bug-12517.php new file mode 100644 index 0000000000..449784b132 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12517.php @@ -0,0 +1,27 @@ +a !== null || $foo->b !== null) { + if ($foo->a === null) { + assertType('null', $foo->a); + assertType('mixed~null', $foo->b); + } + } + + $a = $foo->a; + $b = $foo->b; + if ($a !== null || $b !== null) { + if ($a === null) { + assertType('null', $a); + assertType('mixed~null', $b); + } + } + } +} diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 9a2a8e1a9f..8fa6fe62e0 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -4009,6 +4009,22 @@ public function testBug10422(): void $this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-10422.php'], []); } + public function testBug13446(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-13446.php'], []); + } + + public function testBug9155(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-9155.php'], []); + } + public function testBug13272(): void { $this->checkThisOnly = false; diff --git a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php index a931ed6379..85937c21b0 100644 --- a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php @@ -975,6 +975,12 @@ public function testBug12558(): void ]); } + public function testBug6486(): void + { + $this->checkThisOnly = false; + $this->analyse([__DIR__ . '/data/bug-6486.php'], []); + } + #[RequiresPhp('>= 8.5.0')] public function testPipeOperator(): void { diff --git a/tests/PHPStan/Rules/Methods/data/bug-13446.php b/tests/PHPStan/Rules/Methods/data/bug-13446.php new file mode 100644 index 0000000000..5988c2239d --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-13446.php @@ -0,0 +1,50 @@ +mainCarriage === null && $this->destinationLocals === null) { + return new MoneyVO(0.0, 'EUR'); + } + + if ($this->mainCarriage === null && $this->destinationLocals !== null) { + return $this->destinationLocals; + } + + if ($this->mainCarriage !== null && $this->destinationLocals === null) { + return $this->mainCarriage; + } + + return $this->mainCarriage->add($this->destinationLocals); + } +} + +final readonly class MoneyVO +{ + public function __construct( + public float $value, + public string $currency, + ) { + } + + public function add(self $money): self + { + return new self($this->getRoundedValue() + $money->getRoundedValue(), $this->currency); + } + + public function getRoundedValue(): float + { + return round($this->value, 2); + } +} diff --git a/tests/PHPStan/Rules/Methods/data/bug-6486.php b/tests/PHPStan/Rules/Methods/data/bug-6486.php new file mode 100644 index 0000000000..99f0819d1b --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-6486.php @@ -0,0 +1,44 @@ +only && !$this->exclude) { + return true; + } + + if ($this->only) { + return Preg::isMatch($this->only, $name); + } + + return !Preg::isMatch($this->exclude, $name); + } +} + +class Preg { + /** + * @param non-empty-string $pattern + * @param string $subject + * @param array $matches Set by method + * @param int $flags PREG_UNMATCHED_AS_NULL, only available on PHP 7.2+ + * @param int $offset + * @return bool + */ + public static function isMatch($pattern, $subject, &$matches = null, $flags = 0, $offset = 0) + { + return true; + } +} diff --git a/tests/PHPStan/Rules/Methods/data/bug-9155.php b/tests/PHPStan/Rules/Methods/data/bug-9155.php new file mode 100644 index 0000000000..1b502f5109 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-9155.php @@ -0,0 +1,36 @@ +foo && null === $this->bar) { + return; + } + + if (null === $this->foo && !$this->bar->barF()) { + echo 1; + } + } +} From e956c08a53e3898e3ea566719d0ac05caf6db052 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 4 May 2026 09:31:11 +0200 Subject: [PATCH 3/6] Add tests --- tests/PHPStan/Analyser/nsrt/bug-12517.php | 51 ++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/nsrt/bug-12517.php b/tests/PHPStan/Analyser/nsrt/bug-12517.php index 449784b132..d3e03e8b93 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-12517.php +++ b/tests/PHPStan/Analyser/nsrt/bug-12517.php @@ -6,7 +6,7 @@ class HelloWorld { - public function sayHello(stdClass $foo): void + public function sayHello(\stdClass $foo): void { if ($foo->a !== null || $foo->b !== null) { if ($foo->a === null) { @@ -25,3 +25,52 @@ public function sayHello(stdClass $foo): void } } } + +class Test +{ + /** @var mixed */ + public static $a = null; + /** @var mixed */ + public static $b = null; + + public function sayHello(): void + { + if (Test::$a !== null || Test::$b !== null) { + if (Test::$a === null) { + assertType('null', Test::$a); + assertType('mixed~null', Test::$b); + } + } + + $a = Test::$a; + $b = Test::$b; + if ($a !== null || $b !== null) { + if ($a === null) { + assertType('null', $a); + assertType('mixed~null', $b); + } + } + } +} + +class WithArray +{ + public function sayHello(array $array): void + { + if ($array['a'] !== null || $array['b'] !== null) { + if ($array['a'] === null) { + assertType('null', $array['a']); + assertType('mixed~null', $array['b']); + } + } + + $a = $array['a']; + $b = $array['b']; + if ($a !== null || $b !== null) { + if ($a === null) { + assertType('null', $a); + assertType('mixed~null', $b); + } + } + } +} From 6b65e4451f693e65b0859d7030e5de002ecd5b9a Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 4 May 2026 10:05:15 +0200 Subject: [PATCH 4/6] Remove regression test --- src/Analyser/TypeSpecifier.php | 1 + .../Rules/Methods/CallMethodsRuleTest.php | 8 --- .../PHPStan/Rules/Methods/data/bug-13446.php | 50 ------------------- 3 files changed, 1 insertion(+), 58 deletions(-) delete mode 100644 tests/PHPStan/Rules/Methods/data/bug-13446.php diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index d3bd29f429..9fe1e58872 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -83,6 +83,7 @@ use PHPStan\Type\UnionType; use function array_key_exists; use function array_key_first; +use function array_keys; use function array_last; use function array_map; use function array_merge; diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 8fa6fe62e0..8b4e9fd5bc 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -4009,14 +4009,6 @@ public function testBug10422(): void $this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-10422.php'], []); } - public function testBug13446(): void - { - $this->checkThisOnly = false; - $this->checkNullables = true; - $this->checkUnionTypes = true; - $this->analyse([__DIR__ . '/data/bug-13446.php'], []); - } - public function testBug9155(): void { $this->checkThisOnly = false; diff --git a/tests/PHPStan/Rules/Methods/data/bug-13446.php b/tests/PHPStan/Rules/Methods/data/bug-13446.php deleted file mode 100644 index 5988c2239d..0000000000 --- a/tests/PHPStan/Rules/Methods/data/bug-13446.php +++ /dev/null @@ -1,50 +0,0 @@ -mainCarriage === null && $this->destinationLocals === null) { - return new MoneyVO(0.0, 'EUR'); - } - - if ($this->mainCarriage === null && $this->destinationLocals !== null) { - return $this->destinationLocals; - } - - if ($this->mainCarriage !== null && $this->destinationLocals === null) { - return $this->mainCarriage; - } - - return $this->mainCarriage->add($this->destinationLocals); - } -} - -final readonly class MoneyVO -{ - public function __construct( - public float $value, - public string $currency, - ) { - } - - public function add(self $money): self - { - return new self($this->getRoundedValue() + $money->getRoundedValue(), $this->currency); - } - - public function getRoundedValue(): float - { - return round($this->value, 2); - } -} From 5803d25bfb2442af317a28a819e65ea4e22feca2 Mon Sep 17 00:00:00 2001 From: phpstan-bot Date: Mon, 4 May 2026 09:22:39 +0000 Subject: [PATCH 5/6] Use right-side scope only for non-Variable expressions in conditional holders For property fetches and array dim fetches, the target type in conditional expression holders must be computed from the right-side scope where the base object is already narrowed. For example, `$node->name` needs `$node` narrowed to `FuncCall` (not `CallLike`) to resolve the property type correctly. Variables keep using the original scope to avoid breaking multi-var isset() where the right scope has already removed null from the other variable. Also removes a redundant `!($expr->name instanceof Name)` check that PHPStan correctly identified as always-true after the preceding elseif branch. Co-Authored-By: Claude Opus 4.6 --- src/Analyser/TypeSpecifier.php | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 9fe1e58872..972c5cd7d3 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -597,7 +597,7 @@ public function specifyTypesInCondition( } return $this->handleDefaultTruthyOrFalseyContext($context, $expr, $scope); - } elseif ($expr instanceof FuncCall && !($expr->name instanceof Name)) { + } elseif ($expr instanceof FuncCall) { $specifiedTypes = $this->specifyTypesFromCallableCall($context, $expr, $scope); if ($specifiedTypes !== null) { return $specifiedTypes; @@ -755,10 +755,10 @@ public function specifyTypesInCondition( $result = $result->setAlwaysOverwriteTypes(); } return $result->setNewConditionalExpressionHolders(array_merge( - $this->processBooleanNotSureConditionalTypes($scope, $leftTypesForHolders, $rightTypesForHolders), - $this->processBooleanNotSureConditionalTypes($scope, $rightTypesForHolders, $leftTypesForHolders), - $this->processBooleanSureConditionalTypes($scope, $leftTypesForHolders, $rightTypesForHolders), - $this->processBooleanSureConditionalTypes($scope, $rightTypesForHolders, $leftTypesForHolders), + $this->processBooleanNotSureConditionalTypes($scope, $leftTypesForHolders, $rightTypesForHolders, $rightScope), + $this->processBooleanNotSureConditionalTypes($scope, $rightTypesForHolders, $leftTypesForHolders, $scope), + $this->processBooleanSureConditionalTypes($scope, $leftTypesForHolders, $rightTypesForHolders, $rightScope), + $this->processBooleanSureConditionalTypes($scope, $rightTypesForHolders, $leftTypesForHolders, $scope), ))->setRootExpr($expr); } @@ -805,10 +805,10 @@ public function specifyTypesInCondition( $result = $result->setAlwaysOverwriteTypes(); } return $result->setNewConditionalExpressionHolders(array_merge( - $this->processBooleanNotSureConditionalTypes($scope, $leftTypes, $rightTypes), - $this->processBooleanNotSureConditionalTypes($scope, $rightTypes, $leftTypes), - $this->processBooleanSureConditionalTypes($scope, $leftTypes, $rightTypes), - $this->processBooleanSureConditionalTypes($scope, $rightTypes, $leftTypes), + $this->processBooleanNotSureConditionalTypes($scope, $leftTypes, $rightTypes, $rightScope), + $this->processBooleanNotSureConditionalTypes($scope, $rightTypes, $leftTypes, $scope), + $this->processBooleanSureConditionalTypes($scope, $leftTypes, $rightTypes, $rightScope), + $this->processBooleanSureConditionalTypes($scope, $rightTypes, $leftTypes, $scope), ))->setRootExpr($expr); } @@ -2080,7 +2080,7 @@ private function augmentBooleanOrTruthyWithConditionalHolders(MutatingScope $sco /** * @return array */ - private function processBooleanSureConditionalTypes(Scope $scope, SpecifiedTypes $leftTypes, SpecifiedTypes $rightTypes): array + private function processBooleanSureConditionalTypes(Scope $scope, SpecifiedTypes $leftTypes, SpecifiedTypes $rightTypes, Scope $rightScope): array { $conditionExpressionTypes = []; foreach ($leftTypes->getSureTypes() as $exprString => [$expr, $type]) { @@ -2123,9 +2123,10 @@ private function processBooleanSureConditionalTypes(Scope $scope, SpecifiedTypes continue; } + $targetScope = $expr instanceof Expr\Variable ? $scope : $rightScope; $holder = new ConditionalExpressionHolder( $conditions, - ExpressionTypeHolder::createYes($expr, TypeCombinator::intersect($scope->getType($expr), $type)), + ExpressionTypeHolder::createYes($expr, TypeCombinator::intersect($targetScope->getType($expr), $type)), ); $holders[$exprString][$holder->getKey()] = $holder; } @@ -2277,7 +2278,7 @@ private function specifyTypesForFlattenedBooleanAnd( /** * @return array */ - private function processBooleanNotSureConditionalTypes(Scope $scope, SpecifiedTypes $leftTypes, SpecifiedTypes $rightTypes): array + private function processBooleanNotSureConditionalTypes(Scope $scope, SpecifiedTypes $leftTypes, SpecifiedTypes $rightTypes, Scope $rightScope): array { $conditionExpressionTypes = []; foreach ($leftTypes->getSureNotTypes() as $exprString => [$expr, $type]) { @@ -2314,9 +2315,10 @@ private function processBooleanNotSureConditionalTypes(Scope $scope, SpecifiedTy continue; } + $targetScope = $expr instanceof Expr\Variable ? $scope : $rightScope; $holder = new ConditionalExpressionHolder( $conditions, - ExpressionTypeHolder::createYes($expr, TypeCombinator::remove($scope->getType($expr), $type)), + ExpressionTypeHolder::createYes($expr, TypeCombinator::remove($targetScope->getType($expr), $type)), ); $holders[$exprString][$holder->getKey()] = $holder; } From f9280edc375dc811363cce3e1535bba707852dfa Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 4 May 2026 11:29:22 +0200 Subject: [PATCH 6/6] Add test --- tests/PHPStan/Analyser/nsrt/pr-5596.php | 42 +++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/pr-5596.php diff --git a/tests/PHPStan/Analyser/nsrt/pr-5596.php b/tests/PHPStan/Analyser/nsrt/pr-5596.php new file mode 100644 index 0000000000..7b53949871 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/pr-5596.php @@ -0,0 +1,42 @@ +name instanceof Identifier) { + assertType('PhpParser\Node\Expr\MethodCall', $node); + assertType('PhpParser\Node\Identifier', $node->name); + assertType('PhpParser\Node\Expr', $node->var); + } elseif ($node instanceof StaticCall && $node->name instanceof Identifier && $node->class instanceof Name) { + assertType('PhpParser\Node\Expr\StaticCall', $node); + assertType('PhpParser\Node\Identifier', $node->name); + assertType('PhpParser\Node\Name', $node->class); + } elseif ($node instanceof New_ && $node->class instanceof Name) { + assertType('PhpParser\Node\Expr\New_', $node); + assertType('PhpParser\Node\Name', $node->class); + } elseif ($node instanceof FuncCall && $node->name instanceof Name) { + assertType('PhpParser\Node\Expr\FuncCall', $node); + assertType('PhpParser\Node\Name', $node->name); + } elseif ($node instanceof FuncCall) { + assertType('PhpParser\Node\Expr\FuncCall', $node); + assertType('PhpParser\Node\Expr', $node->name); + } + } +}