From 80b319cfcb094ae6c56ab7e5535577d93d0e6a50 Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Tue, 21 Nov 2023 14:38:14 +0100 Subject: [PATCH 1/2] DynamicReturnTypeExtensions: allow hooking on object --- .../DynamicReturnTypeExtensionRegistry.php | 2 +- .../data/TestDynamicReturnTypeExtensions.php | 21 +++++++++++++++++++ .../data/dynamic-method-return-types.php | 10 +++++++++ .../PHPStan/Analyser/dynamic-return-type.neon | 4 ++++ 4 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/Type/DynamicReturnTypeExtensionRegistry.php b/src/Type/DynamicReturnTypeExtensionRegistry.php index ea3c27a13c..cda990a726 100644 --- a/src/Type/DynamicReturnTypeExtensionRegistry.php +++ b/src/Type/DynamicReturnTypeExtensionRegistry.php @@ -83,7 +83,7 @@ private function getDynamicExtensionsForType(array $extensions, string $classNam $extensionsForClass = [[]]; $class = $this->reflectionProvider->getClass($className); - foreach (array_merge([$className], $class->getParentClassesNames(), $class->getNativeReflection()->getInterfaceNames()) as $extensionClassName) { + foreach (array_merge([$className, 'object'], $class->getParentClassesNames(), $class->getNativeReflection()->getInterfaceNames()) as $extensionClassName) { $extensionClassName = strtolower($extensionClassName); if (!isset($extensions[$extensionClassName])) { continue; diff --git a/tests/PHPStan/Analyser/data/TestDynamicReturnTypeExtensions.php b/tests/PHPStan/Analyser/data/TestDynamicReturnTypeExtensions.php index 446e478cd0..69a489dca4 100644 --- a/tests/PHPStan/Analyser/data/TestDynamicReturnTypeExtensions.php +++ b/tests/PHPStan/Analyser/data/TestDynamicReturnTypeExtensions.php @@ -13,6 +13,7 @@ use PHPStan\Reflection\ResolvedMethodReflection; use PHPStan\Reflection\Type\CalledOnTypeUnresolvedMethodPrototypeReflection; use PHPStan\Reflection\Type\UnionTypeMethodReflection; +use PHPStan\Type\BooleanType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\DynamicMethodReturnTypeExtension; use PHPStan\Type\DynamicStaticMethodReturnTypeExtension; @@ -263,3 +264,23 @@ public function getTypeFromStaticMethodCall( return $scope->getType(new New_($methodCall->class)); } } + + +class ObjectHookedDynamicStaticMethodReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension { + + public function getClass(): string + { + return 'object'; + } + + public function isStaticMethodSupported(MethodReflection $methodReflection): bool + { + return $methodReflection->getName() === 'methodReturningBoolNoMatterTheCaller'; + } + + public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): Type + { + return new BooleanType(); + } + +} diff --git a/tests/PHPStan/Analyser/data/dynamic-method-return-types.php b/tests/PHPStan/Analyser/data/dynamic-method-return-types.php index 0fb7c70d6d..5f74589e87 100644 --- a/tests/PHPStan/Analyser/data/dynamic-method-return-types.php +++ b/tests/PHPStan/Analyser/data/dynamic-method-return-types.php @@ -88,6 +88,8 @@ public function doFoo() assertType('DynamicMethodReturnTypesNamespace\Foo', $container[\DynamicMethodReturnTypesNamespace\Foo::class]); assertType('object', new \DynamicMethodReturnTypesNamespace\Foo()); assertType('object', new \DynamicMethodReturnTypesNamespace\FooWithoutConstructor()); + assertType('bool', \DynamicMethodReturnTypesNamespace\WhateverClass::methodReturningBoolNoMatterTheCaller()); + assertType('bool', \DynamicMethodReturnTypesNamespace\WhateverClass2::methodReturningBoolNoMatterTheCaller()); } } @@ -96,3 +98,11 @@ class FooWithoutConstructor { } + + +class WhateverClass { + public static function methodReturningBoolNoMatterTheCaller(){} +} +class WhateverClass2 { + public static function methodReturningBoolNoMatterTheCaller(){} +} diff --git a/tests/PHPStan/Analyser/dynamic-return-type.neon b/tests/PHPStan/Analyser/dynamic-return-type.neon index e80f2018a0..9bb986adaa 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\ObjectHookedDynamicStaticMethodReturnTypeExtension + tags: + - phpstan.broker.dynamicStaticMethodReturnTypeExtension From b29da6b82867f2e62a5e709d5712d4f1b83b14bc Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Wed, 22 Nov 2023 13:47:26 +0100 Subject: [PATCH 2/2] No magic object, null represents any class --- src/Type/DynamicMethodReturnTypeExtension.php | 2 +- src/Type/DynamicReturnTypeExtensionRegistry.php | 16 ++++++++++++---- .../DynamicStaticMethodReturnTypeExtension.php | 2 +- .../data/TestDynamicReturnTypeExtensions.php | 4 ++-- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/Type/DynamicMethodReturnTypeExtension.php b/src/Type/DynamicMethodReturnTypeExtension.php index 6d03b43f10..1ad46876ec 100644 --- a/src/Type/DynamicMethodReturnTypeExtension.php +++ b/src/Type/DynamicMethodReturnTypeExtension.php @@ -26,7 +26,7 @@ interface DynamicMethodReturnTypeExtension { - public function getClass(): string; + public function getClass(): ?string; public function isMethodSupported(MethodReflection $methodReflection): bool; diff --git a/src/Type/DynamicReturnTypeExtensionRegistry.php b/src/Type/DynamicReturnTypeExtensionRegistry.php index cda990a726..8b05823305 100644 --- a/src/Type/DynamicReturnTypeExtensionRegistry.php +++ b/src/Type/DynamicReturnTypeExtensionRegistry.php @@ -47,7 +47,7 @@ public function getDynamicMethodReturnTypeExtensionsForClass(string $className): if ($this->dynamicMethodReturnTypeExtensionsByClass === null) { $byClass = []; foreach ($this->dynamicMethodReturnTypeExtensions as $extension) { - $byClass[strtolower($extension->getClass())][] = $extension; + $byClass[$this->normalizeClassName($extension->getClass())][] = $extension; } $this->dynamicMethodReturnTypeExtensionsByClass = $byClass; @@ -63,7 +63,7 @@ public function getDynamicStaticMethodReturnTypeExtensionsForClass(string $class if ($this->dynamicStaticMethodReturnTypeExtensionsByClass === null) { $byClass = []; foreach ($this->dynamicStaticMethodReturnTypeExtensions as $extension) { - $byClass[strtolower($extension->getClass())][] = $extension; + $byClass[$this->normalizeClassName($extension->getClass())][] = $extension; } $this->dynamicStaticMethodReturnTypeExtensionsByClass = $byClass; @@ -83,8 +83,8 @@ private function getDynamicExtensionsForType(array $extensions, string $classNam $extensionsForClass = [[]]; $class = $this->reflectionProvider->getClass($className); - foreach (array_merge([$className, 'object'], $class->getParentClassesNames(), $class->getNativeReflection()->getInterfaceNames()) as $extensionClassName) { - $extensionClassName = strtolower($extensionClassName); + foreach (array_merge([$className, $this->normalizeClassName(null)], $class->getParentClassesNames(), $class->getNativeReflection()->getInterfaceNames()) as $extensionClassName) { + $extensionClassName = $this->normalizeClassName($extensionClassName); if (!isset($extensions[$extensionClassName])) { continue; } @@ -103,4 +103,12 @@ public function getDynamicFunctionReturnTypeExtensions(): array return $this->dynamicFunctionReturnTypeExtensions; } + private function normalizeClassName(?string $className): string + { + if ($className === null) { + return '0_any_class'; // such classname cannot ever exist + } + return strtolower($className); + } + } diff --git a/src/Type/DynamicStaticMethodReturnTypeExtension.php b/src/Type/DynamicStaticMethodReturnTypeExtension.php index 87b74c9af4..c4b281432a 100644 --- a/src/Type/DynamicStaticMethodReturnTypeExtension.php +++ b/src/Type/DynamicStaticMethodReturnTypeExtension.php @@ -26,7 +26,7 @@ interface DynamicStaticMethodReturnTypeExtension { - public function getClass(): string; + public function getClass(): ?string; public function isStaticMethodSupported(MethodReflection $methodReflection): bool; diff --git a/tests/PHPStan/Analyser/data/TestDynamicReturnTypeExtensions.php b/tests/PHPStan/Analyser/data/TestDynamicReturnTypeExtensions.php index 69a489dca4..3991befa7a 100644 --- a/tests/PHPStan/Analyser/data/TestDynamicReturnTypeExtensions.php +++ b/tests/PHPStan/Analyser/data/TestDynamicReturnTypeExtensions.php @@ -268,9 +268,9 @@ public function getTypeFromStaticMethodCall( class ObjectHookedDynamicStaticMethodReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension { - public function getClass(): string + public function getClass(): ?string { - return 'object'; + return null; } public function isStaticMethodSupported(MethodReflection $methodReflection): bool