diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 64e46074738..310c280c2a5 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -1087,6 +1087,14 @@ public function issetCheck(Expr $expr, callable $typeCallback, ?bool $result = n } if ($result !== null) { + if ($expr instanceof Node\Expr\PropertyFetch) { + return $this->issetCheck($expr->var, $typeCallback, $result); + } + + if ($expr->class instanceof Expr) { + return $this->issetCheck($expr->class, $typeCallback, $result); + } + return $result; } diff --git a/src/Rules/IssetCheck.php b/src/Rules/IssetCheck.php index 594d01dd259..35195e7ea52 100644 --- a/src/Rules/IssetCheck.php +++ b/src/Rules/IssetCheck.php @@ -196,6 +196,14 @@ static function (Type $type) use ($typeMessageCallback): ?string { $propertyDescription = $this->propertyDescriptor->describeProperty($propertyReflection, $scope, $expr); $propertyType = $propertyReflection->getWritableType(); if ($error !== null) { + if ($expr instanceof Node\Expr\PropertyFetch) { + return $this->check($expr->var, $scope, $operatorDescription, $identifier, $typeMessageCallback, $error); + } + + if ($expr->class instanceof Expr) { + return $this->check($expr->class, $scope, $operatorDescription, $identifier, $typeMessageCallback, $error); + } + return $error; } if (!$this->checkAdvancedIsset) { diff --git a/tests/PHPStan/Analyser/nsrt/bug-14555.php b/tests/PHPStan/Analyser/nsrt/bug-14555.php new file mode 100644 index 00000000000..61e210bbec6 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-14555.php @@ -0,0 +1,26 @@ += 8.1 + +declare(strict_types = 1); + +namespace Bug14555Nsrt; + +use function PHPStan\Testing\assertType; + +class ValueObject { + function __construct( + public readonly string $value, + ) {} +} + +class SomeDTO { + function __construct( + public readonly ValueObject $value, + ) {} +} + +/** @param array> $array */ +function testCoalesceType(array $array): void +{ + $someValue = $array['someKey'][0]->value->value ?? null; + assertType('string|null', $someValue); +} diff --git a/tests/PHPStan/Rules/Variables/IssetRuleTest.php b/tests/PHPStan/Rules/Variables/IssetRuleTest.php index cbf9d52759b..c1492e62901 100644 --- a/tests/PHPStan/Rules/Variables/IssetRuleTest.php +++ b/tests/PHPStan/Rules/Variables/IssetRuleTest.php @@ -531,6 +531,13 @@ public function testBug9503(): void $this->analyse([__DIR__ . '/data/bug-9503.php'], []); } + public function testBug14555(): void + { + $this->treatPhpDocTypesAsCertain = true; + + $this->analyse([__DIR__ . '/data/bug-14555.php'], []); + } + public function testBug14393(): void { $this->treatPhpDocTypesAsCertain = true; diff --git a/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php b/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php index 09960f4a32f..b8ed61a1d96 100644 --- a/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php +++ b/tests/PHPStan/Rules/Variables/NullCoalesceRuleTest.php @@ -397,6 +397,12 @@ public function testBug14458(): void $this->analyse([__DIR__ . '/data/bug-14458.php'], []); } + #[RequiresPhp('>= 8.1.0')] + public function testBug14555(): void + { + $this->analyse([__DIR__ . '/data/bug-14555.php'], []); + } + #[RequiresPhp('>= 8.1.0')] public function testBug14459(): void { diff --git a/tests/PHPStan/Rules/Variables/data/bug-14555.php b/tests/PHPStan/Rules/Variables/data/bug-14555.php new file mode 100644 index 00000000000..25aa52f126a --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/bug-14555.php @@ -0,0 +1,50 @@ += 8.1 + +declare(strict_types = 1); + +namespace Bug14555; + +class ValueObject { + function __construct( + public readonly string $value, + ) {} +} + +class SomeDTO { + function __construct( + public readonly ValueObject $value, + ) {} +} + +class StaticHolder { + public static ValueObject $value; +} + +/** @param array> $array */ +function exampleNullCoalesce(array $array): void +{ + $someValue = $array['someKey'][0]->value->value ?? null; + + $dto = $array['someKey'][0] ?? null; + $someValue2 = $dto->value->value ?? null; +} + +/** @param array> $array */ +function exampleIsset(array $array): void +{ + if (isset($array['someKey'][0]->value->value)) { + echo 'yes'; + } +} + +/** @param array> $array */ +function exampleNullCoalesceAssign(array $array): void +{ + $someValue = $array['someKey'][0]->value->value ??= 'default'; +} + +/** @param array> $array */ +function exampleStaticProperty(array $array): void +{ + $someValue = $array['someKey'][0]::$value->value ?? null; +}