Skip to content
Permalink
Browse files

Fix #1781 by improving handling of callmap options

  • Loading branch information...
muglug committed Jun 15, 2019
1 parent 06e913e commit 2d5f332ebf0bebd1f0def8c60f4228a4fefc5b17
@@ -1372,124 +1372,6 @@ public function getCodebase() : Codebase
return $this->codebase;
}
/**
* @param string $method_id
* @param array<int, PhpParser\Node\Arg> $args
*
* @return array<int, FunctionLikeParameter>
*/
public static function getFunctionParamsFromCallMapById(Codebase $codebase, $method_id, array $args)
{
$function_param_options = CallMap::getParamsFromCallMap($method_id);
if ($function_param_options === null) {
throw new \UnexpectedValueException(
'Not expecting $function_param_options to be null for ' . $method_id
);
}
return self::getMatchingParamsFromCallMapOptions($codebase, $function_param_options, $args);
}
/**
* @param array<int, array<int, FunctionLikeParameter>> $function_param_options
* @param array<int, PhpParser\Node\Arg> $args
*
* @return array<int, FunctionLikeParameter>
*/
public static function getMatchingParamsFromCallMapOptions(
Codebase $codebase,
array $function_param_options,
array $args
) {
if (count($function_param_options) === 1) {
return $function_param_options[0];
}
foreach ($function_param_options as $possible_function_params) {
$all_args_match = true;
$last_param = count($possible_function_params)
? $possible_function_params[count($possible_function_params) - 1]
: null;
$mandatory_param_count = count($possible_function_params);
foreach ($possible_function_params as $i => $possible_function_param) {
if ($possible_function_param->is_optional) {
$mandatory_param_count = $i;
break;
}
}
if ($mandatory_param_count > count($args) && !($last_param && $last_param->is_variadic)) {
continue;
}
foreach ($args as $argument_offset => $arg) {
if ($argument_offset >= count($possible_function_params)) {
if (!$last_param || !$last_param->is_variadic) {
$all_args_match = false;
break;
}
$function_param = $last_param;
} else {
$function_param = $possible_function_params[$argument_offset];
}
$param_type = $function_param->type;
if (!$param_type) {
continue;
}
if (!isset($arg->value->inferredType)) {
continue;
}
$arg_type = $arg->value->inferredType;
if ($arg_type->hasMixed()) {
continue;
}
if ($arg->unpack && !$function_param->is_variadic) {
if ($arg_type->hasArray()) {
/** @var Type\Atomic\TArray|Type\Atomic\ObjectLike */
$array_atomic_type = $arg_type->getTypes()['array'];
if ($array_atomic_type instanceof Type\Atomic\ObjectLike) {
$array_atomic_type = $array_atomic_type->getGenericArrayType();
}
$arg_type = $array_atomic_type->type_params[1];
}
}
if (TypeAnalyzer::isContainedBy(
$codebase,
$arg_type,
$param_type,
true,
true
)) {
continue;
}
$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];
}
/**
* Get a list of suppressed issues
*
@@ -297,11 +297,13 @@ public static function analyze(
}
if ($in_call_map && !$is_stubbed) {
$function_params = FunctionLikeAnalyzer::getFunctionParamsFromCallMapById(
$function_callable = \Psalm\Internal\Codebase\CallMap::getCallableFromCallMapById(
$codebase,
$function_id,
$stmt->args
);
$function_params = $function_callable->params;
}
}
@@ -344,11 +346,13 @@ public static function analyze(
if ($function_exists) {
if ($stmt->name instanceof PhpParser\Node\Name && $function_id) {
if (!$is_stubbed && $in_call_map) {
$function_params = FunctionLikeAnalyzer::getFunctionParamsFromCallMapById(
$function_callable = \Psalm\Internal\Codebase\CallMap::getCallableFromCallMapById(
$codebase,
$function_id,
$stmt->args
);
$function_params = $function_callable->params;
}
}
@@ -1017,7 +1017,13 @@ function (PhpParser\Node\Arg $arg) {
);
}
} else {
$return_type_candidate = CallMap::getReturnTypeFromCallMap($call_map_id);
$callmap_callables = CallMap::getCallablesFromCallMap($call_map_id);
if (!$callmap_callables || $callmap_callables[0]->return_type === null) {
throw new \UnexpectedValueException('Shouldn’t get here');
}
$return_type_candidate = $callmap_callables[0]->return_type;
}
if ($return_type_candidate->isFalsable()) {
@@ -1936,42 +1936,36 @@ private static function checkArrayFunctionClosureType(
);
if (CallMap::inCallMap($function_id)) {
$callmap_params_options = CallMap::getParamsFromCallMap($function_id);
$callmap_callables = CallMap::getCallablesFromCallMap($function_id);
if ($callmap_params_options === null) {
if ($callmap_callables === null) {
throw new \UnexpectedValueException('This should not happen');
}
$passing_callmap_params_options = [];
$passing_callmap_callables = [];
foreach ($callmap_params_options as $callmap_params_option) {
foreach ($callmap_callables as $callmap_callable) {
$required_param_count = 0;
foreach ($callmap_params_option as $i => $param) {
assert($callmap_callable->params !== null);
foreach ($callmap_callable->params as $i => $param) {
if (!$param->is_optional && !$param->is_variadic) {
$required_param_count = $i + 1;
}
}
if ($required_param_count <= $max_closure_param_count) {
$passing_callmap_params_options[] = $callmap_params_option;
$passing_callmap_callables[] = $callmap_callable;
}
}
if ($passing_callmap_params_options) {
foreach ($passing_callmap_params_options as $passing_callmap_params_option) {
$closure_types[] = new Type\Atomic\TFn(
'Closure',
$passing_callmap_params_option,
$function_storage->return_type ?: Type::getMixed()
);
if ($passing_callmap_callables) {
foreach ($passing_callmap_callables as $passing_callmap_callable) {
$closure_types[] = $passing_callmap_callable;
}
} else {
$closure_types[] = new Type\Atomic\TFn(
'Closure',
$callmap_params_options[0],
$function_storage->return_type ?: Type::getMixed()
);
$closure_types[] = $callmap_callables[0];
}
} else {
$closure_types[] = new Type\Atomic\TFn(
@@ -2006,17 +2000,18 @@ private static function checkArrayFunctionClosureType(
}
/**
* @param Type\Atomic\TFn|Type\Atomic\TCallable $closure_type
* @param string $method_id
* @param int $min_closure_param_count
* @param int $max_closure_param_count [description]
* @param int $max_closure_param_count
* @param (TArray|null)[] $array_arg_types
*
* @return false|null
*/
private static function checkArrayFunctionClosureTypeArgs(
StatementsAnalyzer $statements_analyzer,
$method_id,
Type\Atomic\TFn $closure_type,
Type\Atomic $closure_type,
PhpParser\Node\Arg $closure_arg,
$min_closure_param_count,
$max_closure_param_count,
@@ -1270,7 +1270,7 @@ public static function isAtomicContainedBy(
}
}
$input_callable = self::getCallableFromAtomic($codebase, $input_type_part);
$input_callable = self::getCallableFromAtomic($codebase, $input_type_part, $container_type_part);
if ($input_callable) {
$all_types_contain = true;
@@ -1421,8 +1421,11 @@ public static function isAtomicContainedBy(
/**
* @return ?TCallable
*/
public static function getCallableFromAtomic(Codebase $codebase, Type\Atomic $input_type_part)
{
public static function getCallableFromAtomic(
Codebase $codebase,
Type\Atomic $input_type_part,
?TCallable $container_type_part = null
) : ?TCallable {
if ($input_type_part instanceof TLiteralString) {
try {
$function_storage = $codebase->functions->getStorage(null, $input_type_part->value);
@@ -1434,16 +1437,24 @@ public static function getCallableFromAtomic(Codebase $codebase, Type\Atomic $in
);
} catch (\Exception $e) {
if (CallMap::inCallMap($input_type_part->value)) {
$function_params = FunctionLikeAnalyzer::getFunctionParamsFromCallMapById(
$args = [];
if ($container_type_part && $container_type_part->params) {
foreach ($container_type_part->params as $i => $param) {
$arg = new \PhpParser\Node\Arg(
new \PhpParser\Node\Expr\Variable('_' . $i)
);
$arg->value->inferredType = $param->type;
$args[] = $arg;
}
}
return \Psalm\Internal\Codebase\CallMap::getCallableFromCallMapById(
$codebase,
$input_type_part->value,
[]
);
return new TCallable(
'callable',
$function_params,
CallMap::getReturnTypeFromCallMap($input_type_part->value)
$args
);
}
}

0 comments on commit 2d5f332

Please sign in to comment.
You can’t perform that action at this time.