From 7068e2245213a481078d24135094c1f925cff8c9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 28 Feb 2026 15:03:54 +0000 Subject: [PATCH 1/2] Fix conditional return type on $this not using subtracted type - When a method has a conditional return type like `@return ($this is self::A ? int : null)`, and `$this` has a subtracted type (e.g. `$this(X~X::A)` from narrowing), the conditional was not properly resolved because the subtracted type was not propagated - The root cause was in `StaticType::transformStaticType` which transforms `StaticType` instances in return types but did not propagate the calling type's subtracted type - Added subtracted type propagation in `StaticType::transformStaticType` to match the behavior of `CalledOnTypeUnresolvedMethodPrototypeReflection::transformStaticType` - New regression test in tests/PHPStan/Analyser/nsrt/bug-12244.php Closes https://github.com/phpstan/phpstan/issues/12244 --- src/Type/StaticType.php | 7 +++++ tests/PHPStan/Analyser/nsrt/bug-12244.php | 32 +++++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12244.php diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index 0243c96256..a7bd9932f2 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -369,6 +369,13 @@ private function transformStaticType(Type $type, ClassMemberAccessAnswerer $scop $type = new self($type->getClassReflection(), $type->getSubtractedType()); } + if ($this->getSubtractedType() !== null && $type->getSubtractedType() === null) { + $type = $type->changeSubtractedType($this->getSubtractedType()); + if (!$type instanceof StaticType) { + return $traverse($type); + } + } + if (!$isFinal || $type instanceof ThisType) { return $traverse($type); } diff --git a/tests/PHPStan/Analyser/nsrt/bug-12244.php b/tests/PHPStan/Analyser/nsrt/bug-12244.php new file mode 100644 index 0000000000..ba792e4625 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12244.php @@ -0,0 +1,32 @@ += 8.1 + +declare(strict_types = 1); + +namespace Bug12244; + +use function PHPStan\Testing\assertType; + +enum X: string { + case A = 'a'; + case B = 'b'; + case C = 'c'; + + /** @return ($this is self::A ? int : null) */ + public function get(): ?int { + return ($this === self::A) ? 123 : null; + } + + public function doSomething(): void { + if ($this !== self::A) { + assertType('$this(Bug12244\X~Bug12244\X::A)', $this); + assertType('null', $this->get()); + } + } + + public static function doSomethingFor(X $x): void { + if ($x !== self::A) { + assertType('Bug12244\X~Bug12244\X::A', $x); + assertType('null', $x->get()); + } + } +} From f8715871b88c858d4c2e3ac61980536c57b3e4bf Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sat, 28 Feb 2026 16:57:27 +0100 Subject: [PATCH 2/2] Simplify --- src/Type/StaticType.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index a7bd9932f2..2f1e27ee22 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -369,8 +369,8 @@ private function transformStaticType(Type $type, ClassMemberAccessAnswerer $scop $type = new self($type->getClassReflection(), $type->getSubtractedType()); } - if ($this->getSubtractedType() !== null && $type->getSubtractedType() === null) { - $type = $type->changeSubtractedType($this->getSubtractedType()); + if ($this->getSubtractedType() !== null) { + $type = $type->subtract($this->getSubtractedType()); if (!$type instanceof StaticType) { return $traverse($type); }