From 9d9135315b1194f95bf5502a4dde62539eff38ec Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 17 Apr 2025 14:15:47 +0200 Subject: [PATCH 01/13] Don't remember non-nullable properties as nullable --- src/Analyser/NodeScopeResolver.php | 22 +++++++++------ .../nsrt/property-null-after-assignment.php | 27 +++++++++++++++++++ 2 files changed, 41 insertions(+), 8 deletions(-) create mode 100644 tests/PHPStan/Analyser/nsrt/property-null-after-assignment.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 6910dfa051..30f59152db 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -5625,12 +5625,15 @@ static function (): void { $assignedExprType = $scope->getType($assignedExpr); $nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scope); if ($propertyReflection->canChangeTypeAfterAssignment()) { - if ($propertyReflection->hasNativeType() && $scope->isDeclareStrictTypes()) { - $propertyNativeType = $propertyReflection->getNativeType(); + $assignedNativeType = $scope->getNativeType($assignedExpr); + $propertyNativeType = $propertyReflection->getNativeType(); - $scope = $scope->assignExpression($var, TypeCombinator::intersect($assignedExprType->toCoercedArgumentType(true), $propertyNativeType), TypeCombinator::intersect($scope->getNativeType($assignedExpr)->toCoercedArgumentType(true), $propertyNativeType)); + if ($propertyReflection->hasNativeType() && $scope->isDeclareStrictTypes()) { + $scope = $scope->assignExpression($var, TypeCombinator::intersect($assignedExprType->toCoercedArgumentType(true), $propertyNativeType), TypeCombinator::intersect($assignedNativeType->toCoercedArgumentType(true), $propertyNativeType)); + } elseif ($propertyNativeType->isNull()->no()) { + $scope = $scope->assignExpression($var, TypeCombinator::removeNull($assignedExprType), TypeCombinator::removeNull($assignedNativeType)); } else { - $scope = $scope->assignExpression($var, $assignedExprType, $scope->getNativeType($assignedExpr)); + $scope = $scope->assignExpression($var, $assignedExprType, $assignedNativeType); } } $declaringClass = $propertyReflection->getDeclaringClass(); @@ -5696,12 +5699,15 @@ static function (): void { $assignedExprType = $scope->getType($assignedExpr); $nodeCallback(new PropertyAssignNode($var, $assignedExpr, $isAssignOp), $scope); if ($propertyReflection !== null && $propertyReflection->canChangeTypeAfterAssignment()) { - if ($propertyReflection->hasNativeType() && $scope->isDeclareStrictTypes()) { - $propertyNativeType = $propertyReflection->getNativeType(); + $assignedNativeType = $scope->getNativeType($assignedExpr); + $propertyNativeType = $propertyReflection->getNativeType(); - $scope = $scope->assignExpression($var, TypeCombinator::intersect($assignedExprType->toCoercedArgumentType(true), $propertyNativeType), TypeCombinator::intersect($scope->getNativeType($assignedExpr)->toCoercedArgumentType(true), $propertyNativeType)); + if ($propertyReflection->hasNativeType() && $scope->isDeclareStrictTypes()) { + $scope = $scope->assignExpression($var, TypeCombinator::intersect($assignedExprType->toCoercedArgumentType(true), $propertyNativeType), TypeCombinator::intersect($assignedNativeType->toCoercedArgumentType(true), $propertyNativeType)); + } elseif ($propertyNativeType->isNull()->no()) { + $scope = $scope->assignExpression($var, TypeCombinator::removeNull($assignedExprType), TypeCombinator::removeNull($assignedNativeType)); } else { - $scope = $scope->assignExpression($var, $assignedExprType, $scope->getNativeType($assignedExpr)); + $scope = $scope->assignExpression($var, $assignedExprType, $assignedNativeType); } } } else { diff --git a/tests/PHPStan/Analyser/nsrt/property-null-after-assignment.php b/tests/PHPStan/Analyser/nsrt/property-null-after-assignment.php new file mode 100644 index 0000000000..097edec38a --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/property-null-after-assignment.php @@ -0,0 +1,27 @@ +i = getIntOrNull(); + } + + public function doFoo(): void { + assertType('int', $this->i); + assertNativeType('int', $this->i); + } +} + +function getIntOrNull(): ?int { + if (rand(0, 1) === 0) { + return null; + } + return 1; +} From 077c040de2f5824aabf30200496b07b2f0cb6309 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 17 Apr 2025 14:24:48 +0200 Subject: [PATCH 02/13] Update property-null-after-assignment.php --- .../nsrt/property-null-after-assignment.php | 48 ++++++++++++++++++- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/tests/PHPStan/Analyser/nsrt/property-null-after-assignment.php b/tests/PHPStan/Analyser/nsrt/property-null-after-assignment.php index 097edec38a..4441eb7a0b 100644 --- a/tests/PHPStan/Analyser/nsrt/property-null-after-assignment.php +++ b/tests/PHPStan/Analyser/nsrt/property-null-after-assignment.php @@ -1,11 +1,11 @@ i = getIntOrFloatOrNull(); + } + + public function doFoo(): void { + assertType('float|int', $this->i); + assertNativeType('float|int', $this->i); + } +} + +function getIntOrFloatOrNull(): null|int|float { + if (rand(0, 1) === 0) { + return null; + } + + if (rand(0, 10) === 0) { + return 1.0; + } + return 1; +} + +class NarrowsUnion { + private readonly int|float $i; + + public function __construct() + { + $this->i = getInt(); + } + + public function doFoo(): void { + assertType('int', $this->i); + assertNativeType('int', $this->i); + } +} + +function getInt(): int { + return 1; +} From bd78d229172bd80ed0cf68e32f1de9ab94120847 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 17 Apr 2025 14:31:07 +0200 Subject: [PATCH 03/13] readonly property is php 8.1+ --- tests/PHPStan/Analyser/nsrt/property-null-after-assignment.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/nsrt/property-null-after-assignment.php b/tests/PHPStan/Analyser/nsrt/property-null-after-assignment.php index 4441eb7a0b..854fe09380 100644 --- a/tests/PHPStan/Analyser/nsrt/property-null-after-assignment.php +++ b/tests/PHPStan/Analyser/nsrt/property-null-after-assignment.php @@ -1,4 +1,4 @@ -= 8.1 namespace PropertyNullAfterAssignmentStrictTypesDisabled; From 6491c5deec880778bdaf31dabbcebc19f3d6f843 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 17 Apr 2025 14:33:23 +0200 Subject: [PATCH 04/13] rename test --- ...fter-assignment.php => remember-non-nullable-property.php} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename tests/PHPStan/Analyser/nsrt/{property-null-after-assignment.php => remember-non-nullable-property.php} (92%) diff --git a/tests/PHPStan/Analyser/nsrt/property-null-after-assignment.php b/tests/PHPStan/Analyser/nsrt/remember-non-nullable-property.php similarity index 92% rename from tests/PHPStan/Analyser/nsrt/property-null-after-assignment.php rename to tests/PHPStan/Analyser/nsrt/remember-non-nullable-property.php index 854fe09380..d9c9f4da7d 100644 --- a/tests/PHPStan/Analyser/nsrt/property-null-after-assignment.php +++ b/tests/PHPStan/Analyser/nsrt/remember-non-nullable-property.php @@ -1,6 +1,6 @@ = 8.1 -namespace PropertyNullAfterAssignmentStrictTypesDisabled; +namespace RememberNonNullablePropertyWhenStrictTypesDisabled; use function PHPStan\Testing\assertNativeType; use function PHPStan\Testing\assertType; @@ -28,7 +28,7 @@ function getIntOrNull(): ?int { class KeepsPropertyNonNullable2 { - private readonly int|float $i; + private int|float $i; public function __construct() { From 3285edc01fcfb942deaa688ffa10f980c2bbe1f5 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 17 Apr 2025 14:44:18 +0200 Subject: [PATCH 05/13] more tests --- .../nsrt/remember-non-nullable-property.php | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/tests/PHPStan/Analyser/nsrt/remember-non-nullable-property.php b/tests/PHPStan/Analyser/nsrt/remember-non-nullable-property.php index d9c9f4da7d..1b832c0104 100644 --- a/tests/PHPStan/Analyser/nsrt/remember-non-nullable-property.php +++ b/tests/PHPStan/Analyser/nsrt/remember-non-nullable-property.php @@ -19,6 +19,21 @@ public function doFoo(): void { } } +class DontCoercePhpdocType { + /** @var int */ + private $i; + + public function __construct() + { + $this->i = getIntOrNull(); + } + + public function doFoo(): void { + assertType('mixed', $this->i); + assertNativeType('mixed', $this->i); + } +} + function getIntOrNull(): ?int { if (rand(0, 1) === 0) { return null; @@ -52,8 +67,8 @@ function getIntOrFloatOrNull(): null|int|float { return 1; } -class NarrowsUnion { - private readonly int|float $i; +class NarrowsNativeUnion { + private int|float $i; public function __construct() { From 7612714718bc97b9d3a3836c7af5a18558d1e205 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 17 Apr 2025 14:49:13 +0200 Subject: [PATCH 06/13] Update remember-non-nullable-property.php --- .../PHPStan/Analyser/nsrt/remember-non-nullable-property.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/PHPStan/Analyser/nsrt/remember-non-nullable-property.php b/tests/PHPStan/Analyser/nsrt/remember-non-nullable-property.php index 1b832c0104..ed22584733 100644 --- a/tests/PHPStan/Analyser/nsrt/remember-non-nullable-property.php +++ b/tests/PHPStan/Analyser/nsrt/remember-non-nullable-property.php @@ -29,7 +29,7 @@ public function __construct() } public function doFoo(): void { - assertType('mixed', $this->i); + assertType('int', $this->i); assertNativeType('mixed', $this->i); } } @@ -68,7 +68,7 @@ function getIntOrFloatOrNull(): null|int|float { } class NarrowsNativeUnion { - private int|float $i; + private readonly int|float $i; public function __construct() { From 4d1b094e7039bf2faf3c9fb4b6a8b81edb90f394 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Thu, 17 Apr 2025 14:51:58 +0200 Subject: [PATCH 07/13] Update NodeScopeResolver.php --- src/Analyser/NodeScopeResolver.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 30f59152db..64c27264de 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -5630,7 +5630,7 @@ static function (): void { if ($propertyReflection->hasNativeType() && $scope->isDeclareStrictTypes()) { $scope = $scope->assignExpression($var, TypeCombinator::intersect($assignedExprType->toCoercedArgumentType(true), $propertyNativeType), TypeCombinator::intersect($assignedNativeType->toCoercedArgumentType(true), $propertyNativeType)); - } elseif ($propertyNativeType->isNull()->no()) { + } elseif ($propertyReflection->hasNativeType() && $propertyNativeType->isNull()->no()) { $scope = $scope->assignExpression($var, TypeCombinator::removeNull($assignedExprType), TypeCombinator::removeNull($assignedNativeType)); } else { $scope = $scope->assignExpression($var, $assignedExprType, $assignedNativeType); @@ -5704,7 +5704,7 @@ static function (): void { if ($propertyReflection->hasNativeType() && $scope->isDeclareStrictTypes()) { $scope = $scope->assignExpression($var, TypeCombinator::intersect($assignedExprType->toCoercedArgumentType(true), $propertyNativeType), TypeCombinator::intersect($assignedNativeType->toCoercedArgumentType(true), $propertyNativeType)); - } elseif ($propertyNativeType->isNull()->no()) { + } elseif ($propertyReflection->hasNativeType() && $propertyNativeType->isNull()->no()) { $scope = $scope->assignExpression($var, TypeCombinator::removeNull($assignedExprType), TypeCombinator::removeNull($assignedNativeType)); } else { $scope = $scope->assignExpression($var, $assignedExprType, $assignedNativeType); From 2c8c9ee670c98797a1cfc4a782acd949b126bbae Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 18 Apr 2025 08:28:17 +0200 Subject: [PATCH 08/13] property cannot get null, even in strict-types --- remember-non-nullable-property.php | 100 ++++++++++++++++++ src/Analyser/NodeScopeResolver.php | 14 ++- ...ember-non-nullable-property-non-strict.php | 86 +++++++++++++++ .../nsrt/remember-non-nullable-property.php | 72 +------------ 4 files changed, 201 insertions(+), 71 deletions(-) create mode 100644 remember-non-nullable-property.php create mode 100644 tests/PHPStan/Analyser/nsrt/remember-non-nullable-property-non-strict.php diff --git a/remember-non-nullable-property.php b/remember-non-nullable-property.php new file mode 100644 index 0000000000..5c48b935c1 --- /dev/null +++ b/remember-non-nullable-property.php @@ -0,0 +1,100 @@ += 8.1 + +namespace RememberNonNullablePropertyWhenStrictTypesDisabled; + +use function PHPStan\Testing\assertNativeType; +use function PHPStan\Testing\assertType; + +class NarrowsNativeUnion { + private readonly int|float $i; + + public function __construct() + { + $this->i = getInt(); + } + + public function doFoo(): void { + assertType('int', $this->i); + assertNativeType('int', $this->i); + } +} + +class KeepsPropertyNonNullable { + private readonly int $i; + + public function __construct() + { + $this->i = getIntOrNull(); + } + + public function doFoo(): void { + assertType('int', $this->i); + assertNativeType('int', $this->i); + } +} + +class DontCoercePhpdocType { + /** @var int */ + private $i; + + public function __construct() + { + $this->i = getIntOrNull(); + } + + public function doFoo(): void { + assertType('int', $this->i); + assertNativeType('mixed', $this->i); + } +} + +function getIntOrNull(): ?int { + if (rand(0, 1) === 0) { + return null; + } + return 1; +} + + +class KeepsPropertyNonNullable2 { + private int|float $i; + + public function __construct() + { + $this->i = getIntOrFloatOrNull(); + } + + public function doFoo(): void { + assertType('float|int', $this->i); + assertNativeType('float|int', $this->i); + } +} + +function getIntOrFloatOrNull(): null|int|float { + if (rand(0, 1) === 0) { + return null; + } + + if (rand(0, 10) === 0) { + return 1.0; + } + return 1; +} + +class NarrowsNativeUnion { + private readonly int|float $i; + + public function __construct() + { + $this->i = getInt(); + } + + public function doFoo(): void { + assertType('int', $this->i); + assertNativeType('int', $this->i); + } +} + +function getInt(): int { + return 1; +} diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 64c27264de..c88252a9fe 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -5628,10 +5628,13 @@ static function (): void { $assignedNativeType = $scope->getNativeType($assignedExpr); $propertyNativeType = $propertyReflection->getNativeType(); + if ($propertyReflection->hasNativeType() && $propertyNativeType->isNull()->no()) { + $assignedExprType = TypeCombinator::removeNull($assignedExprType); + $assignedNativeType = TypeCombinator::removeNull($assignedNativeType); + } + if ($propertyReflection->hasNativeType() && $scope->isDeclareStrictTypes()) { $scope = $scope->assignExpression($var, TypeCombinator::intersect($assignedExprType->toCoercedArgumentType(true), $propertyNativeType), TypeCombinator::intersect($assignedNativeType->toCoercedArgumentType(true), $propertyNativeType)); - } elseif ($propertyReflection->hasNativeType() && $propertyNativeType->isNull()->no()) { - $scope = $scope->assignExpression($var, TypeCombinator::removeNull($assignedExprType), TypeCombinator::removeNull($assignedNativeType)); } else { $scope = $scope->assignExpression($var, $assignedExprType, $assignedNativeType); } @@ -5702,10 +5705,13 @@ static function (): void { $assignedNativeType = $scope->getNativeType($assignedExpr); $propertyNativeType = $propertyReflection->getNativeType(); + if ($propertyReflection->hasNativeType() && $propertyNativeType->isNull()->no()) { + $assignedExprType = TypeCombinator::removeNull($assignedExprType); + $assignedNativeType = TypeCombinator::removeNull($assignedNativeType); + } + if ($propertyReflection->hasNativeType() && $scope->isDeclareStrictTypes()) { $scope = $scope->assignExpression($var, TypeCombinator::intersect($assignedExprType->toCoercedArgumentType(true), $propertyNativeType), TypeCombinator::intersect($assignedNativeType->toCoercedArgumentType(true), $propertyNativeType)); - } elseif ($propertyReflection->hasNativeType() && $propertyNativeType->isNull()->no()) { - $scope = $scope->assignExpression($var, TypeCombinator::removeNull($assignedExprType), TypeCombinator::removeNull($assignedNativeType)); } else { $scope = $scope->assignExpression($var, $assignedExprType, $assignedNativeType); } diff --git a/tests/PHPStan/Analyser/nsrt/remember-non-nullable-property-non-strict.php b/tests/PHPStan/Analyser/nsrt/remember-non-nullable-property-non-strict.php new file mode 100644 index 0000000000..cb808d96d4 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/remember-non-nullable-property-non-strict.php @@ -0,0 +1,86 @@ += 8.1 + +namespace RememberNonNullablePropertyWhenStrictTypesDisabled; + +use function PHPStan\Testing\assertNativeType; +use function PHPStan\Testing\assertType; + +class KeepsPropertyNonNullable { + private readonly int $i; + + public function __construct() + { + $this->i = getIntOrNull(); + } + + public function doFoo(): void { + assertType('int', $this->i); + assertNativeType('int', $this->i); + } +} + +class DontCoercePhpdocType { + /** @var int */ + private $i; + + public function __construct() + { + $this->i = getIntOrNull(); + } + + public function doFoo(): void { + assertType('int', $this->i); + assertNativeType('mixed', $this->i); + } +} + +function getIntOrNull(): ?int { + if (rand(0, 1) === 0) { + return null; + } + return 1; +} + + +class KeepsPropertyNonNullable2 { + private int|float $i; + + public function __construct() + { + $this->i = getIntOrFloatOrNull(); + } + + public function doFoo(): void { + assertType('float|int', $this->i); + assertNativeType('float|int', $this->i); + } +} + +function getIntOrFloatOrNull(): null|int|float { + if (rand(0, 1) === 0) { + return null; + } + + if (rand(0, 10) === 0) { + return 1.0; + } + return 1; +} + +class NarrowsNativeUnion { + private readonly int|float $i; + + public function __construct() + { + $this->i = getInt(); + } + + public function doFoo(): void { + assertType('int', $this->i); + assertNativeType('int', $this->i); + } +} + +function getInt(): int { + return 1; +} diff --git a/tests/PHPStan/Analyser/nsrt/remember-non-nullable-property.php b/tests/PHPStan/Analyser/nsrt/remember-non-nullable-property.php index ed22584733..ca2fc557b9 100644 --- a/tests/PHPStan/Analyser/nsrt/remember-non-nullable-property.php +++ b/tests/PHPStan/Analyser/nsrt/remember-non-nullable-property.php @@ -1,73 +1,11 @@ -= 8.1 += 8.1 -namespace RememberNonNullablePropertyWhenStrictTypesDisabled; +namespace RememberNonNullableProperty; use function PHPStan\Testing\assertNativeType; use function PHPStan\Testing\assertType; -class KeepsPropertyNonNullable { - private readonly int $i; - - public function __construct() - { - $this->i = getIntOrNull(); - } - - public function doFoo(): void { - assertType('int', $this->i); - assertNativeType('int', $this->i); - } -} - -class DontCoercePhpdocType { - /** @var int */ - private $i; - - public function __construct() - { - $this->i = getIntOrNull(); - } - - public function doFoo(): void { - assertType('int', $this->i); - assertNativeType('mixed', $this->i); - } -} - -function getIntOrNull(): ?int { - if (rand(0, 1) === 0) { - return null; - } - return 1; -} - - -class KeepsPropertyNonNullable2 { - private int|float $i; - - public function __construct() - { - $this->i = getIntOrFloatOrNull(); - } - - public function doFoo(): void { - assertType('float|int', $this->i); - assertNativeType('float|int', $this->i); - } -} - -function getIntOrFloatOrNull(): null|int|float { - if (rand(0, 1) === 0) { - return null; - } - - if (rand(0, 10) === 0) { - return 1.0; - } - return 1; -} - -class NarrowsNativeUnion { +class DoesntNarrowNativeUnion { private readonly int|float $i; public function __construct() @@ -76,8 +14,8 @@ public function __construct() } public function doFoo(): void { - assertType('int', $this->i); - assertNativeType('int', $this->i); + assertType('float|int', $this->i); + assertNativeType('float|int', $this->i); } } From 307d43e63bf76bfd36469e55985afcb26f04dad2 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 18 Apr 2025 10:39:28 +0200 Subject: [PATCH 09/13] restructure --- src/Analyser/NodeScopeResolver.php | 32 +++++++++++++++++++----------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index c88252a9fe..023ec595d0 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -5628,13 +5628,17 @@ static function (): void { $assignedNativeType = $scope->getNativeType($assignedExpr); $propertyNativeType = $propertyReflection->getNativeType(); - if ($propertyReflection->hasNativeType() && $propertyNativeType->isNull()->no()) { - $assignedExprType = TypeCombinator::removeNull($assignedExprType); - $assignedNativeType = TypeCombinator::removeNull($assignedNativeType); - } + if ($propertyReflection->hasNativeType()) { + if ($propertyNativeType->isNull()->no()) { + $assignedExprType = TypeCombinator::removeNull($assignedExprType); + $assignedNativeType = TypeCombinator::removeNull($assignedNativeType); + } - if ($propertyReflection->hasNativeType() && $scope->isDeclareStrictTypes()) { - $scope = $scope->assignExpression($var, TypeCombinator::intersect($assignedExprType->toCoercedArgumentType(true), $propertyNativeType), TypeCombinator::intersect($assignedNativeType->toCoercedArgumentType(true), $propertyNativeType)); + if ($scope->isDeclareStrictTypes()) { + $scope = $scope->assignExpression($var, TypeCombinator::intersect($assignedExprType->toCoercedArgumentType(true), $propertyNativeType), TypeCombinator::intersect($assignedNativeType->toCoercedArgumentType(true), $propertyNativeType)); + } else { + $scope = $scope->assignExpression($var, $assignedExprType, $assignedNativeType); + } } else { $scope = $scope->assignExpression($var, $assignedExprType, $assignedNativeType); } @@ -5705,13 +5709,17 @@ static function (): void { $assignedNativeType = $scope->getNativeType($assignedExpr); $propertyNativeType = $propertyReflection->getNativeType(); - if ($propertyReflection->hasNativeType() && $propertyNativeType->isNull()->no()) { - $assignedExprType = TypeCombinator::removeNull($assignedExprType); - $assignedNativeType = TypeCombinator::removeNull($assignedNativeType); - } + if ($propertyReflection->hasNativeType()) { + if ($propertyNativeType->isNull()->no()) { + $assignedExprType = TypeCombinator::removeNull($assignedExprType); + $assignedNativeType = TypeCombinator::removeNull($assignedNativeType); + } - if ($propertyReflection->hasNativeType() && $scope->isDeclareStrictTypes()) { - $scope = $scope->assignExpression($var, TypeCombinator::intersect($assignedExprType->toCoercedArgumentType(true), $propertyNativeType), TypeCombinator::intersect($assignedNativeType->toCoercedArgumentType(true), $propertyNativeType)); + if ($scope->isDeclareStrictTypes()) { + $scope = $scope->assignExpression($var, TypeCombinator::intersect($assignedExprType->toCoercedArgumentType(true), $propertyNativeType), TypeCombinator::intersect($assignedNativeType->toCoercedArgumentType(true), $propertyNativeType)); + } else { + $scope = $scope->assignExpression($var, $assignedExprType, $assignedNativeType); + } } else { $scope = $scope->assignExpression($var, $assignedExprType, $assignedNativeType); } From d88023a50154ab45e18878199627ff03264841c9 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 18 Apr 2025 11:06:12 +0200 Subject: [PATCH 10/13] restructure 2 --- src/Analyser/NodeScopeResolver.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 023ec595d0..57337bc218 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -5629,13 +5629,13 @@ static function (): void { $propertyNativeType = $propertyReflection->getNativeType(); if ($propertyReflection->hasNativeType()) { - if ($propertyNativeType->isNull()->no()) { + if ($scope->isDeclareStrictTypes()) { + $scope = $scope->assignExpression($var, TypeCombinator::intersect($assignedExprType->toCoercedArgumentType(true), $propertyNativeType), TypeCombinator::intersect($assignedNativeType->toCoercedArgumentType(true), $propertyNativeType)); + } elseif ($propertyNativeType->isNull()->no()) { $assignedExprType = TypeCombinator::removeNull($assignedExprType); $assignedNativeType = TypeCombinator::removeNull($assignedNativeType); - } - if ($scope->isDeclareStrictTypes()) { - $scope = $scope->assignExpression($var, TypeCombinator::intersect($assignedExprType->toCoercedArgumentType(true), $propertyNativeType), TypeCombinator::intersect($assignedNativeType->toCoercedArgumentType(true), $propertyNativeType)); + $scope = $scope->assignExpression($var, $assignedExprType, $assignedNativeType); } else { $scope = $scope->assignExpression($var, $assignedExprType, $assignedNativeType); } @@ -5710,13 +5710,13 @@ static function (): void { $propertyNativeType = $propertyReflection->getNativeType(); if ($propertyReflection->hasNativeType()) { - if ($propertyNativeType->isNull()->no()) { + if ($scope->isDeclareStrictTypes()) { + $scope = $scope->assignExpression($var, TypeCombinator::intersect($assignedExprType->toCoercedArgumentType(true), $propertyNativeType), TypeCombinator::intersect($assignedNativeType->toCoercedArgumentType(true), $propertyNativeType)); + } elseif ($propertyNativeType->isNull()->no()) { $assignedExprType = TypeCombinator::removeNull($assignedExprType); $assignedNativeType = TypeCombinator::removeNull($assignedNativeType); - } - if ($scope->isDeclareStrictTypes()) { - $scope = $scope->assignExpression($var, TypeCombinator::intersect($assignedExprType->toCoercedArgumentType(true), $propertyNativeType), TypeCombinator::intersect($assignedNativeType->toCoercedArgumentType(true), $propertyNativeType)); + $scope = $scope->assignExpression($var, $assignedExprType, $assignedNativeType); } else { $scope = $scope->assignExpression($var, $assignedExprType, $assignedNativeType); } From 938ed5c8d9a1206bb4f65aa657675286fa84ecea Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 18 Apr 2025 11:07:48 +0200 Subject: [PATCH 11/13] Delete tests/PHPStan/Analyser/nsrt/remember-non-nullable-property.php --- .../nsrt/remember-non-nullable-property.php | 24 ------------------- 1 file changed, 24 deletions(-) delete mode 100644 tests/PHPStan/Analyser/nsrt/remember-non-nullable-property.php diff --git a/tests/PHPStan/Analyser/nsrt/remember-non-nullable-property.php b/tests/PHPStan/Analyser/nsrt/remember-non-nullable-property.php deleted file mode 100644 index ca2fc557b9..0000000000 --- a/tests/PHPStan/Analyser/nsrt/remember-non-nullable-property.php +++ /dev/null @@ -1,24 +0,0 @@ -= 8.1 - -namespace RememberNonNullableProperty; - -use function PHPStan\Testing\assertNativeType; -use function PHPStan\Testing\assertType; - -class DoesntNarrowNativeUnion { - private readonly int|float $i; - - public function __construct() - { - $this->i = getInt(); - } - - public function doFoo(): void { - assertType('float|int', $this->i); - assertNativeType('float|int', $this->i); - } -} - -function getInt(): int { - return 1; -} From f1be00989028b6fd7e3a7de5a12258a3ce7fb265 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 18 Apr 2025 11:24:56 +0200 Subject: [PATCH 12/13] TypeInferenceTestCase: support declare(); before // lint --- src/Testing/TypeInferenceTestCase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Testing/TypeInferenceTestCase.php b/src/Testing/TypeInferenceTestCase.php index 0d7e0306d1..e45110b51c 100644 --- a/src/Testing/TypeInferenceTestCase.php +++ b/src/Testing/TypeInferenceTestCase.php @@ -398,7 +398,7 @@ private static function isFileLintSkipped(string $file): bool @fclose($f); - if (preg_match('~ Date: Fri, 18 Apr 2025 11:29:37 +0200 Subject: [PATCH 13/13] Delete remember-non-nullable-property.php --- remember-non-nullable-property.php | 100 ----------------------------- 1 file changed, 100 deletions(-) delete mode 100644 remember-non-nullable-property.php diff --git a/remember-non-nullable-property.php b/remember-non-nullable-property.php deleted file mode 100644 index 5c48b935c1..0000000000 --- a/remember-non-nullable-property.php +++ /dev/null @@ -1,100 +0,0 @@ -= 8.1 - -namespace RememberNonNullablePropertyWhenStrictTypesDisabled; - -use function PHPStan\Testing\assertNativeType; -use function PHPStan\Testing\assertType; - -class NarrowsNativeUnion { - private readonly int|float $i; - - public function __construct() - { - $this->i = getInt(); - } - - public function doFoo(): void { - assertType('int', $this->i); - assertNativeType('int', $this->i); - } -} - -class KeepsPropertyNonNullable { - private readonly int $i; - - public function __construct() - { - $this->i = getIntOrNull(); - } - - public function doFoo(): void { - assertType('int', $this->i); - assertNativeType('int', $this->i); - } -} - -class DontCoercePhpdocType { - /** @var int */ - private $i; - - public function __construct() - { - $this->i = getIntOrNull(); - } - - public function doFoo(): void { - assertType('int', $this->i); - assertNativeType('mixed', $this->i); - } -} - -function getIntOrNull(): ?int { - if (rand(0, 1) === 0) { - return null; - } - return 1; -} - - -class KeepsPropertyNonNullable2 { - private int|float $i; - - public function __construct() - { - $this->i = getIntOrFloatOrNull(); - } - - public function doFoo(): void { - assertType('float|int', $this->i); - assertNativeType('float|int', $this->i); - } -} - -function getIntOrFloatOrNull(): null|int|float { - if (rand(0, 1) === 0) { - return null; - } - - if (rand(0, 10) === 0) { - return 1.0; - } - return 1; -} - -class NarrowsNativeUnion { - private readonly int|float $i; - - public function __construct() - { - $this->i = getInt(); - } - - public function doFoo(): void { - assertType('int', $this->i); - assertNativeType('int', $this->i); - } -} - -function getInt(): int { - return 1; -}