From f59887ed31298759cba4bb83927371c40362db72 Mon Sep 17 00:00:00 2001 From: HypeMC Date: Tue, 3 Feb 2026 22:05:23 +0100 Subject: [PATCH] Fix invokable objects incorrectly labeled as instances of `Closure` --- src/Type/Accessory/HasMethodType.php | 8 +++++- .../Classes/ImpossibleInstanceOfRuleTest.php | 13 ++++++++++ .../Rules/Classes/data/bug-13975-1.php | 18 +++++++++++++ .../Rules/Classes/data/bug-13975-2.php | 25 +++++++++++++++++++ 4 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/Classes/data/bug-13975-1.php create mode 100644 tests/PHPStan/Rules/Classes/data/bug-13975-2.php diff --git a/src/Type/Accessory/HasMethodType.php b/src/Type/Accessory/HasMethodType.php index 66d8365595..132cd36c33 100644 --- a/src/Type/Accessory/HasMethodType.php +++ b/src/Type/Accessory/HasMethodType.php @@ -2,6 +2,7 @@ namespace PHPStan\Type\Accessory; +use Closure; use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\Reflection\ClassMemberAccessAnswerer; @@ -17,6 +18,7 @@ use PHPStan\Type\ErrorType; use PHPStan\Type\IntersectionType; use PHPStan\Type\IsSuperTypeOfResult; +use PHPStan\Type\ObjectType; use PHPStan\Type\StringType; use PHPStan\Type\Traits\NonGeneralizableTypeTrait; use PHPStan\Type\Traits\NonGenericTypeTrait; @@ -83,7 +85,11 @@ public function isSubTypeOf(Type $otherType): IsSuperTypeOfResult return $otherType->isSuperTypeOf($this); } - if ($this->isCallable()->yes() && $otherType->isCallable()->yes()) { + if ( + $this->isCallable()->yes() + && $otherType->isCallable()->yes() + && !(new ObjectType(Closure::class))->isSuperTypeOf($otherType)->yes() + ) { return IsSuperTypeOfResult::createYes(); } diff --git a/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php b/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php index 5fb37f5340..9f76c85b25 100644 --- a/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php +++ b/tests/PHPStan/Rules/Classes/ImpossibleInstanceOfRuleTest.php @@ -569,4 +569,17 @@ public function testBug13469(): void ]); } + public static function dataBug13975(): iterable + { + yield [__DIR__ . '/data/bug-13975-1.php']; + yield [__DIR__ . '/data/bug-13975-2.php']; + } + + #[DataProvider('dataBug13975')] + public function testBug13975(string $file): void + { + $this->treatPhpDocTypesAsCertain = true; + $this->analyse([$file], []); + } + } diff --git a/tests/PHPStan/Rules/Classes/data/bug-13975-1.php b/tests/PHPStan/Rules/Classes/data/bug-13975-1.php new file mode 100644 index 0000000000..d218a1c380 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/bug-13975-1.php @@ -0,0 +1,18 @@ +