diff --git a/src/Extension/ImmediatelyCalledCallableThrowTypeExtension.php b/src/Extension/ImmediatelyCalledCallableThrowTypeExtension.php index d9d2938..7b07bd3 100644 --- a/src/Extension/ImmediatelyCalledCallableThrowTypeExtension.php +++ b/src/Extension/ImmediatelyCalledCallableThrowTypeExtension.php @@ -21,9 +21,12 @@ use PHPStan\Type\DynamicStaticMethodThrowTypeExtension; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; -use function array_keys; -use function in_array; +use function array_merge; +use function array_unique; +use function array_values; +use function explode; use function is_int; +use function strpos; class ImmediatelyCalledCallableThrowTypeExtension implements DynamicFunctionThrowTypeExtension, DynamicMethodThrowTypeExtension, DynamicStaticMethodThrowTypeExtension { @@ -75,11 +78,7 @@ public function isStaticMethodSupported(MethodReflection $methodReflection): boo */ private function isCallSupported(object $callReflection): bool { - return in_array( - $this->getCallNotation($callReflection), - array_keys($this->immediatelyCalledCallables), - true, - ); + return $this->getClosureArgumentPositions($callReflection) !== []; } public function getThrowTypeFromFunctionCall( @@ -197,28 +196,37 @@ static function (): void { */ private function getClosureArgumentPositions(object $callReflection): array { - $arguments = $this->immediatelyCalledCallables[$this->getCallNotation($callReflection)]; + if ($callReflection instanceof FunctionReflection) { + return $this->normalizeArgumentIndexes($this->immediatelyCalledCallables[$callReflection->getName()] ?? []); + } + + $argumentPositions = []; + $classReflection = $callReflection->getDeclaringClass(); + + foreach ($this->immediatelyCalledCallables as $immediateCallerAndMethod => $indexes) { + if (strpos($immediateCallerAndMethod, '::') === false) { + continue; + } + + [$callerClass, $methodName] = explode('::', $immediateCallerAndMethod); + + if ($methodName !== $callReflection->getName() || !$classReflection->is($callerClass)) { + continue; + } - if (is_int($arguments)) { - return [$arguments]; + $argumentPositions = array_merge($argumentPositions, $this->normalizeArgumentIndexes($indexes)); } - return $arguments; + return array_values(array_unique($argumentPositions)); } /** - * @param FunctionReflection|MethodReflection $callReflection + * @param int|list $argumentIndexes + * @return list */ - private function getCallNotation(object $callReflection): string + private function normalizeArgumentIndexes($argumentIndexes): array { - if ($callReflection instanceof MethodReflection) { - $class = $callReflection->getDeclaringClass()->getName(); - $method = $callReflection->getName(); - - return "{$class}::{$method}"; - } - - return $callReflection->getName(); + return is_int($argumentIndexes) ? [$argumentIndexes] : $argumentIndexes; } } diff --git a/tests/Extension/data/ImmediatelyCalledCallableThrowTypeExtension/code.php b/tests/Extension/data/ImmediatelyCalledCallableThrowTypeExtension/code.php index b2e93bc..1089c49 100644 --- a/tests/Extension/data/ImmediatelyCalledCallableThrowTypeExtension/code.php +++ b/tests/Extension/data/ImmediatelyCalledCallableThrowTypeExtension/code.php @@ -5,11 +5,29 @@ use PHPStan\TrinaryLogic; use function PHPStan\Testing\assertVariableCertainty; -class Immediate { +interface ImmediateInterface { + + public function inheritedMethod(callable $first, callable $second): int; + +} + +class BaseImmediate implements ImmediateInterface { + + public function inheritedMethod(callable $first, callable $second): int { + $first(); + $second(); + return 1; + } + +} + +class Immediate extends BaseImmediate { + public static function method(callable $callable): int { $callable(); return 1; } + } class MethodCallExtensionTest @@ -89,6 +107,27 @@ public function testFirstClassCallableNoThrow(): void } } + public function testInheritedMethod(): void + { + try { + $result1 = (new Immediate())->inheritedMethod($this->noThrow(...), $this->noThrow(...)); + } finally { + assertVariableCertainty(TrinaryLogic::createYes(), $result1); + } + + try { + $result2 = (new Immediate())->inheritedMethod($this->throw(...), $this->noThrow(...)); + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $result2); + } + + try { + $result3 = (new Immediate())->inheritedMethod($this->noThrow(...), $this->throw(...)); + } finally { + assertVariableCertainty(TrinaryLogic::createMaybe(), $result3); + } + } + } diff --git a/tests/Extension/data/ImmediatelyCalledCallableThrowTypeExtension/extension.neon b/tests/Extension/data/ImmediatelyCalledCallableThrowTypeExtension/extension.neon index 0d59cbd..cc0fff4 100644 --- a/tests/Extension/data/ImmediatelyCalledCallableThrowTypeExtension/extension.neon +++ b/tests/Extension/data/ImmediatelyCalledCallableThrowTypeExtension/extension.neon @@ -8,5 +8,7 @@ services: arguments: immediatelyCalledCallables: array_map: 0 + ImmediatelyCalledCallableThrowTypeExtension\ImmediateInterface::inheritedMethod: 0 + ImmediatelyCalledCallableThrowTypeExtension\BaseImmediate::inheritedMethod: 1 ImmediatelyCalledCallableThrowTypeExtension\Immediate::method: [0, 1]