diff --git a/src/Reflection/Annotations/AnnotationMethodReflection.php b/src/Reflection/Annotations/AnnotationMethodReflection.php index 6a50a0ce9c..b24b8ae7d7 100644 --- a/src/Reflection/Annotations/AnnotationMethodReflection.php +++ b/src/Reflection/Annotations/AnnotationMethodReflection.php @@ -10,6 +10,7 @@ use PHPStan\TrinaryLogic; use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\MixedType; +use PHPStan\Type\ThisType; use PHPStan\Type\Type; class AnnotationMethodReflection implements ExtendedMethodReflection @@ -122,6 +123,10 @@ public function hasSideEffects(): TrinaryLogic return TrinaryLogic::createYes(); } + if ((new ThisType($this->declaringClass))->isSuperTypeOf($this->returnType)->yes()) { + return TrinaryLogic::createYes(); + } + return TrinaryLogic::createMaybe(); } diff --git a/src/Reflection/Php/PhpMethodReflection.php b/src/Reflection/Php/PhpMethodReflection.php index b374e9e6f7..6ef5a0c4a1 100644 --- a/src/Reflection/Php/PhpMethodReflection.php +++ b/src/Reflection/Php/PhpMethodReflection.php @@ -30,6 +30,7 @@ use PHPStan\Type\MixedType; use PHPStan\Type\ObjectWithoutClassType; use PHPStan\Type\StringType; +use PHPStan\Type\ThisType; use PHPStan\Type\Type; use PHPStan\Type\TypehintHelper; use PHPStan\Type\VoidType; @@ -430,6 +431,10 @@ public function hasSideEffects(): TrinaryLogic return TrinaryLogic::createFromBoolean(!$this->isPure); } + if ((new ThisType($this->declaringClass))->isSuperTypeOf($this->getReturnType())->yes()) { + return TrinaryLogic::createYes(); + } + return TrinaryLogic::createMaybe(); } diff --git a/tests/PHPStan/Analyser/data/impure-method.php b/tests/PHPStan/Analyser/data/impure-method.php index d471c0d54a..70a7dcb592 100644 --- a/tests/PHPStan/Analyser/data/impure-method.php +++ b/tests/PHPStan/Analyser/data/impure-method.php @@ -4,6 +4,9 @@ use function PHPStan\Testing\assertType; +/** + * @method $this phpDocReturnThis() + */ class Foo { @@ -15,6 +18,14 @@ public function voidMethod(): void $this->fooProp = rand(0, 1); } + /** + * @return $this + */ + public function returnsThis() + { + $this->fooProp = rand(0, 1); + } + public function ordinaryMethod(): int { return 1; @@ -51,6 +62,24 @@ public function doFoo(): void assertType('int', $this->fooProp); } + public function doFluent(): void + { + $this->fooProp = 1; + assertType('1', $this->fooProp); + + $this->returnsThis(); + assertType('int', $this->fooProp); + } + + public function doFluent2(): void + { + $this->fooProp = 1; + assertType('1', $this->fooProp); + + $this->phpDocReturnThis(); + assertType('int', $this->fooProp); + } + public function doBar(): void { $this->fooProp = 1;