Skip to content

Commit

Permalink
Add class inheritance support of whitelisted methods in ImmediatelyCa…
Browse files Browse the repository at this point in the history
…lledCallableThrowTypeExtension
  • Loading branch information
xificurk committed Sep 14, 2023
1 parent 3babe26 commit da25b33
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 22 deletions.
50 changes: 29 additions & 21 deletions src/Extension/ImmediatelyCalledCallableThrowTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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<int> $argumentIndexes
* @return list<int>
*/
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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
}
}

}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@ services:
arguments:
immediatelyCalledCallables:
array_map: 0
ImmediatelyCalledCallableThrowTypeExtension\ImmediateInterface::inheritedMethod: 0
ImmediatelyCalledCallableThrowTypeExtension\BaseImmediate::inheritedMethod: 1
ImmediatelyCalledCallableThrowTypeExtension\Immediate::method: [0, 1]

0 comments on commit da25b33

Please sign in to comment.