1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-22 13:51:54 +01:00
psalm/src/Psalm/Checker/FunctionLikeChecker.php

822 lines
26 KiB
PHP
Raw Normal View History

2016-08-13 23:26:45 -04:00
<?php
namespace Psalm\Checker;
ini_set('xdebug.max_nesting_level', 512);
use PhpParser;
2016-10-15 00:12:57 -04:00
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Stmt\Function_;
use PhpParser\Node\Stmt\ClassMethod;
2016-08-13 23:26:45 -04:00
use Psalm\Issue\InvalidDocblock;
use Psalm\Issue\InvalidReturnType;
use Psalm\FunctionLikeParameter;
2016-08-13 23:26:45 -04:00
use Psalm\StatementsSource;
use Psalm\Type;
use Psalm\Config;
use Psalm\Context;
use Psalm\IssueBuffer;
abstract class FunctionLikeChecker implements StatementsSource
{
2016-10-14 00:53:43 -04:00
/**
2016-10-15 00:12:57 -04:00
* @var Closure|Function_|ClassMethod
2016-10-14 00:53:43 -04:00
*/
2016-08-13 23:26:45 -04:00
protected $function;
2016-10-14 00:53:43 -04:00
/**
* @var string
*/
2016-08-13 23:26:45 -04:00
protected $namespace;
2016-10-14 00:53:43 -04:00
/**
* @var string
*/
2016-08-13 23:26:45 -04:00
protected $file_name;
2016-10-14 00:53:43 -04:00
/**
2016-10-15 00:12:57 -04:00
* @var string|null
2016-10-14 00:53:43 -04:00
*/
2016-08-24 19:00:44 -04:00
protected $include_file_name;
2016-10-14 00:53:43 -04:00
/**
* @var bool
*/
2016-08-13 23:26:45 -04:00
protected $is_static = false;
2016-10-14 00:53:43 -04:00
/**
* @var string
*/
2016-08-13 23:26:45 -04:00
protected $absolute_class;
2016-10-14 00:53:43 -04:00
/**
* @var StatementsChecker|null
*/
2016-08-13 23:26:45 -04:00
protected $statements_checker;
2016-10-14 00:53:43 -04:00
/**
* @var StatementsSource
*/
2016-08-13 23:26:45 -04:00
protected $source;
2016-10-14 00:53:43 -04:00
2016-10-15 00:12:57 -04:00
/**
* @var array<string,array<string,Psalm\Type\Union>>
*/
2016-08-13 23:26:45 -04:00
protected $return_vars_in_scope = [];
2016-10-15 00:12:57 -04:00
/**
* @var array<string,array<string,bool>>
*/
2016-08-13 23:26:45 -04:00
protected $return_vars_possibly_in_scope = [];
protected $class_name;
/**
* @var string|null
*/
2016-08-13 23:26:45 -04:00
protected $class_extends;
/**
* @var array
*/
protected $suppressed_issues;
protected static $no_effects_hashes = [];
2016-10-15 00:12:57 -04:00
/**
* @param Closure|Function_|ClassMethod $function
* @param StatementsSource $source
*/
public function __construct($function, StatementsSource $source)
2016-08-13 23:26:45 -04:00
{
$this->function = $function;
$this->namespace = $source->getNamespace();
$this->class_name = $source->getClassName();
$this->class_extends = $source->getParentClass();
$this->file_name = $source->getFileName();
2016-08-24 19:00:44 -04:00
$this->include_file_name = $source->getIncludeFileName();
2016-08-13 23:26:45 -04:00
$this->absolute_class = $source->getAbsoluteClass();
$this->source = $source;
$this->suppressed_issues = $source->getSuppressedIssues();
}
public function check(Context $context, $check_methods = true)
{
2016-10-14 00:53:43 -04:00
if ($function_stmts = $this->function->getStmts()) {
2016-08-13 23:26:45 -04:00
$has_context = (bool) count($context->vars_in_scope);
2016-09-01 18:02:09 -04:00
$statements_checker = new StatementsChecker($this, $has_context, $check_methods);
2016-10-03 11:38:59 -04:00
$hash = null;
if ($this instanceof MethodChecker) {
2016-08-13 23:26:45 -04:00
if (ClassLikeChecker::getThisClass()) {
$hash = $this->getMethodId() . json_encode([$context->vars_in_scope, $context->vars_possibly_in_scope]);
// if we know that the function has no effects on vars, we don't bother rechecking
if (isset(self::$no_effects_hashes[$hash])) {
list($context->vars_in_scope, $context->vars_possibly_in_scope) = self::$no_effects_hashes[$hash];
return;
}
}
elseif ($context->self) {
2016-10-13 18:27:23 -04:00
$context->vars_in_scope['$this'] = new Type\Union([new Type\Atomic($context->self)]);
2016-08-13 23:26:45 -04:00
}
2016-10-15 00:12:57 -04:00
$function_params = MethodChecker::getMethodParams((string)$this->getMethodId());
2016-09-01 18:02:09 -04:00
}
else if ($this instanceof FunctionChecker) {
2016-10-15 00:12:57 -04:00
$function_params = FunctionChecker::getParams((string)$this->getMethodId(), $this->file_name);
2016-08-13 23:26:45 -04:00
}
2016-09-01 18:02:09 -04:00
else { // Closure
$function_params = [];
2016-08-13 23:26:45 -04:00
// @todo deprecate this code
2016-10-14 00:53:43 -04:00
foreach ($this->function->getParams() as $param) {
2016-08-13 23:26:45 -04:00
$is_nullable = $param->default !== null &&
$param->default instanceof \PhpParser\Node\Expr\ConstFetch &&
$param->default->name instanceof PhpParser\Node\Name &&
$param->default->name->parts = ['null'];
2016-09-01 18:02:09 -04:00
$param_type = null;
2016-08-13 23:26:45 -04:00
if ($param->type) {
2016-10-03 11:38:59 -04:00
$param_type_string = '';
2016-09-01 18:02:09 -04:00
if (is_string($param->type)) {
$param_type_string = $param->type;
2016-08-13 23:26:45 -04:00
}
2016-09-01 18:02:09 -04:00
elseif ($param->type instanceof PhpParser\Node\Name) {
$param_type_string = $param->type->parts === ['self']
? $this->absolute_class
: ClassLikeChecker::getAbsoluteClassFromName(
$param->type,
$this->namespace,
$this->getAliasedClasses()
);
2016-08-13 23:26:45 -04:00
}
2016-09-01 18:02:09 -04:00
if ($is_nullable) {
$param_type_string .= '|null';
}
$param_type = Type::parseString($param_type_string);
2016-08-13 23:26:45 -04:00
}
2016-09-01 18:02:09 -04:00
if (!$param_type) {
$param_type = Type::getMixed();
2016-08-13 23:26:45 -04:00
}
$function_params[] = new FunctionLikeParameter(
$param->name,
false,
2016-10-15 00:12:57 -04:00
$param_type,
false,
false,
$param->getLine()
);
2016-08-13 23:26:45 -04:00
}
}
2016-09-01 18:02:09 -04:00
foreach ($function_params as $function_param) {
$param_type = StatementsChecker::fleshOutTypes(
clone $function_param->type,
2016-09-01 18:02:09 -04:00
[],
$context->self,
$this->getMethodId()
);
foreach ($param_type->types as $atomic_type) {
if ($atomic_type->isObjectType()
2016-09-01 20:27:16 -04:00
&& !$atomic_type->isObject()
2016-10-15 00:12:57 -04:00
&& $this->function instanceof PhpParser\Node
2016-09-01 18:02:09 -04:00
&& ClassLikeChecker::checkAbsoluteClassOrInterface(
$atomic_type->value,
$this->file_name,
$this->function->getLine(),
$this->suppressed_issues
) === false
) {
return false;
}
}
2016-10-13 18:27:23 -04:00
$context->vars_in_scope['$' . $function_param->name] = $param_type;
2016-09-01 18:02:09 -04:00
2016-10-15 00:12:57 -04:00
$statements_checker->registerVariable($function_param->name, $function_param->line);
2016-09-01 18:02:09 -04:00
}
2016-08-13 23:26:45 -04:00
2016-10-14 00:53:43 -04:00
$statements_checker->check($function_stmts, $context);
2016-08-13 23:26:45 -04:00
if (isset($this->return_vars_in_scope[''])) {
$context->vars_in_scope = TypeChecker::combineKeyedTypes($context->vars_in_scope, $this->return_vars_in_scope['']);
}
if (isset($this->return_vars_possibly_in_scope[''])) {
$context->vars_possibly_in_scope = array_merge($context->vars_possibly_in_scope, $this->return_vars_possibly_in_scope['']);
}
foreach ($context->vars_in_scope as $var => $type) {
2016-10-13 18:27:23 -04:00
if (strpos($var, '$this->') !== 0) {
2016-08-13 23:26:45 -04:00
unset($context->vars_in_scope[$var]);
}
}
foreach ($context->vars_possibly_in_scope as $var => $type) {
2016-10-13 18:27:23 -04:00
if (strpos($var, '$this->') !== 0) {
2016-08-13 23:26:45 -04:00
unset($context->vars_possibly_in_scope[$var]);
}
}
2016-10-03 11:38:59 -04:00
if ($hash && ClassLikeChecker::getThisClass() && $this instanceof MethodChecker) {
2016-08-13 23:26:45 -04:00
self::$no_effects_hashes[$hash] = [$context->vars_in_scope, $context->vars_possibly_in_scope];
}
}
}
/**
* Adds return types for the given function
* @param string $return_type
* @param array<Type> $context->vars_in_scope
* @param array<bool> $context->vars_possibly_in_scope
*/
public function addReturnTypes($return_type, Context $context)
{
if (isset($this->return_vars_in_scope[$return_type])) {
$this->return_vars_in_scope[$return_type] = TypeChecker::combineKeyedTypes($context->vars_in_scope, $this->return_vars_in_scope[$return_type]);
}
else {
$this->return_vars_in_scope[$return_type] = $context->vars_in_scope;
}
if (isset($this->return_vars_possibly_in_scope[$return_type])) {
$this->return_vars_possibly_in_scope[$return_type] = array_merge($context->vars_possibly_in_scope, $this->return_vars_possibly_in_scope[$return_type]);
}
else {
$this->return_vars_possibly_in_scope[$return_type] = $context->vars_possibly_in_scope;
}
}
/**
* @return null|string
*/
public function getMethodId()
{
2016-10-15 00:12:57 -04:00
if ($this->function instanceof Function_ || $this->function instanceof ClassMethod) {
return ($this instanceof MethodChecker ? $this->absolute_class . '::' : '') . strtolower($this->function->name);
2016-08-13 23:26:45 -04:00
}
}
public function getNamespace()
{
return $this->namespace;
}
2016-10-15 00:12:57 -04:00
/**
* @return array<string>
*/
2016-08-13 23:26:45 -04:00
public function getAliasedClasses()
{
return $this->source->getAliasedClasses();
2016-08-13 23:26:45 -04:00
}
public function getAbsoluteClass()
{
return $this->absolute_class;
}
public function getClassName()
{
return $this->class_name;
}
public function getClassLikeChecker()
{
return $this->source->getClassLikeChecker();
}
/**
* @return string|null
*/
2016-08-13 23:26:45 -04:00
public function getParentClass()
{
return $this->class_extends;
}
public function getFileName()
{
return $this->file_name;
}
2016-08-24 19:00:44 -04:00
public function getIncludeFileName()
{
return $this->include_file_name;
}
/**
* @param string|null $file_name
*/
2016-08-24 19:00:44 -04:00
public function setIncludeFileName($file_name)
{
$this->include_file_name = $file_name;
}
public function getCheckedFileName()
{
return $this->include_file_name ?: $this->file_name;
}
2016-08-13 23:26:45 -04:00
public function isStatic()
{
return $this->is_static;
}
2016-10-15 00:12:57 -04:00
/**
* @return StatementsSource
*/
2016-08-13 23:26:45 -04:00
public function getSource()
{
return $this->source;
}
/**
* Get a list of suppressed issues
* @return array<string>
*/
2016-08-13 23:26:45 -04:00
public function getSuppressedIssues()
{
return $this->suppressed_issues;
}
/**
* @return false|null
*/
public function checkReturnTypes($update_doc_comment = false)
{
2016-10-15 00:12:57 -04:00
if (!$this->function->getStmts()) {
2016-08-13 23:26:45 -04:00
return;
}
2016-10-15 00:12:57 -04:00
if ($this->function instanceof ClassMethod || $this->function instanceof Function_) {
if ($this->function->name === '__construct') {
// we know that constructors always return this
return;
}
2016-08-13 23:26:45 -04:00
}
2016-10-15 00:12:57 -04:00
$method_id = (string)$this->getMethodId();
2016-08-13 23:26:45 -04:00
if ($this instanceof MethodChecker) {
$method_return_types = MethodChecker::getMethodReturnTypes($method_id);
2016-08-13 23:26:45 -04:00
}
else {
2016-08-14 13:13:53 -04:00
$method_return_types = FunctionChecker::getFunctionReturnTypes($method_id, $this->file_name);
2016-08-13 23:26:45 -04:00
}
if (!$method_return_types) {
return;
}
// passing it through fleshOutTypes eradicates errant $ vars
$declared_return_type = StatementsChecker::fleshOutTypes(
$method_return_types,
[],
$this->absolute_class,
$method_id
);
if ($declared_return_type) {
2016-10-15 00:12:57 -04:00
$inferred_return_types = \Psalm\EffectsAnalyser::getReturnTypes($this->function->getStmts(), true);
2016-08-13 23:26:45 -04:00
if (!$inferred_return_types) {
if ($declared_return_type->isVoid()) {
return;
}
2016-10-15 00:12:57 -04:00
if (ScopeChecker::onlyThrows($this->function->getStmts())) {
2016-08-13 23:26:45 -04:00
// if there's a single throw statement, it's presumably an exception saying this method is not to be used
return;
}
if (IssueBuffer::accepts(
new InvalidReturnType(
'No return type was found for method ' . MethodChecker::getCasedMethodId($method_id) . ' but return type \'' . $declared_return_type . '\' was expected',
2016-08-24 19:00:44 -04:00
$this->getCheckedFileName(),
2016-08-13 23:26:45 -04:00
$this->function->getLine()
)
)) {
return false;
}
return;
}
$inferred_return_type = Type::combineTypes($inferred_return_types);
if ($inferred_return_type && !$inferred_return_type->isMixed() && !$declared_return_type->isMixed()) {
if ($inferred_return_type->isNull() && $declared_return_type->isVoid()) {
return;
}
if (!TypeChecker::hasIdenticalTypes($declared_return_type, $inferred_return_type, $this->absolute_class)) {
if (IssueBuffer::accepts(
new InvalidReturnType(
'The given return type \'' . $declared_return_type . '\' for ' . MethodChecker::getCasedMethodId($method_id) . ' is incorrect, got \'' . $inferred_return_type . '\'',
2016-08-24 19:00:44 -04:00
$this->getCheckedFileName(),
2016-08-13 23:26:45 -04:00
$this->function->getLine()
),
$this->getSuppressedIssues()
)) {
return false;
}
}
}
return;
}
}
2016-10-15 00:12:57 -04:00
/**
* @param array<object-like{type:string,name:string}> $docblock_params
* @param array<string,Type\Union> $function_param_names
* @param array<\Psalm\FunctionLikeParameter> &$function_signature
* @param int $method_line_number
* @return false|null
*/
2016-08-13 23:26:45 -04:00
protected function improveParamsFromDocblock(array $docblock_params, array $function_param_names, array &$function_signature, $method_line_number)
{
foreach ($docblock_params as $docblock_param) {
$param_name = $docblock_param['name'];
if (!array_key_exists($param_name, $function_param_names)) {
if (IssueBuffer::accepts(
new InvalidDocblock(
'Parameter $' . $param_name .' does not appear in the argument list for ' . $this->getMethodId(),
2016-08-24 19:00:44 -04:00
$this->getCheckedFileName(),
2016-08-13 23:26:45 -04:00
$method_line_number
)
)) {
return false;
}
continue;
}
$new_param_type =
Type::parseString(
self::fixUpLocalType(
$docblock_param['type'],
null,
$this->namespace,
$this->getAliasedClasses()
2016-08-13 23:26:45 -04:00
)
);
if ($function_param_names[$param_name] && !$function_param_names[$param_name]->isMixed()) {
if (!$new_param_type->isIn($function_param_names[$param_name])) {
if (IssueBuffer::accepts(
new InvalidDocblock(
'Parameter $' . $param_name .' has wrong type \'' . $new_param_type . '\', should be \'' . $function_param_names[$param_name] . '\'',
2016-08-24 19:00:44 -04:00
$this->getCheckedFileName(),
2016-08-13 23:26:45 -04:00
$method_line_number
)
)) {
return false;
}
continue;
}
}
foreach ($function_signature as &$function_signature_param) {
if ($function_signature_param->name === $param_name) {
$existing_param_type_nullable = $function_signature_param->is_nullable;
2016-08-13 23:26:45 -04:00
if ($existing_param_type_nullable && !$new_param_type->isNullable()) {
2016-09-09 16:21:49 -04:00
$new_param_type->types['null'] = new Type\Atomic('null');
2016-08-13 23:26:45 -04:00
}
$function_signature_param->type = $new_param_type;
2016-08-13 23:26:45 -04:00
break;
}
}
}
}
/**
* @param PhpParser\Node\Param $param
* @return FunctionLikeParameter
*/
2016-08-13 23:26:45 -04:00
protected function getParamArray(PhpParser\Node\Param $param)
{
$param_type = null;
$is_nullable = $param->default !== null &&
$param->default instanceof \PhpParser\Node\Expr\ConstFetch &&
$param->default->name instanceof PhpParser\Node\Name &&
$param->default->name->parts === ['null'];
if ($param->type) {
if (is_string($param->type)) {
$param_type_string = $param->type;
}
elseif ($param->type instanceof PhpParser\Node\Name\FullyQualified) {
$param_type_string = implode('\\', $param->type->parts);
}
elseif ($param->type->parts === ['self']) {
$param_type_string = $this->absolute_class;
}
else {
$param_type_string = ClassLikeChecker::getAbsoluteClassFromString(
implode('\\', $param->type->parts),
$this->namespace,
$this->getAliasedClasses()
);
2016-08-13 23:26:45 -04:00
}
if ($param_type_string) {
if ($is_nullable) {
$param_type_string .= '|null';
}
$param_type = Type::parseString($param_type_string);
}
}
$is_optional = $param->default !== null;
return new FunctionLikeParameter(
$param->name,
$param->byRef,
$param_type ?: Type::getMixed(),
$is_optional,
2016-10-15 00:12:57 -04:00
$is_nullable,
$param->getLine()
);
2016-08-13 23:26:45 -04:00
}
2016-10-15 00:12:57 -04:00
/**
* @param \ReflectionParameter $param
* @return FunctionLikeParameter
*/
2016-08-14 13:13:53 -04:00
protected static function getReflectionParamArray(\ReflectionParameter $param)
2016-08-13 23:26:45 -04:00
{
$param_type_string = null;
if ($param->isArray()) {
$param_type_string = 'array';
}
else {
$param_class = null;
try {
2016-10-15 00:12:57 -04:00
/** @var \ReflectionClass */
2016-08-13 23:26:45 -04:00
$param_class = $param->getClass();
}
catch (\ReflectionException $e) {
// do nothing
}
if ($param_class) {
2016-10-15 00:12:57 -04:00
$param_type_string = (string)$param_class->getName();
2016-08-13 23:26:45 -04:00
}
}
$is_nullable = false;
2016-10-15 00:12:57 -04:00
$is_optional = (bool)$param->isOptional();
2016-08-13 23:26:45 -04:00
try {
$is_nullable = $param->getDefaultValue() === null;
if ($param_type_string && $is_nullable) {
$param_type_string .= '|null';
}
}
catch (\ReflectionException $e) {
// do nothing
}
2016-10-15 00:12:57 -04:00
$param_name = (string)$param->getName();
2016-08-13 23:26:45 -04:00
$param_type = $param_type_string ? Type::parseString($param_type_string) : Type::getMixed();
return new FunctionLikeParameter(
$param_name,
2016-10-15 00:12:57 -04:00
(bool)$param->isPassedByReference(),
$param_type,
2016-10-10 01:35:12 -04:00
$is_optional,
$is_nullable
);
2016-08-13 23:26:45 -04:00
}
2016-10-14 00:53:43 -04:00
/**
2016-10-15 00:12:57 -04:00
* @param string $return_type
* @param string|null $absolute_class
* @param string $namespace
* @param array $aliased_classes
2016-10-14 00:53:43 -04:00
* @return string
*/
public static function fixUpLocalType($return_type, $absolute_class, $namespace, array $aliased_classes)
2016-08-13 23:26:45 -04:00
{
if (strpos($return_type, '[') !== false) {
$return_type = Type::convertSquareBrackets($return_type);
}
$return_type_tokens = Type::tokenize($return_type);
foreach ($return_type_tokens as $i => &$return_type_token) {
2016-08-13 23:26:45 -04:00
if ($return_type_token[0] === '\\') {
$return_type_token = substr($return_type_token, 1);
continue;
}
if (in_array($return_type_token, ['<', '>', '|', '?', ',', '{', '}', ':'])) {
continue;
}
if (isset($return_type_token[$i + 1]) && $return_type_token[$i + 1] === ':') {
2016-08-13 23:26:45 -04:00
continue;
}
$return_type_token = Type::fixScalarTerms($return_type_token);
if ($return_type_token[0] === strtoupper($return_type_token[0])) {
if ($return_type_token[0] === '$') {
if ($return_type === '$this') {
$return_type_token = 'static';
}
2016-08-13 23:26:45 -04:00
continue;
}
$return_type_token = ClassLikeChecker::getAbsoluteClassFromString($return_type_token, $namespace, $aliased_classes);
}
}
return implode('', $return_type_tokens);
}
2016-08-22 15:00:12 -04:00
/**
* Does the input param type match the given param type
* @param Type\Union $input_type
* @param Type\Union $param_type
* @param bool &$has_scalar_match
* @param bool &$type_coerced whether or not there was type coercion involved
2016-08-22 15:00:12 -04:00
* @return bool
*/
public static function doesParamMatch(Type\Union $input_type, Type\Union $param_type, &$has_scalar_match = null, &$type_coerced = null)
2016-08-22 15:00:12 -04:00
{
$has_scalar_match = true;
if ($param_type->isMixed()) {
return true;
}
$type_match_found = false;
$has_type_mismatch = false;
foreach ($input_type->types as $input_type_part) {
if ($input_type_part->isNull()) {
continue;
}
$type_match_found = false;
$scalar_type_match_found = false;
foreach ($param_type->types as $param_type_part) {
if ($param_type_part->isNull()) {
continue;
}
if ($input_type_part->value === $param_type_part->value ||
ClassChecker::classExtendsOrImplements($input_type_part->value, $param_type_part->value) ||
StatementsChecker::isMock($input_type_part->value)
) {
$type_match_found = true;
break;
}
if ($input_type_part->value === 'false' && $param_type_part->value === 'bool') {
$type_match_found = true;
}
if ($input_type_part->value === 'int' && $param_type_part->value === 'float') {
$type_match_found = true;
}
if ($input_type_part->value === 'Closure' && $param_type_part->value === 'callable') {
$type_match_found = true;
}
2016-08-22 15:00:12 -04:00
if ($param_type_part->isNumeric() && $input_type_part->isNumericType()) {
$type_match_found = true;
}
if ($param_type_part->isArray() && $input_type_part->isObjectLike()) {
$type_match_found = true;
}
2016-08-30 00:39:17 -04:00
if ($param_type_part->isScalar() && $input_type_part->isScalarType()) {
$type_match_found = true;
}
2016-08-22 15:00:12 -04:00
if ($param_type_part->isCallable() && ($input_type_part->value === 'string' || $input_type_part->value === 'array')) {
// @todo add value checks if possible here
$type_match_found = true;
}
2016-08-30 00:39:17 -04:00
if ($input_type_part->isNumeric()) {
if ($param_type_part->isNumericType()) {
$scalar_type_match_found = true;
}
}
2016-08-31 13:25:29 -04:00
if ($input_type_part->isScalarType() || $input_type_part->isScalar()) {
2016-08-22 15:00:12 -04:00
if ($param_type_part->isScalarType()) {
$scalar_type_match_found = true;
}
}
2016-08-30 00:39:17 -04:00
else if ($param_type_part->isObject() && !$input_type_part->isArray() && !$input_type_part->isResource()) {
2016-08-22 15:00:12 -04:00
$type_match_found = true;
}
if (ClassChecker::classExtendsOrImplements($param_type_part->value, $input_type_part->value)) {
$type_coerced = true;
2016-08-22 15:00:12 -04:00
$type_match_found = true;
break;
}
}
if (!$type_match_found) {
if (!$scalar_type_match_found) {
$has_scalar_match = false;
}
return false;
}
}
return true;
}
/**
* @param string $method_id
* @param array<PhpParser\Node\Arg> $args
* @param string $file_name
* @return array<int,FunctionLikeParameter>
*/
2016-08-22 23:02:03 -04:00
public static function getParamsById($method_id, array $args, $file_name)
2016-08-22 15:00:12 -04:00
{
if (strpos($method_id, '::')) {
return MethodChecker::getMethodParams($method_id);
}
2016-10-15 00:12:57 -04:00
$function_param_options = FunctionChecker::getParamsFromCallMap($method_id);
2016-08-22 15:00:12 -04:00
if ($function_param_options === null) {
2016-08-22 15:42:05 -04:00
return FunctionChecker::getParams(strtolower($method_id), $file_name);
2016-08-22 15:00:12 -04:00
}
$function_params = null;
if (count($function_param_options) === 1) {
return $function_param_options[0];
}
foreach ($function_param_options as $possible_function_params) {
$all_args_match = true;
foreach ($args as $argument_offset => $arg) {
2016-08-22 15:43:08 -04:00
if (count($possible_function_params) <= $argument_offset) {
2016-08-22 15:00:12 -04:00
break;
}
$param_type = $possible_function_params[$argument_offset]->type;
2016-08-22 15:00:12 -04:00
if (!isset($arg->value->inferredType)) {
continue;
}
2016-10-10 22:49:43 -04:00
if ($arg->value->inferredType->isMixed()) {
continue;
}
if (FunctionLikeChecker::doesParamMatch($arg->value->inferredType, $param_type)) {
continue;
2016-08-22 15:00:12 -04:00
}
$all_args_match = false;
break;
}
if ($all_args_match) {
return $possible_function_params;
}
}
// if we don't succeed in finding a match, set to the first possible and wait for issues below
return $function_param_options[0];
}
2016-08-13 23:26:45 -04:00
}