diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 1662d1fa9a..ee382d508b 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1040,11 +1040,6 @@ parameters: count: 3 path: src/Type/Generic/TemplateIntersectionType.php - - - message: "#^Instanceof between PHPStan\\\\Type\\\\Type and PHPStan\\\\Type\\\\IntersectionType will always evaluate to false\\.$#" - count: 2 - path: src/Type/Generic/TemplateIntersectionType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\IntersectionType is error\\-prone and deprecated\\.$#" count: 3 @@ -1145,11 +1140,6 @@ parameters: count: 3 path: src/Type/Generic/TemplateUnionType.php - - - message: "#^Instanceof between PHPStan\\\\Type\\\\Type and PHPStan\\\\Type\\\\UnionType will always evaluate to false\\.$#" - count: 2 - path: src/Type/Generic/TemplateUnionType.php - - message: "#^Doing instanceof PHPStan\\\\Type\\\\ConstantScalarType is error\\-prone and deprecated\\. Use Type\\:\\:isConstantScalarValue\\(\\) or Type\\:\\:getConstantScalarTypes\\(\\) or Type\\:\\:getConstantScalarValues\\(\\) instead\\.$#" count: 1 diff --git a/src/Analyser/TypeSpecifier.php b/src/Analyser/TypeSpecifier.php index 088236bac5..f7d01f2da6 100644 --- a/src/Analyser/TypeSpecifier.php +++ b/src/Analyser/TypeSpecifier.php @@ -156,14 +156,17 @@ public function specifyTypesInCondition( } $classType = $scope->getType($expr->class); - $type = TypeTraverser::map($classType, static function (Type $type, callable $traverse): Type { + $uncertainty = false; + $type = TypeTraverser::map($classType, static function (Type $type, callable $traverse) use (&$uncertainty): Type { if ($type instanceof UnionType || $type instanceof IntersectionType) { return $traverse($type); } if ($type->getObjectClassNames() !== []) { + $uncertainty = true; return $type; } if ($type instanceof GenericClassStringType) { + $uncertainty = true; return $type->getGenericType(); } if ($type instanceof ConstantStringType) { @@ -179,7 +182,7 @@ public function specifyTypesInCondition( new ObjectWithoutClassType(), ); return $this->create($exprNode, $type, $context, false, $scope, $rootExpr); - } elseif ($context->false()) { + } elseif ($context->false() && !$uncertainty) { $exprType = $scope->getType($expr->expr); if (!$type->isSuperTypeOf($exprType)->yes()) { return $this->create($exprNode, $type, $context, false, $scope, $rootExpr); diff --git a/tests/PHPStan/Analyser/nsrt/bug-12107.php b/tests/PHPStan/Analyser/nsrt/bug-12107.php new file mode 100644 index 0000000000..1a2c839c05 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12107.php @@ -0,0 +1,43 @@ + $e2 */ + public function sayHello2(Throwable $e1, string $e2): void + { + if ($e1 instanceof $e2) { + return; + } + + + assertType('Throwable', $e1); + assertType('bool', $e1 instanceof $e2); // could be false + } + + public function sayHello3(Throwable $e1): void + { + if ($e1 instanceof LogicException) { + return; + } + + assertType('Throwable~LogicException', $e1); + assertType('false', $e1 instanceof LogicException); + } +} diff --git a/tests/PHPStan/Analyser/nsrt/instanceof-class-string.php b/tests/PHPStan/Analyser/nsrt/instanceof-class-string.php index dc2bcaa2f3..22c58c4b20 100644 --- a/tests/PHPStan/Analyser/nsrt/instanceof-class-string.php +++ b/tests/PHPStan/Analyser/nsrt/instanceof-class-string.php @@ -32,7 +32,7 @@ public function doBar(Foo $foo, Bar $bar): void if ($foo instanceof $class) { assertType(self::class, $foo); } else { - assertType('InstanceOfClassString\Foo~InstanceOfClassString\Bar', $foo); + assertType('InstanceOfClassString\Foo', $foo); } } diff --git a/tests/PHPStan/Analyser/nsrt/instanceof.php b/tests/PHPStan/Analyser/nsrt/instanceof.php index 098b74cb47..9ad5cceea7 100644 --- a/tests/PHPStan/Analyser/nsrt/instanceof.php +++ b/tests/PHPStan/Analyser/nsrt/instanceof.php @@ -80,9 +80,9 @@ public function testExprInstanceof($subject, string $classString, $union, $inter assertType('true', $subject instanceof Foo); assertType('bool', $subject instanceof $classString); } else { - assertType('mixed~InstanceOfNamespace\Foo', $subject); - assertType('false', $subject instanceof Foo); - assertType('false', $subject instanceof $classString); + assertType('mixed', $subject); + assertType('bool', $subject instanceof Foo); + assertType('bool', $subject instanceof $classString); // could be false } $constantString = 'InstanceOfNamespace\BarParent'; @@ -132,7 +132,7 @@ public function testExprInstanceof($subject, string $classString, $union, $inter assertType('ObjectT of InstanceOfNamespace\BarInterface (method InstanceOfNamespace\Foo::testExprInstanceof(), argument)', $subject); assertType('bool', $subject instanceof $objectT); } else { - assertType('mixed~ObjectT of InstanceOfNamespace\BarInterface (method InstanceOfNamespace\Foo::testExprInstanceof(), argument)', $subject); + assertType('mixed', $subject); assertType('bool', $subject instanceof $objectT); // can be false } @@ -140,7 +140,7 @@ public function testExprInstanceof($subject, string $classString, $union, $inter assertType('ObjectT of InstanceOfNamespace\BarInterface (method InstanceOfNamespace\Foo::testExprInstanceof(), argument)', $subject); assertType('bool', $subject instanceof $objectTString); } else { - assertType('mixed~ObjectT of InstanceOfNamespace\BarInterface (method InstanceOfNamespace\Foo::testExprInstanceof(), argument)', $subject); + assertType('mixed', $subject); assertType('bool', $subject instanceof $objectTString); // can be false } @@ -148,7 +148,7 @@ public function testExprInstanceof($subject, string $classString, $union, $inter assertType('MixedT (method InstanceOfNamespace\Foo::testExprInstanceof(), argument)&object', $subject); assertType('bool', $subject instanceof $mixedTString); } else { - assertType('mixed~MixedT (method InstanceOfNamespace\Foo::testExprInstanceof(), argument)', $subject); + assertType('mixed', $subject); assertType('bool', $subject instanceof $mixedTString); // can be false } @@ -180,8 +180,8 @@ public function testExprInstanceof($subject, string $classString, $union, $inter assertType('InstanceOfNamespace\Foo', $object); assertType('bool', $object instanceof $classString); } else { - assertType('object~InstanceOfNamespace\Foo', $object); - assertType('false', $object instanceof $classString); + assertType('object', $object); + assertType('bool', $object instanceof $classString); // could be false } if ($instance instanceof $string) { diff --git a/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php b/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php index fde28cab1a..5382491c02 100644 --- a/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php @@ -167,11 +167,6 @@ public function testInstanceof(): void 388, $tipText, ], - [ - 'Instanceof between T of Exception and Error will always evaluate to false.', - 404, - $tipText, - ], [ 'Instanceof between class-string and DateTimeInterface will always evaluate to false.', 418, @@ -270,11 +265,6 @@ public function testInstanceofWithoutAlwaysTrue(): void 'Instanceof between mixed and ImpossibleInstanceOf\InvalidTypeTest|int results in an error.', 362, ],*/ - [ - 'Instanceof between T of Exception and Error will always evaluate to false.', - 404, - $tipText, - ], [ 'Instanceof between class-string and DateTimeInterface will always evaluate to false.', 418,