From 1dd6b3f9218dd94e7e50ed3e965752cdde71595c Mon Sep 17 00:00:00 2001 From: Takuya Aramaki Date: Mon, 20 Oct 2025 03:40:21 +0900 Subject: [PATCH 1/2] Add regression test for #13694 --- .../Variables/DefinedVariableRuleTest.php | 10 ++++++++++ .../Rules/Variables/data/bug-13694.php | 19 +++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 tests/PHPStan/Rules/Variables/data/bug-13694.php diff --git a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php index 96b89cf070..cbd0e45c27 100644 --- a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php +++ b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php @@ -1169,4 +1169,14 @@ public function testBug8719(): void $this->analyse([__DIR__ . '/data/bug-8719.php'], []); } + public function testBug13694(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = true; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + + $this->analyse([__DIR__ . '/data/bug-13694.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Variables/data/bug-13694.php b/tests/PHPStan/Rules/Variables/data/bug-13694.php new file mode 100644 index 0000000000..8d71a3a89c --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/bug-13694.php @@ -0,0 +1,19 @@ + $things + */ +function evaluateThings(array $keys, array $things): void +{ + foreach ($keys as $key) { + if (array_key_exists($key, $things) && $things[$key] === null) { + echo "Value for key $key is null\n"; + continue; + } + + if (isset($things[$key])) { + echo "Key $key is set\n"; + } + } +} From 457de4e98fb25f666e3d17f3e492c3b0982e7821 Mon Sep 17 00:00:00 2001 From: Takuya Aramaki Date: Mon, 20 Oct 2025 03:55:49 +0900 Subject: [PATCH 2/2] Revert "Cleanup `instanceof Type` checks" This partially reverts commit 3b72271648184ebc0cc3fa215ac355e6315f10cc. --- src/Analyser/MutatingScope.php | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 2bedcd238e..075b4c99bd 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -4367,7 +4367,7 @@ public function specifyExpressionType(Expr $expr, Type $type, Type $nativeType, && !$expr->dim instanceof Expr\PostInc ) { $dimType = $scope->getType($expr->dim)->toArrayKey(); - if ($dimType->isInteger()->yes() || $dimType->isString()->yes()) { + if ($dimType instanceof ConstantIntegerType || $dimType instanceof ConstantStringType) { $exprVarType = $scope->getType($expr->var); if (!$exprVarType instanceof MixedType && !$exprVarType->isArray()->no()) { $types = [ @@ -4375,21 +4375,16 @@ public function specifyExpressionType(Expr $expr, Type $type, Type $nativeType, new ObjectType(ArrayAccess::class), new NullType(), ]; - if ($dimType->isInteger()->yes()) { + if ($dimType instanceof ConstantIntegerType) { $types[] = new StringType(); } - $offsetValueType = TypeCombinator::intersect($exprVarType, TypeCombinator::union(...$types)); - - if ($dimType instanceof ConstantIntegerType || $dimType instanceof ConstantStringType) { - $offsetValueType = TypeCombinator::intersect( - $offsetValueType, - new HasOffsetValueType($dimType, $type), - ); - } $scope = $scope->specifyExpressionType( $expr->var, - $offsetValueType, + TypeCombinator::intersect( + TypeCombinator::intersect($exprVarType, TypeCombinator::union(...$types)), + new HasOffsetValueType($dimType, $type), + ), $scope->getNativeType($expr->var), $certainty, );