1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-26 20:34:47 +01:00

Extract template params from string/array callables cc @weirdan

The culmination of what you were getting at
This commit is contained in:
Matthew Brown 2019-01-19 20:18:45 -05:00
parent 590bea305d
commit 1c17d2e2f2
4 changed files with 89 additions and 64 deletions

View File

@ -959,69 +959,7 @@ class TypeAnalyzer
)
)
) {
$input_callable = null;
if ($input_type_part instanceof TLiteralString) {
try {
$function_storage = $codebase->functions->getStorage(null, $input_type_part->value);
$input_callable = new TCallable(
'callable',
$function_storage->params,
$function_storage->return_type
);
} catch (\Exception $e) {
if (CallMap::inCallMap($input_type_part->value)) {
$function_params = FunctionLikeAnalyzer::getFunctionParamsFromCallMapById(
$codebase,
$input_type_part->value,
[]
);
$input_callable = new TCallable(
'callable',
$function_params,
CallMap::getReturnTypeFromCallMap($input_type_part->value)
);
}
}
} elseif ($input_type_part instanceof ObjectLike) {
if (isset($input_type_part->properties[0])
&& isset($input_type_part->properties[1])
&& $input_type_part->properties[1]->isSingleStringLiteral()
) {
$lhs = $input_type_part->properties[0];
$method_name = $input_type_part->properties[1]->getSingleStringLiteral()->value;
$class_name = null;
if ($lhs->isSingleStringLiteral()) {
$class_name = $lhs->getSingleStringLiteral()->value;
} elseif ($lhs->isSingle()) {
foreach ($lhs->getTypes() as $lhs_atomic_type) {
if ($lhs_atomic_type instanceof TNamedObject) {
$class_name = $lhs_atomic_type->value;
}
}
}
if ($class_name) {
$method_id = $class_name . '::' . $method_name;
try {
$method_storage = $codebase->methods->getStorage($method_id);
$input_callable = new TCallable(
'callable',
$method_storage->params,
$method_storage->return_type
);
} catch (\Exception $e) {
// do nothing
}
}
}
}
$input_callable = self::getCallableFromAtomic($codebase, $input_type_part);
if ($input_callable) {
$all_types_contain = true;
@ -1165,6 +1103,76 @@ class TypeAnalyzer
return false;
}
/**
* @return ?TCallable
*/
public static function getCallableFromAtomic(Codebase $codebase, Type\Atomic $input_type_part)
{
if ($input_type_part instanceof TLiteralString) {
try {
$function_storage = $codebase->functions->getStorage(null, $input_type_part->value);
return new TCallable(
'callable',
$function_storage->params,
$function_storage->return_type
);
} catch (\Exception $e) {
if (CallMap::inCallMap($input_type_part->value)) {
$function_params = FunctionLikeAnalyzer::getFunctionParamsFromCallMapById(
$codebase,
$input_type_part->value,
[]
);
return new TCallable(
'callable',
$function_params,
CallMap::getReturnTypeFromCallMap($input_type_part->value)
);
}
}
} elseif ($input_type_part instanceof ObjectLike) {
if (isset($input_type_part->properties[0])
&& isset($input_type_part->properties[1])
&& $input_type_part->properties[1]->isSingleStringLiteral()
) {
$lhs = $input_type_part->properties[0];
$method_name = $input_type_part->properties[1]->getSingleStringLiteral()->value;
$class_name = null;
if ($lhs->isSingleStringLiteral()) {
$class_name = $lhs->getSingleStringLiteral()->value;
} elseif ($lhs->isSingle()) {
foreach ($lhs->getTypes() as $lhs_atomic_type) {
if ($lhs_atomic_type instanceof TNamedObject) {
$class_name = $lhs_atomic_type->value;
}
}
}
if ($class_name) {
$method_id = $class_name . '::' . $method_name;
try {
$method_storage = $codebase->methods->getStorage($method_id);
return new TCallable(
'callable',
$method_storage->params,
$method_storage->return_type
);
} catch (\Exception $e) {
// do nothing
}
}
}
}
return null;
}
/**
* @param Codebase $codebase
* @param Type\Atomic $input_type_part

View File

@ -177,7 +177,7 @@ trait CallableTrait
foreach ($this->params as $offset => $param) {
$input_param_type = null;
if ($input_type instanceof Atomic\Fn
if (($input_type instanceof Atomic\Fn || $input_type instanceof Atomic\TCallable)
&& isset($input_type->params[$offset])
) {
$input_param_type = $input_type->params[$offset]->type;

View File

@ -897,6 +897,17 @@ class Union
break;
}
if ($key === 'callable') {
$matching_atomic_type = TypeAnalyzer::getCallableFromAtomic(
$codebase,
$atomic_input_type
);
if ($matching_atomic_type) {
break;
}
}
if ($atomic_input_type instanceof TNamedObject && $atomic_type instanceof TNamedObject) {
try {
$classlike_storage =

View File

@ -1764,6 +1764,12 @@ class FunctionCallTest extends TestCase
);',
'error_message' => 'InvalidArgument',
],
'usortInvalidCallableString' => [
'<?php
$a = [[1], [2], [3]];
usort($a, "strcmp");',
'error_message' => 'InvalidArgument',
],
];
}
}