From 32f6952bbe46d5387769838541d98ebbc0b11a96 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 17:16:37 +0000 Subject: [PATCH] Fix false positives for is_a() and similar checks on $this inside traits - In ImpossibleCheckTypeHelper::findSpecifiedType(), treat class-specific type checks on $this as uncertain when inside a trait, since $this is polymorphic across the different classes that use the trait - Added isThis() helper method for detecting $this expression references - New regression test in tests/PHPStan/Rules/Comparison/data/bug-13023.php Closes https://github.com/phpstan/phpstan/issues/13023 --- .../Comparison/ImpossibleCheckTypeHelper.php | 16 ++++++++++++ ...mpossibleCheckTypeFunctionCallRuleTest.php | 6 +++++ .../Rules/Comparison/data/bug-13023.php | 25 +++++++++++++++++++ 3 files changed, 47 insertions(+) create mode 100644 tests/PHPStan/Rules/Comparison/data/bug-13023.php diff --git a/src/Rules/Comparison/ImpossibleCheckTypeHelper.php b/src/Rules/Comparison/ImpossibleCheckTypeHelper.php index 1ebe569289..052a8feefb 100644 --- a/src/Rules/Comparison/ImpossibleCheckTypeHelper.php +++ b/src/Rules/Comparison/ImpossibleCheckTypeHelper.php @@ -299,6 +299,7 @@ public function findSpecifiedType( } } } + $isInTrait = $scope->isInTrait(); foreach ($sureTypes as $sureType) { if (self::isSpecified($typeSpecifierScope, $node, $sureType[0])) { $results[] = TrinaryLogic::createMaybe(); @@ -322,6 +323,11 @@ public function findSpecifiedType( $argumentType = $scope->getType($assignedInCallVar->expr); } + if ($isInTrait && self::isThis($sureType[0]) && $resultType->getObjectClassNames() !== []) { + $results[] = TrinaryLogic::createMaybe(); + continue; + } + $results[] = $resultType->isSuperTypeOf($argumentType)->result; } @@ -340,6 +346,11 @@ public function findSpecifiedType( /** @var Type $resultType */ $resultType = $sureNotType[1]; + if ($isInTrait && self::isThis($sureNotType[0]) && $resultType->getObjectClassNames() !== []) { + $results[] = TrinaryLogic::createMaybe(); + continue; + } + $results[] = $resultType->isSuperTypeOf($argumentType)->negate()->result; } @@ -376,6 +387,11 @@ private static function isSpecified(Scope $scope, Expr $node, Expr $expr): bool ) && $scope->hasExpressionType($expr)->yes(); } + private static function isThis(Expr $expr): bool + { + return $expr instanceof Expr\Variable && $expr->name === 'this'; + } + /** * @param Node\Arg[] $args */ diff --git a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php index 0a399fdc3c..535e7e945d 100644 --- a/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php +++ b/tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php @@ -1179,4 +1179,10 @@ public function testBug14177(): void $this->analyse([__DIR__ . '/data/bug-14177.php'], []); } + public function testBug13023(): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([__DIR__ . '/data/bug-13023.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Comparison/data/bug-13023.php b/tests/PHPStan/Rules/Comparison/data/bug-13023.php new file mode 100644 index 0000000000..dae307df47 --- /dev/null +++ b/tests/PHPStan/Rules/Comparison/data/bug-13023.php @@ -0,0 +1,25 @@ +