diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 603035e2d1..028bffe544 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -2819,7 +2819,17 @@ static function (): void { } foreach ($properties as $name => $type) { $optional = in_array($name, $optionalProperties, true) || $refCount[$name] < count($constantArrays); - $scope = $scope->assignVariable($name, $type, $type, $optional ? TrinaryLogic::createMaybe() : TrinaryLogic::createYes()); + + if (!$optional) { + $scope = $scope->assignVariable($name, $type, $type, TrinaryLogic::createYes()); + } else { + $hasVariable = $scope->hasVariableType($name); + if (!$hasVariable->no()) { + $type = TypeCombinator::union($scope->getVariableType($name), $type); + } + + $scope = $scope->assignVariable($name, $type, $type, $scope->hasVariableType($name)->or(TrinaryLogic::createMaybe())); + } } } else { $scope = $scope->afterExtractCall(); diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index a90728cb30..e6639e0907 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -226,6 +226,7 @@ private static function findTestFiles(): iterable yield __DIR__ . '/../Rules/Comparison/data/bug-5365.php'; yield __DIR__ . '/../Rules/Comparison/data/bug-6551.php'; yield __DIR__ . '/../Rules/Variables/data/bug-9403.php'; + yield __DIR__ . '/../Rules/Variables/data/bug-12364.php'; yield __DIR__ . '/../Rules/Methods/data/bug-9542.php'; yield __DIR__ . '/../Rules/Functions/data/bug-9803.php'; yield __DIR__ . '/../Rules/PhpDoc/data/bug-10594.php'; diff --git a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php index 96b89cf070..01628eaaa9 100644 --- a/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php +++ b/tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php @@ -1011,6 +1011,15 @@ public function testIsStringNarrowsCertainty(): void ]); } + public function testBug12364(): void + { + $this->cliArgumentsVariablesRegistered = true; + $this->polluteScopeWithLoopInitialAssignments = true; + $this->checkMaybeUndefinedVariables = true; + $this->polluteScopeWithAlwaysIterableForeach = true; + $this->analyse([__DIR__ . '/data/bug-12364.php'], []); + } + public function testDiscussion10252(): void { $this->cliArgumentsVariablesRegistered = true; diff --git a/tests/PHPStan/Rules/Variables/data/bug-12364.php b/tests/PHPStan/Rules/Variables/data/bug-12364.php new file mode 100644 index 0000000000..84cb57f799 --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/bug-12364.php @@ -0,0 +1,19 @@ + 'foo' ]; +} + +$x = $y = null; +assertType('null', $x); +assertType('null', $y); +extract(foo()); +assertType('string', $x); +assertType('string|null', $y); // <-- should be: null|string +var_dump($x); +var_dump($y); // <-- does exist