From ecd37c8af5d2b60a900d880608ecb7264a367d1e Mon Sep 17 00:00:00 2001 From: VincentLanglet <9052536+VincentLanglet@users.noreply.github.com> Date: Thu, 2 Apr 2026 13:44:37 +0000 Subject: [PATCH 1/2] Fix phpstan/phpstan#12597: variable might not be defined after in_array check - Added subtype matching for conditional expressions in MutatingScope::processSpecifiedTypes - After exact-match conditional resolution, a second pass checks if specified types are subtypes of guard types for maybe-defined variables - Limited to Variable targets with Maybe certainty to avoid interfering with type narrowing - New regression test in tests/PHPStan/Rules/Variables/data/bug-12597.php --- src/Analyser/MutatingScope.php | 26 +++++++++++++++++++ .../Variables/DefinedVariableRuleTest.php | 9 +++++++ .../Rules/Variables/data/bug-12597.php | 22 ++++++++++++++++ 3 files changed, 57 insertions(+) create mode 100644 tests/PHPStan/Rules/Variables/data/bug-12597.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index a43ba35dda..5307ad301f 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -3229,6 +3229,32 @@ public function filterBySpecifiedTypes(SpecifiedTypes $specifiedTypes): self } } + foreach ($scope->conditionalExpressions as $conditionalExprString => $conditionalExpressions) { + if (array_key_exists($conditionalExprString, $conditions)) { + continue; + } + if ( + !array_key_exists($conditionalExprString, $scope->expressionTypes) + || $scope->expressionTypes[$conditionalExprString]->getCertainty()->yes() + || !$scope->expressionTypes[$conditionalExprString]->getExpr() instanceof Variable + ) { + continue; + } + foreach ($conditionalExpressions as $conditionalExpression) { + foreach ($conditionalExpression->getConditionExpressionTypeHolders() as $holderExprString => $conditionalTypeHolder) { + if ( + !array_key_exists($holderExprString, $specifiedExpressions) + || !$specifiedExpressions[$holderExprString]->getCertainty()->equals($conditionalTypeHolder->getCertainty()) + || !$conditionalTypeHolder->getType()->isSuperTypeOf($specifiedExpressions[$holderExprString]->getType())->yes() + ) { + continue 2; + } + } + + $conditions[$conditionalExprString][] = $conditionalExpression; + } + } + foreach ($conditions as $conditionalExprString => $expressions) { $certainty = TrinaryLogic::lazyExtremeIdentity($expressions, static fn (ConditionalExpressionHolder $holder) => $holder->getTypeHolder()->getCertainty()); if ($certainty->no()) { diff --git a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php index b6161df233..428309dbdd 100644 --- a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php +++ b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php @@ -905,6 +905,15 @@ public function testBug8212(): void $this->analyse([__DIR__ . '/data/bug-8212.php'], []); } + public function testBug12597(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = true; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + $this->analyse([__DIR__ . '/data/bug-12597.php'], []); + } + public function testBug4173(): void { $this->cliArgumentsVariablesRegistered = true; diff --git a/tests/PHPStan/Rules/Variables/data/bug-12597.php b/tests/PHPStan/Rules/Variables/data/bug-12597.php new file mode 100644 index 0000000000..2cfdf45974 --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/bug-12597.php @@ -0,0 +1,22 @@ +message($message); + } + } + + public function message(string $message): void {} +} From bcfcc3b7bea5b94c07de78b04d931faf6b98a8be Mon Sep 17 00:00:00 2001 From: phpstan-bot Date: Thu, 2 Apr 2026 14:32:06 +0000 Subject: [PATCH 2/2] Add non-regression tests for bug-12597 with is_int/is_string/is_object/instanceof Add additional test scenarios per review feedback to ensure the conditional variable definition fix works with type-narrowing functions (is_int, is_string, is_object) and instanceof checks, not just in_array. Co-Authored-By: Claude Opus 4.6 --- .../Rules/Variables/data/bug-12597.php | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/PHPStan/Rules/Variables/data/bug-12597.php b/tests/PHPStan/Rules/Variables/data/bug-12597.php index 2cfdf45974..6964dd4f04 100644 --- a/tests/PHPStan/Rules/Variables/data/bug-12597.php +++ b/tests/PHPStan/Rules/Variables/data/bug-12597.php @@ -20,3 +20,49 @@ public function test(int $type): void public function message(string $message): void {} } + +class HelloWorld2 +{ + public function test(mixed $type): void + { + if (is_int($type) || is_string($type)) { + $message = 'Hello!'; + } + + if (is_int($type)) { + $this->message($message); + } + } + + public function message(string $message): void {} +} + +class Foo {} +class Bar {} + +class HelloWorld3 +{ + public function test(mixed $type): void + { + if (is_int($type) || is_object($type)) { + $message = 'Hello!'; + } + + if (is_int($type)) { + $this->message($message); + } + } + + public function test2(mixed $type): void + { + if ($type instanceof Foo || $type instanceof Bar) { + $message = 'Hello!'; + } + + if ($type instanceof Foo) { + $this->message($message); + } + } + + public function message(string $message): void {} +}