From 3b51f22f96c56079d08c22b6b062e636818d59ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Thu, 31 Jul 2025 11:16:04 +0200 Subject: [PATCH 1/4] Add test for phpstan issue 7396 --- ...icReturnTypeExtensionTypeInferenceTest.php | 1 + .../data/TestDynamicReturnTypeExtensions.php | 36 +++++++++++++++++++ tests/PHPStan/Analyser/data/bug-7385.php | 24 +++++++++++++ .../PHPStan/Analyser/dynamic-return-type.neon | 4 +++ 4 files changed, 65 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/bug-7385.php diff --git a/tests/PHPStan/Analyser/DynamicReturnTypeExtensionTypeInferenceTest.php b/tests/PHPStan/Analyser/DynamicReturnTypeExtensionTypeInferenceTest.php index d336534264..2f199271ab 100644 --- a/tests/PHPStan/Analyser/DynamicReturnTypeExtensionTypeInferenceTest.php +++ b/tests/PHPStan/Analyser/DynamicReturnTypeExtensionTypeInferenceTest.php @@ -20,6 +20,7 @@ public static function dataAsserts(): iterable yield from self::gatherAssertTypes(__DIR__ . '/data/dynamic-method-return-compound-types.php'); yield from self::gatherAssertTypes(__DIR__ . '/data/bug-7344.php'); yield from self::gatherAssertTypes(__DIR__ . '/data/bug-7391b.php'); + yield from self::gatherAssertTypes(__DIR__ . '/data/bug-7385.php'); } /** diff --git a/tests/PHPStan/Analyser/data/TestDynamicReturnTypeExtensions.php b/tests/PHPStan/Analyser/data/TestDynamicReturnTypeExtensions.php index dd72c4525e..376802584e 100644 --- a/tests/PHPStan/Analyser/data/TestDynamicReturnTypeExtensions.php +++ b/tests/PHPStan/Analyser/data/TestDynamicReturnTypeExtensions.php @@ -7,6 +7,10 @@ use PhpParser\Node\Expr\New_; use PhpParser\Node\Expr\StaticCall; use PHPStan\Analyser\Scope; +use PHPStan\Analyser\SpecifiedTypes; +use PHPStan\Analyser\TypeSpecifier; +use PHPStan\Analyser\TypeSpecifierAwareExtension; +use PHPStan\Analyser\TypeSpecifierContext; use PHPStan\Reflection\Dummy\ChangedTypeMethodReflection; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\ParametersAcceptorSelector; @@ -18,6 +22,7 @@ use PHPStan\Type\DynamicStaticMethodReturnTypeExtension; use PHPStan\Type\IntegerType; use PHPStan\Type\IntersectionType; +use PHPStan\Type\MethodTypeSpecifyingExtension; use PHPStan\Type\NeverType; use PHPStan\Type\ObjectType; use PHPStan\Type\ObjectWithoutClassType; @@ -299,3 +304,34 @@ public function getTypeFromStaticMethodCall( return $scope->getType(new New_($methodCall->class)); } } + +class Bug7385MethodTypeSpecifyingExtension implements TypeSpecifierAwareExtension, MethodTypeSpecifyingExtension +{ + public function getClass(): string + { + return \Bug7385\Model::class; + } + + public function isMethodSupported(MethodReflection $methodReflection, ?MethodCall $methodCall = null, ?TypeSpecifierContext $context = null): bool + { + return $methodReflection->getName() === 'assertHasIface'; + } + + /** @var TypeSpecifier */ + protected $typeSpecifier; + + public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void + { + $this->typeSpecifier = $typeSpecifier; + } + + public function specifyTypes(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes + { + $type = TypeCombinator::intersect( + $scope->getType($methodCall->var), + new ObjectType(\Bug7385\Iface::class) + ); + + return $this->typeSpecifier->create($methodCall->var, $type, TypeSpecifierContext::createNull()); + } +} diff --git a/tests/PHPStan/Analyser/data/bug-7385.php b/tests/PHPStan/Analyser/data/bug-7385.php new file mode 100644 index 0000000000..0190b1e736 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-7385.php @@ -0,0 +1,24 @@ +assertHasIface(); + assertType('Bug7385\Model&Bug7391\Iface', $m); +}; diff --git a/tests/PHPStan/Analyser/dynamic-return-type.neon b/tests/PHPStan/Analyser/dynamic-return-type.neon index e80f2018a0..038a84da54 100644 --- a/tests/PHPStan/Analyser/dynamic-return-type.neon +++ b/tests/PHPStan/Analyser/dynamic-return-type.neon @@ -39,3 +39,7 @@ services: class: PHPStan\Tests\Bug7391BDynamicStaticMethodReturnTypeExtension tags: - phpstan.broker.dynamicStaticMethodReturnTypeExtension + - + class: PHPStan\Tests\Bug7385MethodTypeSpecifyingExtension + tags: + - phpstan.typeSpecifier.methodTypeSpecifyingExtension From d336f4319d1697f9daced49b27978bd12f34a978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Sat, 18 Jun 2022 13:11:02 +0200 Subject: [PATCH 2/4] TEST --- tests/PHPStan/Analyser/data/TestDynamicReturnTypeExtensions.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/PHPStan/Analyser/data/TestDynamicReturnTypeExtensions.php b/tests/PHPStan/Analyser/data/TestDynamicReturnTypeExtensions.php index 376802584e..1a2f3e4e9a 100644 --- a/tests/PHPStan/Analyser/data/TestDynamicReturnTypeExtensions.php +++ b/tests/PHPStan/Analyser/data/TestDynamicReturnTypeExtensions.php @@ -332,6 +332,8 @@ public function specifyTypes(MethodReflection $methodReflection, MethodCall $met new ObjectType(\Bug7385\Iface::class) ); + throw new \Error('reached: ' . $type->describe(\PHPStan\Type\VerbosityLevel::precise())); + return $this->typeSpecifier->create($methodCall->var, $type, TypeSpecifierContext::createNull()); } } From ee7c678d83e9fd32a2a096d04efa7eb0b232e00f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Sat, 18 Jun 2022 13:13:48 +0200 Subject: [PATCH 3/4] fix test --- tests/PHPStan/Analyser/data/bug-7385.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/data/bug-7385.php b/tests/PHPStan/Analyser/data/bug-7385.php index 0190b1e736..b41d53fbd2 100644 --- a/tests/PHPStan/Analyser/data/bug-7385.php +++ b/tests/PHPStan/Analyser/data/bug-7385.php @@ -20,5 +20,5 @@ function () { $m = random_int(0, 1) === 0 ? new Model() : new class() extends Model implements Iface {}; assertType('Bug7385\Model', $m); $m->assertHasIface(); - assertType('Bug7385\Model&Bug7391\Iface', $m); + assertType('Bug7385\Iface&Bug7385\Model', $m); }; From 4bdba0835f5f3091f06ff4840e39f3165abbd902 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Sat, 18 Jun 2022 13:11:02 +0200 Subject: [PATCH 4/4] Revert "TEST" --- tests/PHPStan/Analyser/data/TestDynamicReturnTypeExtensions.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/PHPStan/Analyser/data/TestDynamicReturnTypeExtensions.php b/tests/PHPStan/Analyser/data/TestDynamicReturnTypeExtensions.php index 1a2f3e4e9a..376802584e 100644 --- a/tests/PHPStan/Analyser/data/TestDynamicReturnTypeExtensions.php +++ b/tests/PHPStan/Analyser/data/TestDynamicReturnTypeExtensions.php @@ -332,8 +332,6 @@ public function specifyTypes(MethodReflection $methodReflection, MethodCall $met new ObjectType(\Bug7385\Iface::class) ); - throw new \Error('reached: ' . $type->describe(\PHPStan\Type\VerbosityLevel::precise())); - return $this->typeSpecifier->create($methodCall->var, $type, TypeSpecifierContext::createNull()); } }