Skip to content

Commit

Permalink
is_callable() with an array with two items results in method_exists()
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Jun 9, 2019
1 parent 7ef590b commit 886e636
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 1 deletion.
29 changes: 28 additions & 1 deletion src/Type/Php/IsCallableFunctionTypeSpecifyingExtension.php
Expand Up @@ -2,22 +2,34 @@

namespace PHPStan\Type\Php;

use PhpParser\Node\Arg;
use PhpParser\Node\Expr\Array_;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Name;
use PHPStan\Analyser\Scope;
use PHPStan\Analyser\SpecifiedTypes;
use PHPStan\Analyser\TypeSpecifier;
use PHPStan\Analyser\TypeSpecifierAwareExtension;
use PHPStan\Analyser\TypeSpecifierContext;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Type\CallableType;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\FunctionTypeSpecifyingExtension;

class IsCallableFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension
{

/** @var \PHPStan\Type\Php\MethodExistsTypeSpecifyingExtension */
private $methodExistsExtension;

/** @var \PHPStan\Analyser\TypeSpecifier */
private $typeSpecifier;

public function __construct(MethodExistsTypeSpecifyingExtension $methodExistsExtension)
{
$this->methodExistsExtension = $methodExistsExtension;
}

public function isFunctionSupported(FunctionReflection $functionReflection, FuncCall $node, TypeSpecifierContext $context): bool
{
return strtolower($functionReflection->getName()) === 'is_callable'
Expand All @@ -31,7 +43,22 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n
throw new \PHPStan\ShouldNotHappenException();
}

return $this->typeSpecifier->create($node->args[0]->value, new CallableType(), $context);
$value = $node->args[0]->value;
$valueType = $scope->getType($value);
if (
$value instanceof Array_
&& count($value->items) === 2
&& $valueType instanceof ConstantArrayType
&& !$valueType->isCallable()->no()
) {
$functionCall = new FuncCall(new Name('method_exists'), [
new Arg($value->items[0]->value),
new Arg($value->items[1]->value),
]);
return $this->methodExistsExtension->specifyTypes($functionReflection, $functionCall, $scope, $context);
}

return $this->typeSpecifier->create($value, new CallableType(), $context);
}

public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void
Expand Down
15 changes: 15 additions & 0 deletions tests/PHPStan/Rules/Methods/data/call-methods.php
Expand Up @@ -1299,3 +1299,18 @@ public function requireIntOrString($parameter)
}

}

class IsCallableResultsInMethodExists
{

/**
* @param object $value
*/
public function doFoo($value): void
{
if (is_callable([$value, 'toArray'])) {
$value->toArray();
}
}

}

0 comments on commit 886e636

Please sign in to comment.