From f5e2e32932644d61b3745e3b0f2c0910f722a86d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 3 Sep 2024 17:31:22 +0200 Subject: [PATCH] Support `@mixin` above traits --- .../MixinMethodsClassReflectionExtension.php | 9 ++++ ...ixinPropertiesClassReflectionExtension.php | 9 ++++ .../Analyser/NodeScopeResolverTest.php | 2 + .../Rules/Methods/CallMethodsRuleTest.php | 9 ++++ .../Rules/Methods/data/trait-mixin.php | 44 ++++++++++++++++++ .../Properties/AccessPropertiesRuleTest.php | 8 ++++ .../Rules/Properties/data/trait-mixin.php | 45 +++++++++++++++++++ 7 files changed, 126 insertions(+) create mode 100644 tests/PHPStan/Rules/Methods/data/trait-mixin.php create mode 100644 tests/PHPStan/Rules/Properties/data/trait-mixin.php diff --git a/src/Reflection/Mixin/MixinMethodsClassReflectionExtension.php b/src/Reflection/Mixin/MixinMethodsClassReflectionExtension.php index 68ccf90ed9..58ae58ca2f 100644 --- a/src/Reflection/Mixin/MixinMethodsClassReflectionExtension.php +++ b/src/Reflection/Mixin/MixinMethodsClassReflectionExtension.php @@ -74,6 +74,15 @@ private function findMethod(ClassReflection $classReflection, string $methodName return new MixinMethodReflection($method, $static); } + foreach ($classReflection->getTraits() as $traitClass) { + $methodWithDeclaringClass = $this->findMethod($traitClass, $methodName); + if ($methodWithDeclaringClass === null) { + continue; + } + + return $methodWithDeclaringClass; + } + $parentClass = $classReflection->getParentClass(); while ($parentClass !== null) { $method = $this->findMethod($parentClass, $methodName); diff --git a/src/Reflection/Mixin/MixinPropertiesClassReflectionExtension.php b/src/Reflection/Mixin/MixinPropertiesClassReflectionExtension.php index 8e6d32054f..4b21f92451 100644 --- a/src/Reflection/Mixin/MixinPropertiesClassReflectionExtension.php +++ b/src/Reflection/Mixin/MixinPropertiesClassReflectionExtension.php @@ -65,6 +65,15 @@ private function findProperty(ClassReflection $classReflection, string $property return $property; } + foreach ($classReflection->getTraits() as $traitClass) { + $methodWithDeclaringClass = $this->findProperty($traitClass, $propertyName); + if ($methodWithDeclaringClass === null) { + continue; + } + + return $methodWithDeclaringClass; + } + $parentClass = $classReflection->getParentClass(); while ($parentClass !== null) { $property = $this->findProperty($parentClass, $propertyName); diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index f3e8929e16..ec6abc47e7 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -114,6 +114,8 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/conditional-complex-templates.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/bug-7511.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Properties/data/trait-mixin.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Methods/data/trait-mixin.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Comparison/data/bug-4708.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Functions/data/bug-7156.php'); yield from $this->gatherAssertTypes(__DIR__ . '/../Rules/Arrays/data/bug-6364.php'); diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index 255d9f9c02..3f29976a12 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -3345,4 +3345,13 @@ public function testNoNamedArguments(): void ]); } + public function testTraitMixin(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/trait-mixin.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/trait-mixin.php b/tests/PHPStan/Rules/Methods/data/trait-mixin.php new file mode 100644 index 0000000000..1aa5c3b428 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/trait-mixin.php @@ -0,0 +1,44 @@ + + */ +trait FooTrait +{ + +} + +class Usages +{ + + use FooTrait; + +} + +class ChildUsages extends Usages +{ + +} + +function (Usages $u): void { + assertType(Usages::class, $u->get()); +}; + +function (ChildUsages $u): void { + assertType(ChildUsages::class, $u->get()); +}; diff --git a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php index 7697f826e3..a07611980b 100644 --- a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php @@ -949,4 +949,12 @@ public function testBug9694(): void $this->analyse([__DIR__ . '/data/bug-9694.php'], []); } + public function testTraitMixin(): void + { + $this->checkThisOnly = false; + $this->checkUnionTypes = true; + $this->checkDynamicProperties = true; + $this->analyse([__DIR__ . '/data/trait-mixin.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/trait-mixin.php b/tests/PHPStan/Rules/Properties/data/trait-mixin.php new file mode 100644 index 0000000000..621a02e3f9 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/trait-mixin.php @@ -0,0 +1,45 @@ + + */ +trait FooTrait +{ + +} + +#[\AllowDynamicProperties] +class Usages +{ + + use FooTrait; + +} + +class ChildUsages extends Usages +{ + +} + +function (Usages $u): void { + assertType(Usages::class, $u->a); +}; + +function (ChildUsages $u): void { + assertType(ChildUsages::class, $u->a); +};