diff --git a/src/Reflection/Dummy/ChangedTypeMethodReflection.php b/src/Reflection/Dummy/ChangedTypeMethodReflection.php index fc4b21656b..9a345c22cd 100644 --- a/src/Reflection/Dummy/ChangedTypeMethodReflection.php +++ b/src/Reflection/Dummy/ChangedTypeMethodReflection.php @@ -27,6 +27,7 @@ public function __construct( private ?array $namedArgumentsVariants, private ?Type $selfOutType, private ?Type $throwType, + private Assertions $assertions, ) { } @@ -133,7 +134,7 @@ public function hasSideEffects(): TrinaryLogic public function getAsserts(): Assertions { - return $this->reflection->getAsserts(); + return $this->assertions; } public function acceptsNamedArguments(): TrinaryLogic diff --git a/src/Reflection/Type/CallbackUnresolvedMethodPrototypeReflection.php b/src/Reflection/Type/CallbackUnresolvedMethodPrototypeReflection.php index 2874a465fd..1fba733364 100644 --- a/src/Reflection/Type/CallbackUnresolvedMethodPrototypeReflection.php +++ b/src/Reflection/Type/CallbackUnresolvedMethodPrototypeReflection.php @@ -137,6 +137,7 @@ private function transformMethodWithStaticType(ClassReflection $declaringClass, $namedArgumentVariants, $selfOutType, $throwType, + $method->getAsserts()->mapTypes($this->transformStaticTypeCallback), ); } diff --git a/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php b/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php index 44709d32ae..68d3200bec 100644 --- a/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php +++ b/src/Reflection/Type/CalledOnTypeUnresolvedMethodPrototypeReflection.php @@ -132,6 +132,7 @@ private function transformMethodWithStaticType(ClassReflection $declaringClass, $namedArgumentsVariants, $selfOutType, $throwType, + $method->getAsserts()->mapTypes(fn (Type $type): Type => $this->transformStaticType($type)), ); } diff --git a/tests/PHPStan/Analyser/nsrt/bug-12376.php b/tests/PHPStan/Analyser/nsrt/bug-12376.php new file mode 100644 index 0000000000..a37fe35b91 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12376.php @@ -0,0 +1,66 @@ + $class + * @return T + */ +function newNonFinalInstance(string $class): object +{ + return new $class(); +} + +class Base {} + +class A extends Base +{ + /** + * @template T of object + * @param T $object + * @return (T is static ? T : static) + * + * @phpstan-assert static $object + */ + public static function assertInstanceOf(object $object) + { + if (!$object instanceof static) { + throw new \Exception(); + } + + return $object; + } +} + +class B extends A {} +class C extends Base {} + +$o = newNonFinalInstance(\DateTime::class); +$r = A::assertInstanceOf($o); +assertType('*NEVER*', $o); +assertType('Bug12376\A', $r); + +$o = newNonFinalInstance(A::class); +$r = A::assertInstanceOf($o); +assertType('Bug12376\A', $o); +assertType('Bug12376\A', $r); + +$o = newNonFinalInstance(B::class); +$r = A::assertInstanceOf($o); +assertType('Bug12376\B', $o); +assertType('Bug12376\B', $r); + +$o = newNonFinalInstance(C::class); +$r = A::assertInstanceOf($o); +assertType('*NEVER*', $o); +assertType('Bug12376\A', $r); + +$o = newNonFinalInstance(A::class); +$r = B::assertInstanceOf($o); +assertType('Bug12376\B', $o); +assertType('Bug12376\B', $r); diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 649a505d5b..73b0e4aee1 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -3608,6 +3608,16 @@ public function testBug9141(): void $this->analyse([__DIR__ . '/data/bug-9141.php'], []); } + public function testBug12548(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->checkExplicitMixed = true; + + $this->analyse([__DIR__ . '/data/bug-12548.php'], []); + } + public function testBug3589(): void { $this->checkThisOnly = false; diff --git a/tests/PHPStan/Rules/Methods/data/bug-12548.php b/tests/PHPStan/Rules/Methods/data/bug-12548.php new file mode 100644 index 0000000000..2ea2544bac --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-12548.php @@ -0,0 +1,86 @@ +createStdSat(); + $o->foo(); // @phpstan-ignore method.nonObject (EXPECTED) + + $o = $this->createStdSat(); + if (StdSat::assertInstanceOf($o)) { + $o->foo(); + } + + $o = $this->createStdSat(); + StdSat::assertInstanceOf2($o); + $o->foo(); + + $o = $this->createStdSat(); + StdSat::assertInstanceOf3($o); + $o->foo(); + } +} diff --git a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php index 11abea2f2d..d6239c8c27 100644 --- a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php @@ -1165,6 +1165,14 @@ public function testBug12645(): void ]); } + public function testBug11289(): void + { + $this->checkThisOnly = false; + $this->checkUnionTypes = true; + $this->checkDynamicProperties = false; + $this->analyse([__DIR__ . '/data/bug-11289.php'], []); + } + public function testBug8668(): void { $this->checkThisOnly = false; diff --git a/tests/PHPStan/Rules/Properties/data/bug-11289.php b/tests/PHPStan/Rules/Properties/data/bug-11289.php new file mode 100644 index 0000000000..89ac44c02a --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-11289.php @@ -0,0 +1,37 @@ += 8.0 + +declare(strict_types = 1); + +namespace Bug11289; + +abstract class SomeAbstractClass +{ + private bool $someValue = true; + + /** + * @phpstan-assert-if-true =static $other + */ + public function equals(?self $other): bool + { + return $other instanceof static + && $this->someValue === $other->someValue; + } +} + +class SomeConcreteClass extends SomeAbstractClass +{ + public function __construct( + private bool $someOtherValue, + ) {} + + public function equals(?SomeAbstractClass $other): bool + { + return parent::equals($other) + && $this->someOtherValue === $other->someOtherValue; + } +} + +$a = new SomeConcreteClass(true); +$b = new SomeConcreteClass(false); + +var_dump($a->equals($b), $b->equals($b));