Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/Type/StaticType.php
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,13 @@ private function transformStaticType(Type $type, ClassMemberAccessAnswerer $scop
$type = new self($type->getClassReflection(), $type->getSubtractedType());
}

if ($this->getSubtractedType() !== null) {
Copy link
Contributor

@staabm staabm Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will this also make a difference for param-out?

https://phpstan.org/r/e53128e5-21a6-4b35-88ba-868a23a28dca

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool - please add a test

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

$type = $type->subtract($this->getSubtractedType());
if (!$type instanceof StaticType) {
return $traverse($type);
}
}

if (!$isFinal || $type instanceof ThisType) {
return $traverse($type);
}
Expand Down
77 changes: 77 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-12244.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php // lint >= 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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it make sense to test a $x !== self::B && $x !== self::C could make get return 123 ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

assertType('$this(Bug12244\X~Bug12244\X::A)', $this);
assertType('null', $this->get());
} else {
assertType('int', $this->get());
}

if ($this !== self::B && $this !== self::C) {
assertType('int', $this->get());
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add else branch with assertions for all the expressions tested in the if-branch?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

}

public static function doSomethingFor(X $x): void {
if ($x !== self::A) {
assertType('Bug12244\X~Bug12244\X::A', $x);
assertType('null', $x->get());
} else {
assertType('int', $x->get());
}

if ($x !== self::B && $x !== self::C) {
assertType('int', $x->get());
}
}
}

enum Y: string {
case A = 'a';
case B = 'b';
case C = 'c';

/** @param-out ($this is self::A ? int : null) $i */
public function get(?int &$i): void {
($this === self::A) ? $i=null : $i=123;
}

public function doSomething(): void {
$i = 0;
if ($this !== self::A) {
$this->get($i);
assertType('null', $i); // null
} else {
$this->get($i);
assertType('int', $i); // int
}
}

public static function doSomethingFor(Y $x): void {
$i = 0;
if ($x !== self::A) {
$x->get($i);
assertType('null', $i); // null
} else {
$x->get($i);
assertType('int', $i); // int
}
}
}
Loading