From aa38695f3790b4984e0d60301f4b0ef32eb9578f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 21 Jul 2020 22:07:25 +0200 Subject: [PATCH] `@mixin` above classes with `__callStatic()` creates static methods --- .../Mixin/MixinMethodReflection.php | 94 +++++++++++++++++++ .../MixinMethodsClassReflectionExtension.php | 12 ++- .../Methods/CallStaticMethodsRuleTest.php | 11 +++ tests/PHPStan/Rules/Methods/data/bug-3641.php | 33 +++++++ 4 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 src/Reflection/Mixin/MixinMethodReflection.php create mode 100644 tests/PHPStan/Rules/Methods/data/bug-3641.php diff --git a/src/Reflection/Mixin/MixinMethodReflection.php b/src/Reflection/Mixin/MixinMethodReflection.php new file mode 100644 index 0000000000..544e6fc5f7 --- /dev/null +++ b/src/Reflection/Mixin/MixinMethodReflection.php @@ -0,0 +1,94 @@ +reflection = $reflection; + $this->static = $static; + } + + public function getDeclaringClass(): ClassReflection + { + return $this->reflection->getDeclaringClass(); + } + + public function isStatic(): bool + { + return $this->static; + } + + public function isPrivate(): bool + { + return $this->reflection->isPrivate(); + } + + public function isPublic(): bool + { + return $this->reflection->isPublic(); + } + + public function getDocComment(): ?string + { + return $this->reflection->getDocComment(); + } + + public function getName(): string + { + return $this->reflection->getName(); + } + + public function getPrototype(): ClassMemberReflection + { + return $this->reflection->getPrototype(); + } + + public function getVariants(): array + { + return $this->reflection->getVariants(); + } + + public function isDeprecated(): TrinaryLogic + { + return $this->reflection->isDeprecated(); + } + + public function getDeprecatedDescription(): ?string + { + return $this->reflection->getDeprecatedDescription(); + } + + public function isFinal(): TrinaryLogic + { + return $this->reflection->isFinal(); + } + + public function isInternal(): TrinaryLogic + { + return $this->reflection->isInternal(); + } + + public function getThrowType(): ?Type + { + return $this->reflection->getThrowType(); + } + + public function hasSideEffects(): TrinaryLogic + { + return $this->reflection->hasSideEffects(); + } + +} diff --git a/src/Reflection/Mixin/MixinMethodsClassReflectionExtension.php b/src/Reflection/Mixin/MixinMethodsClassReflectionExtension.php index 43abed6e2c..f5af02cc28 100644 --- a/src/Reflection/Mixin/MixinMethodsClassReflectionExtension.php +++ b/src/Reflection/Mixin/MixinMethodsClassReflectionExtension.php @@ -49,7 +49,17 @@ private function findMethod(ClassReflection $classReflection, string $methodName continue; } - return $type->getMethod($methodName, new OutOfClassScope()); + $method = $type->getMethod($methodName, new OutOfClassScope()); + $static = $method->isStatic(); + if ( + !$static + && $classReflection->hasNativeMethod('__callStatic') + && !$classReflection->hasNativeMethod('__call') + ) { + $static = true; + } + + return new MixinMethodReflection($method, $static); } foreach ($classReflection->getParents() as $parentClass) { diff --git a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php index 74a04b5fc3..ef1a70548d 100644 --- a/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallStaticMethodsRuleTest.php @@ -362,4 +362,15 @@ public function testBug3448(): void ]); } + public function testBug3641(): void + { + $this->checkThisOnly = false; + $this->analyse([__DIR__ . '/data/bug-3641.php'], [ + [ + 'Static method Bug3641\Foo::bar() invoked with 1 parameter, 0 required.', + 32, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-3641.php b/tests/PHPStan/Rules/Methods/data/bug-3641.php new file mode 100644 index 0000000000..8480c30ac6 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-3641.php @@ -0,0 +1,33 @@ +$method(...$args); + } +} + +function (): void { + Bar::bar(); + Bar::bar(1); +};