mirror of
https://github.com/danog/psalm.git
synced 2024-12-02 17:52:45 +01:00
178 lines
5.7 KiB
PHP
178 lines
5.7 KiB
PHP
<?php
|
|
|
|
namespace Psalm\Tests\Config\Plugin\Hook;
|
|
|
|
use Psalm\Codebase;
|
|
use Psalm\Plugin\DynamicFunctionStorage;
|
|
use Psalm\Plugin\DynamicTemplateProvider;
|
|
use Psalm\Plugin\EventHandler\DynamicFunctionStorageProviderInterface;
|
|
use Psalm\Plugin\EventHandler\Event\DynamicFunctionStorageProviderEvent;
|
|
use Psalm\Storage\FunctionLikeParameter;
|
|
use Psalm\Type;
|
|
use Psalm\Type\Atomic\TCallable;
|
|
use Psalm\Type\Atomic\TTemplateParam;
|
|
use Psalm\Type\Union;
|
|
|
|
use function array_keys;
|
|
use function array_map;
|
|
use function count;
|
|
|
|
class CustomArrayMapFunctionStorageProvider implements DynamicFunctionStorageProviderInterface
|
|
{
|
|
public static function getFunctionIds(): array
|
|
{
|
|
return ['custom_array_map'];
|
|
}
|
|
|
|
public static function getFunctionStorage(DynamicFunctionStorageProviderEvent $event): ?DynamicFunctionStorage
|
|
{
|
|
$template_provider = $event->getTemplateProvider();
|
|
$arg_type_inferer = $event->getArgTypeInferer();
|
|
$call_args = $event->getArgs();
|
|
$args_count = count($call_args);
|
|
$expected_callable_args_count = $args_count - 1;
|
|
|
|
if ($expected_callable_args_count < 1) {
|
|
return null;
|
|
}
|
|
|
|
$last_arg = $call_args[$args_count - 1];
|
|
|
|
if (!($input_array_type = $arg_type_inferer->infer($last_arg)) ||
|
|
!($input_value_type = self::toValueType($event->getCodebase(), $input_array_type))
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
$all_expected_callables = [
|
|
self::createExpectedCallable($input_value_type, $template_provider),
|
|
...self::createRestCallables($template_provider, $expected_callable_args_count),
|
|
];
|
|
|
|
$custom_array_map_storage = new DynamicFunctionStorage();
|
|
$custom_array_map_storage->templates = self::createTemplates($template_provider, $expected_callable_args_count);
|
|
$custom_array_map_storage->return_type = self::createReturnType($all_expected_callables);
|
|
$custom_array_map_storage->params = [
|
|
...array_map(
|
|
function (TCallable $expected, int $offset) {
|
|
$t = new Union([$expected]);
|
|
$param = new FunctionLikeParameter('fn' . $offset, false, $t, $t);
|
|
$param->is_optional = false;
|
|
|
|
return $param;
|
|
},
|
|
$all_expected_callables,
|
|
array_keys($all_expected_callables)
|
|
),
|
|
self::createLastArrayMapParam($input_array_type)
|
|
];
|
|
|
|
return $custom_array_map_storage;
|
|
}
|
|
|
|
private static function createLastArrayMapParam(Union $input_array_type): FunctionLikeParameter
|
|
{
|
|
return new FunctionLikeParameter(
|
|
'input',
|
|
false,
|
|
$input_array_type,
|
|
$input_array_type,
|
|
null,
|
|
null,
|
|
false
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Resolves value type from array-like type:
|
|
* list<int> -> int
|
|
* list<int|string> -> int|string
|
|
*/
|
|
private static function toValueType(Codebase $codebase, Union $array_like_type): ?Union
|
|
{
|
|
$value_types = [];
|
|
|
|
foreach ($array_like_type->getAtomicTypes() as $atomic) {
|
|
if ($atomic instanceof Type\Atomic\TList) {
|
|
$value_types[] = $atomic->type_param;
|
|
} elseif ($atomic instanceof Type\Atomic\TArray) {
|
|
$value_types[] = $atomic->type_params[1];
|
|
} elseif ($atomic instanceof Type\Atomic\TKeyedArray) {
|
|
$value_types[] = $atomic->getGenericValueType();
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
return Type::combineUnionTypeArray($value_types, $codebase);
|
|
}
|
|
|
|
private static function createExpectedCallable(
|
|
Union $input_type,
|
|
DynamicTemplateProvider $template_provider,
|
|
int $return_template_offset = 0
|
|
): TCallable {
|
|
return new TCallable(
|
|
'callable',
|
|
[new FunctionLikeParameter('a', false, $input_type, $input_type)],
|
|
new Union([
|
|
$template_provider->createTemplate('T' . $return_template_offset)
|
|
])
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @return list<TCallable>
|
|
*/
|
|
private static function createRestCallables(
|
|
DynamicTemplateProvider $template_provider,
|
|
int $expected_callable_args_count
|
|
): array {
|
|
$rest_callable_params = [];
|
|
|
|
for ($template_offset = 0; $template_offset < $expected_callable_args_count - 1; $template_offset++) {
|
|
$rest_callable_params[] = self::createExpectedCallable(
|
|
new Union([
|
|
$template_provider->createTemplate('T' . $template_offset)
|
|
]),
|
|
$template_provider,
|
|
$template_offset + 1
|
|
);
|
|
}
|
|
|
|
return $rest_callable_params;
|
|
}
|
|
|
|
/**
|
|
* Extracts return type for custom_array_map from last callable arg.
|
|
*
|
|
* @param list<TCallable> $all_expected_callables
|
|
*/
|
|
private static function createReturnType(array $all_expected_callables): Union
|
|
{
|
|
$last_callable_arg = $all_expected_callables[count($all_expected_callables) - 1];
|
|
|
|
return new Union([
|
|
new Type\Atomic\TList($last_callable_arg->return_type ?? Type::getMixed())
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Creates variadic template list for custom_array_map function.
|
|
*
|
|
* @return list<TTemplateParam>
|
|
*/
|
|
private static function createTemplates(
|
|
DynamicTemplateProvider $template_provider,
|
|
int $expected_callable_count
|
|
): array {
|
|
$template_params = [];
|
|
|
|
for ($i = 0; $i < $expected_callable_count; $i++) {
|
|
$template_params[] = $template_provider->createTemplate('T' . $i);
|
|
}
|
|
|
|
return $template_params;
|
|
}
|
|
}
|