mirror of
https://github.com/danog/psalm.git
synced 2024-11-27 04:45:20 +01:00
Merge pull request #7167 from AndrolGenhald/bugfix/7164-conflicting-fake-variable
This commit is contained in:
commit
7e97c5c84e
@ -29,6 +29,7 @@ use function array_map;
|
||||
use function array_slice;
|
||||
use function count;
|
||||
use function is_string;
|
||||
use function mt_rand;
|
||||
use function reset;
|
||||
use function spl_object_id;
|
||||
|
||||
@ -185,13 +186,15 @@ class ArrayFilterReturnTypeProvider implements FunctionReturnTypeProviderInterfa
|
||||
if ($array_arg && $mapping_function_ids) {
|
||||
$assertions = [];
|
||||
|
||||
$fake_var_discriminator = mt_rand();
|
||||
ArrayMapReturnTypeProvider::getReturnTypeFromMappingIds(
|
||||
$statements_source,
|
||||
$mapping_function_ids,
|
||||
$context,
|
||||
$function_call_arg,
|
||||
array_slice($call_args, 0, 1),
|
||||
$assertions
|
||||
$assertions,
|
||||
$fake_var_discriminator
|
||||
);
|
||||
|
||||
$array_var_id = ExpressionIdentifier::getArrayVarId(
|
||||
@ -200,10 +203,13 @@ class ArrayFilterReturnTypeProvider implements FunctionReturnTypeProviderInterfa
|
||||
$statements_source
|
||||
);
|
||||
|
||||
if (isset($assertions[$array_var_id . '[$__fake_offset_var__]'])) {
|
||||
if (isset($assertions[$array_var_id . "[\$__fake_{$fake_var_discriminator}_offset_var__]"])) {
|
||||
$changed_var_ids = [];
|
||||
|
||||
$assertions = ['$inner_type' => $assertions[$array_var_id . '[$__fake_offset_var__]']];
|
||||
$assertions = [
|
||||
'$inner_type' =>
|
||||
$assertions["{$array_var_id}[\$__fake_{$fake_var_discriminator}_offset_var__]"],
|
||||
];
|
||||
|
||||
$reconciled_types = Reconciler::reconcileKeyedTypes(
|
||||
$assertions,
|
||||
@ -221,6 +227,8 @@ class ArrayFilterReturnTypeProvider implements FunctionReturnTypeProviderInterfa
|
||||
$inner_type = $reconciled_types['$inner_type'];
|
||||
}
|
||||
}
|
||||
|
||||
ArrayMapReturnTypeProvider::cleanContext($context, $fake_var_discriminator);
|
||||
}
|
||||
} elseif (($function_call_arg->value instanceof PhpParser\Node\Expr\Closure
|
||||
|| $function_call_arg->value instanceof PhpParser\Node\Expr\ArrowFunction)
|
||||
|
@ -39,7 +39,9 @@ use function array_slice;
|
||||
use function count;
|
||||
use function explode;
|
||||
use function in_array;
|
||||
use function mt_rand;
|
||||
use function reset;
|
||||
use function str_contains;
|
||||
use function strpos;
|
||||
use function substr;
|
||||
|
||||
@ -340,6 +342,8 @@ class ArrayMapReturnTypeProvider implements FunctionReturnTypeProviderInterface
|
||||
/**
|
||||
* @param non-empty-array<int, string> $mapping_function_ids
|
||||
* @param list<PhpParser\Node\Arg> $array_args
|
||||
* @param int|null $fake_var_discriminator Set the fake variable id to a known value with the discriminator
|
||||
* as a substring, and don't clear it from the context.
|
||||
* @param-out array<string, array<array<int, string>>>|null $assertions
|
||||
*/
|
||||
public static function getReturnTypeFromMappingIds(
|
||||
@ -348,15 +352,23 @@ class ArrayMapReturnTypeProvider implements FunctionReturnTypeProviderInterface
|
||||
Context $context,
|
||||
PhpParser\Node\Arg $function_call_arg,
|
||||
array $array_args,
|
||||
?array &$assertions = null
|
||||
?array &$assertions = null,
|
||||
?int $fake_var_discriminator = null
|
||||
): Union {
|
||||
$mapping_return_type = null;
|
||||
|
||||
$codebase = $statements_source->getCodebase();
|
||||
|
||||
$clean_context = false;
|
||||
|
||||
foreach ($mapping_function_ids as $mapping_function_id) {
|
||||
$mapping_function_id_parts = explode('&', $mapping_function_id);
|
||||
|
||||
if ($fake_var_discriminator === null) {
|
||||
$fake_var_discriminator = mt_rand();
|
||||
$clean_context = true;
|
||||
}
|
||||
|
||||
foreach ($mapping_function_id_parts as $mapping_function_id_part) {
|
||||
$fake_args = [];
|
||||
|
||||
@ -365,7 +377,7 @@ class ArrayMapReturnTypeProvider implements FunctionReturnTypeProviderInterface
|
||||
new VirtualArrayDimFetch(
|
||||
$array_arg->value,
|
||||
new VirtualVariable(
|
||||
'__fake_offset_var__',
|
||||
"__fake_{$fake_var_discriminator}_offset_var__",
|
||||
$array_arg->value->getAttributes()
|
||||
),
|
||||
$array_arg->value->getAttributes()
|
||||
@ -390,7 +402,7 @@ class ArrayMapReturnTypeProvider implements FunctionReturnTypeProviderInterface
|
||||
if ($is_instance) {
|
||||
$fake_method_call = new VirtualMethodCall(
|
||||
new VirtualVariable(
|
||||
'__fake_method_call_var__',
|
||||
"__fake_{$fake_var_discriminator}_method_call_var__",
|
||||
$function_call_arg->getAttributes()
|
||||
),
|
||||
new VirtualIdentifier(
|
||||
@ -416,11 +428,9 @@ class ArrayMapReturnTypeProvider implements FunctionReturnTypeProviderInterface
|
||||
}
|
||||
}
|
||||
|
||||
$context->vars_in_scope['$__fake_offset_var__'] = Type::getMixed();
|
||||
$context->vars_in_scope['$__fake_method_call_var__'] = $lhs_instance_type
|
||||
?: new Union([
|
||||
new TNamedObject($callable_fq_class_name)
|
||||
]);
|
||||
$context->vars_in_scope["\$__fake_{$fake_var_discriminator}_offset_var__"] = Type::getMixed();
|
||||
$context->vars_in_scope["\$__fake_{$fake_var_discriminator}_method_call_var__"] =
|
||||
$lhs_instance_type ?: new Union([new TNamedObject($callable_fq_class_name)]);
|
||||
|
||||
$fake_method_return_type = self::executeFakeCall(
|
||||
$statements_source,
|
||||
@ -428,9 +438,6 @@ class ArrayMapReturnTypeProvider implements FunctionReturnTypeProviderInterface
|
||||
$context,
|
||||
$assertions
|
||||
);
|
||||
|
||||
unset($context->vars_in_scope['$__fake_offset_var__']);
|
||||
unset($context->vars_in_scope['$__method_call_var__']);
|
||||
} else {
|
||||
$fake_method_call = new VirtualStaticCall(
|
||||
new VirtualFullyQualified(
|
||||
@ -445,7 +452,7 @@ class ArrayMapReturnTypeProvider implements FunctionReturnTypeProviderInterface
|
||||
$function_call_arg->getAttributes()
|
||||
);
|
||||
|
||||
$context->vars_in_scope['$__fake_offset_var__'] = Type::getMixed();
|
||||
$context->vars_in_scope["\$__fake_{$fake_var_discriminator}_offset_var__"] = Type::getMixed();
|
||||
|
||||
$fake_method_return_type = self::executeFakeCall(
|
||||
$statements_source,
|
||||
@ -453,8 +460,6 @@ class ArrayMapReturnTypeProvider implements FunctionReturnTypeProviderInterface
|
||||
$context,
|
||||
$assertions
|
||||
);
|
||||
|
||||
unset($context->vars_in_scope['$__fake_offset_var__']);
|
||||
}
|
||||
|
||||
$function_id_return_type = $fake_method_return_type ?? Type::getMixed();
|
||||
@ -468,7 +473,7 @@ class ArrayMapReturnTypeProvider implements FunctionReturnTypeProviderInterface
|
||||
$function_call_arg->getAttributes()
|
||||
);
|
||||
|
||||
$context->vars_in_scope['$__fake_offset_var__'] = Type::getMixed();
|
||||
$context->vars_in_scope["\$__fake_{$fake_var_discriminator}_offset_var__"] = Type::getMixed();
|
||||
|
||||
$fake_function_return_type = self::executeFakeCall(
|
||||
$statements_source,
|
||||
@ -477,12 +482,16 @@ class ArrayMapReturnTypeProvider implements FunctionReturnTypeProviderInterface
|
||||
$assertions
|
||||
);
|
||||
|
||||
unset($context->vars_in_scope['$__fake_offset_var__']);
|
||||
|
||||
$function_id_return_type = $fake_function_return_type ?? Type::getMixed();
|
||||
}
|
||||
}
|
||||
|
||||
if ($clean_context) {
|
||||
self::cleanContext($context, $fake_var_discriminator);
|
||||
}
|
||||
|
||||
$fake_var_discriminator = null;
|
||||
|
||||
$mapping_return_type = Type::combineUnionTypes(
|
||||
$function_id_return_type,
|
||||
$mapping_return_type,
|
||||
@ -492,4 +501,13 @@ class ArrayMapReturnTypeProvider implements FunctionReturnTypeProviderInterface
|
||||
|
||||
return $mapping_return_type;
|
||||
}
|
||||
|
||||
public static function cleanContext(Context $context, int $fake_var_discriminator): void
|
||||
{
|
||||
foreach ($context->vars_in_scope as $var_in_scope => $_) {
|
||||
if (str_contains($var_in_scope, "__fake_{$fake_var_discriminator}_")) {
|
||||
unset($context->vars_in_scope[$var_in_scope]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1065,6 +1065,29 @@ class ReturnTypeTest extends TestCase
|
||||
}
|
||||
}'
|
||||
],
|
||||
'nestedArrayMapReturnTypeDoesntCrash' => [
|
||||
'<?php
|
||||
function bar(array $a): array {
|
||||
return $a;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array[] $x
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
function foo(array $x): array {
|
||||
return array_map(
|
||||
"array_merge",
|
||||
array_map(
|
||||
"bar",
|
||||
$x
|
||||
),
|
||||
$x
|
||||
);
|
||||
}
|
||||
',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user