diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index a43ba35dda5..b82d5c1119b 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1574,7 +1574,7 @@ public function enterPropertyHook( $realParameterTypes = $this->getRealParameterTypes($hook); - return $this->enterFunctionLike( + $scope = $this->enterFunctionLike( new PhpMethodFromParserNodeReflection( $this->getClassReflection(), $hook, @@ -1606,6 +1606,12 @@ public function enterPropertyHook( ), true, ); + + if ($hookName === 'set' && !$this->getClassReflection()->getNativeProperty($propertyName)->getNativeReflection()->hasDefaultValue()) { + $scope = $scope->invalidateExpression(new PropertyInitializationExpr($propertyName)); + } + + return $scope; } private function transformStaticType(Type $type): Type diff --git a/tests/PHPStan/Rules/Variables/IssetRuleTest.php b/tests/PHPStan/Rules/Variables/IssetRuleTest.php index fd841e49b16..c2a29dd0116 100644 --- a/tests/PHPStan/Rules/Variables/IssetRuleTest.php +++ b/tests/PHPStan/Rules/Variables/IssetRuleTest.php @@ -526,6 +526,19 @@ public function testBug9503(): void $this->analyse([__DIR__ . '/data/bug-9503.php'], []); } + #[RequiresPhp('>= 8.4')] + public function testBug13473(): void + { + $this->treatPhpDocTypesAsCertain = true; + + $this->analyse([__DIR__ . '/data/bug-13473.php'], [ + [ + 'Property Bug13473\Bar::$bar in isset() is not nullable nor uninitialized.', + 30, + ], + ]); + } + public function testBug14393(): void { $this->treatPhpDocTypesAsCertain = true; diff --git a/tests/PHPStan/Rules/Variables/data/bug-13473.php b/tests/PHPStan/Rules/Variables/data/bug-13473.php new file mode 100644 index 00000000000..558228d1d2b --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/bug-13473.php @@ -0,0 +1,41 @@ += 8.4 + +declare(strict_types = 1); + +namespace Bug13473; + +class Foo { + private(set) int $bar { + get => $this->bar; + set(int $bar) { + if (isset($this->bar)) { + throw new \Exception('bar is set'); + } + $this->bar = $bar; + } + } + + public function __construct(int $bar) + { + $this->bar = $bar; + } +} + +$foo = new Foo(10); + +class Bar { + private(set) int $bar = 1 { + get => $this->bar; + set(int $bar) { + if (isset($this->bar)) { + throw new \Exception('bar is set'); + } + $this->bar = $bar; + } + } + + public function __construct(int $bar) + { + $this->bar = $bar; + } +}