From cc805de82fa57ace10f2b412858431e14863e3e6 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Tue, 27 Feb 2024 09:52:42 +0100 Subject: [PATCH] Fixup --- .../Expression/Call/ArgumentAnalyzer.php | 132 +++++----- .../Expression/Call/ArgumentsAnalyzer.php | 83 +++---- .../Call/ArrayFunctionArgumentsAnalyzer.php | 228 +++++++++--------- src/Psalm/Type/Atomic/TArray.php | 2 +- src/Psalm/Type/UnionTrait.php | 2 +- 5 files changed, 219 insertions(+), 228 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php index 8c61044b04b..c1d4a3f6b85 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php @@ -122,30 +122,35 @@ public static function checkArgumentMatches( } $param_type = $function_param->type; - + + $param_types = []; if ($function_param->is_variadic && $param_type && $param_type->hasArray() ) { - $array_type = $param_type->getArray(); - - if ($array_type instanceof TKeyedArray && $array_type->is_list) { - $param_type = $array_type->getGenericValueType(); - } elseif ($array_type instanceof TArray) { - $param_type = $array_type->type_params[1]; + foreach ($param_type->getArrays() as $array_type) { + if ($array_type instanceof TKeyedArray && $array_type->is_list) { + $param_types []= $array_type->getGenericValueType(); + } elseif ($array_type instanceof TArray) { + $param_types []= $array_type->type_params[1]; + } } + } elseif ($param_type) { + $param_types = [$param_type]; } - if ($param_type && !$param_type->hasMixed()) { - IssueBuffer::maybeAdd( - new MixedArgument( - 'Argument ' . ($argument_offset + 1) . ' of ' . $cased_method_id - . ' cannot be mixed, expecting ' . $param_type, - new CodeLocation($statements_analyzer->getSource(), $arg->value), - $cased_method_id, - ), - $statements_analyzer->getSuppressedIssues(), - ); + foreach ($param_types as $param_type) { + if ($param_type->hasMixed()) { + IssueBuffer::maybeAdd( + new MixedArgument( + 'Argument ' . ($argument_offset + 1) . ' of ' . $cased_method_id + . ' cannot be mixed, expecting ' . $param_type, + new CodeLocation($statements_analyzer->getSource(), $arg->value), + $cased_method_id, + ), + $statements_analyzer->getSuppressedIssues(), + ); + } } } @@ -472,62 +477,62 @@ private static function checkFunctionLikeTypeMatches( } if ($arg_value_type->hasArray()) { - $unpacked_atomic_array = $arg_value_type->getArray(); $arg_key_allowed = true; + foreach ($arg_value_type->getArrays() as $unpacked_atomic_array) { + if ($unpacked_atomic_array instanceof TKeyedArray) { + if (!$allow_named_args && !$unpacked_atomic_array->getGenericKeyType()->isInt()) { + $arg_key_allowed = false; + } - if ($unpacked_atomic_array instanceof TKeyedArray) { - if (!$allow_named_args && !$unpacked_atomic_array->getGenericKeyType()->isInt()) { - $arg_key_allowed = false; - } - - if ($function_param->is_variadic) { - $arg_value_type = $unpacked_atomic_array->getGenericValueType(); - } elseif ($codebase->analysis_php_version_id >= 8_00_00 + if ($function_param->is_variadic) { + $arg_value_type = $unpacked_atomic_array->getGenericValueType(); + } elseif ($codebase->analysis_php_version_id >= 8_00_00 && $allow_named_args && isset($unpacked_atomic_array->properties[$function_param->name]) - ) { - $arg_value_type = $unpacked_atomic_array->properties[$function_param->name]; - } elseif ($unpacked_atomic_array->is_list + ) { + $arg_value_type = $unpacked_atomic_array->properties[$function_param->name]; + } elseif ($unpacked_atomic_array->is_list && isset($unpacked_atomic_array->properties[$unpacked_argument_offset]) - ) { - $arg_value_type = $unpacked_atomic_array->properties[$unpacked_argument_offset]; - } elseif ($unpacked_atomic_array->fallback_params) { - $arg_value_type = $unpacked_atomic_array->fallback_params[1]; - } elseif ($function_param->is_optional && $function_param->default_type) { - if ($function_param->default_type instanceof Union) { - $arg_value_type = $function_param->default_type; - } else { - $arg_value_type_atomic = ConstantTypeResolver::resolve( - $codebase->classlikes, - $function_param->default_type, - $statements_analyzer, - ); + ) { + $arg_value_type = $unpacked_atomic_array->properties[$unpacked_argument_offset]; + } elseif ($unpacked_atomic_array->fallback_params) { + $arg_value_type = $unpacked_atomic_array->fallback_params[1]; + } elseif ($function_param->is_optional && $function_param->default_type) { + if ($function_param->default_type instanceof Union) { + $arg_value_type = $function_param->default_type; + } else { + $arg_value_type_atomic = ConstantTypeResolver::resolve( + $codebase->classlikes, + $function_param->default_type, + $statements_analyzer, + ); - $arg_value_type = new Union([$arg_value_type_atomic]); + $arg_value_type = new Union([$arg_value_type_atomic]); + } + } else { + $arg_value_type = Type::getMixed(); } - } else { + } elseif ($unpacked_atomic_array instanceof TClassStringMap) { $arg_value_type = Type::getMixed(); + } else { + if (!$allow_named_args && !$unpacked_atomic_array->type_params[0]->isInt()) { + $arg_key_allowed = false; + } + $arg_value_type = $unpacked_atomic_array->type_params[1]; } - } elseif ($unpacked_atomic_array instanceof TClassStringMap) { - $arg_value_type = Type::getMixed(); - } else { - if (!$allow_named_args && !$unpacked_atomic_array->type_params[0]->isInt()) { - $arg_key_allowed = false; - } - $arg_value_type = $unpacked_atomic_array->type_params[1]; - } - if (!$arg_key_allowed) { - IssueBuffer::maybeAdd( - new NamedArgumentNotAllowed( - 'Method ' . $cased_method_id + if (!$arg_key_allowed) { + IssueBuffer::maybeAdd( + new NamedArgumentNotAllowed( + 'Method ' . $cased_method_id . ' called with named unpacked array ' . $unpacked_atomic_array->getId() . ' (array with string keys)', - new CodeLocation($statements_analyzer->getSource(), $arg->value), - $cased_method_id, - ), - $statements_analyzer->getSuppressedIssues(), - ); + new CodeLocation($statements_analyzer->getSource(), $arg->value), + $cased_method_id, + ), + $statements_analyzer->getSuppressedIssues(), + ); + } } } else { $non_iterable = false; @@ -1246,9 +1251,8 @@ private static function verifyExplicitParam( } } elseif ($param_type_part instanceof TCallable) { $can_be_callable_like_array = false; - if ($param_type->hasArray()) { - $param_array_type = $param_type->getArray(); - + + foreach ($param_type->getArrays() as $param_array_type) { $row_type = null; if ($param_array_type instanceof TArray) { $row_type = $param_array_type->type_params[1]; diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php index 2cbbd3faf49..eeccc5b3f84 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php @@ -722,56 +722,53 @@ public static function checkArgumentsMatch( if (($arg_value_type = $statements_analyzer->node_data->getType($arg->value)) && $arg_value_type->hasArray()) { - /** - * @var TArray|TKeyedArray - */ - $array_type = $arg_value_type->getArray(); - - if ($array_type instanceof TKeyedArray) { - $array_type = $array_type->getGenericArrayType(); - $key_types = $array_type->type_params[0]->getAtomicTypes(); - - foreach ($key_types as $key_type) { - if (!$key_type instanceof TLiteralString - || ($function_storage && !$function_storage->allow_named_arg_calls)) { - continue; - } + foreach ($arg_value_type->getArrays() as $array_type) { + if ($array_type instanceof TKeyedArray) { + $array_type = $array_type->getGenericArrayType(); + $key_types = $array_type->type_params[0]->getAtomicTypes(); + + foreach ($key_types as $key_type) { + if (!$key_type instanceof TLiteralString + || ($function_storage && !$function_storage->allow_named_arg_calls)) { + continue; + } - $param_found = false; - - foreach ($function_params as $candidate_param) { - if ($candidate_param->name === $key_type->value || $candidate_param->is_variadic) { - if ($candidate_param->name === $key_type->value) { - if (isset($matched_args[$candidate_param->name])) { - IssueBuffer::maybeAdd( - new InvalidNamedArgument( - 'Parameter $' . $key_type->value . ' has already been used in ' - . ($cased_method_id ?: $method_id), - new CodeLocation($statements_analyzer, $arg), - (string)$method_id, - ), - $statements_analyzer->getSuppressedIssues(), - ); + $param_found = false; + + foreach ($function_params as $candidate_param) { + if ($candidate_param->name === $key_type->value || $candidate_param->is_variadic) { + if ($candidate_param->name === $key_type->value) { + if (isset($matched_args[$candidate_param->name])) { + IssueBuffer::maybeAdd( + new InvalidNamedArgument( + 'Parameter $' . $key_type->value . ' has already been used in ' + . ($cased_method_id ?: $method_id), + new CodeLocation($statements_analyzer, $arg), + (string)$method_id, + ), + $statements_analyzer->getSuppressedIssues(), + ); + } + + $matched_args[$candidate_param->name] = true; } - $matched_args[$candidate_param->name] = true; + $param_found = true; + break; } - - $param_found = true; - break; } - } - if (!$param_found) { - IssueBuffer::maybeAdd( - new InvalidNamedArgument( - 'Parameter $' . $key_type->value . ' does not exist on function ' + if (!$param_found) { + IssueBuffer::maybeAdd( + new InvalidNamedArgument( + 'Parameter $' . $key_type->value . ' does not exist on function ' . ($cased_method_id ?: $method_id), - new CodeLocation($statements_analyzer, $arg), - (string)$method_id, - ), - $statements_analyzer->getSuppressedIssues(), - ); + new CodeLocation($statements_analyzer, $arg), + (string)$method_id, + ), + $statements_analyzer->getSuppressedIssues(), + ); + } } } } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php index a8465bf5656..2bee3430904 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php @@ -81,20 +81,8 @@ public static function checkArgumentsMatch( if ($i === 1 && $method_id === 'array_filter') { break; } - - /** - * @var TKeyedArray|TArray|null - */ - $array_arg_type = ($arg_value_type = $statements_analyzer->node_data->getType($arg->value)) - && $arg_value_type->hasArray() - ? $arg_value_type->getArray() - : null; - - if ($array_arg_type instanceof TKeyedArray) { - $array_arg_type = $array_arg_type->getGenericArrayType(); - } - - $array_arg_types[] = $array_arg_type; + $arg_value_type = $statements_analyzer->node_data->getType($arg->value); + $array_arg_types []= $arg_value_type; } $closure_arg = $args[$closure_index] ?? null; @@ -727,7 +715,7 @@ public static function handleByRefArrayAdjustment( } /** - * @param (TArray|null)[] $array_arg_types + * @param list $array_arg_types */ private static function checkClosureType( StatementsAnalyzer $statements_analyzer, @@ -893,7 +881,7 @@ private static function checkClosureType( /** * @param TClosure|TCallable $closure_type - * @param (TArray|null)[] $array_arg_types + * @param list $array_arg_types */ private static function checkClosureTypeArgs( StatementsAnalyzer $statements_analyzer, @@ -963,136 +951,138 @@ private static function checkClosureTypeArgs( continue; } - $array_arg_type = $array_arg_types[$i]; - - $input_type = $array_arg_type->type_params[1]; + foreach ($array_arg_types[$i]->getArrays() as $array_arg_type) { + $input_type = $array_arg_type instanceof TKeyedArray + ? $array_arg_type->getGenericValueType() + : $array_arg_type->type_params[1]; - if ($input_type->hasMixed()) { - continue; - } + if ($input_type->hasMixed()) { + continue; + } - $closure_param_type = $closure_param->type; + $closure_param_type = $closure_param->type; - if (!$closure_param_type) { - continue; - } + if (!$closure_param_type) { + continue; + } - if ($method_id === 'array_map' + if ($method_id === 'array_map' && $i === 0 && $closure_type->return_type && $closure_param_type->hasTemplate() - ) { - $template_result = new TemplateResult( - [], - [], - ); + ) { + $template_result = new TemplateResult( + [], + [], + ); + + foreach ($closure_param_type->getTemplateTypes() as $template_type) { + $template_result->template_types[$template_type->param_name] = [ + ($template_type->defining_class) => $template_type->as, + ]; + } - foreach ($closure_param_type->getTemplateTypes() as $template_type) { - $template_result->template_types[$template_type->param_name] = [ - ($template_type->defining_class) => $template_type->as, - ]; + $closure_param_type = TemplateStandinTypeReplacer::replace( + $closure_param_type, + $template_result, + $codebase, + $statements_analyzer, + $input_type, + $i, + $context->self, + $context->calling_method_id ?: $context->calling_function_id, + ); + + $closure_type = $closure_type->replaceTemplateTypesWithArgTypes( + $template_result, + $codebase, + ); } - $closure_param_type = TemplateStandinTypeReplacer::replace( - $closure_param_type, - $template_result, + $closure_param_type = TypeExpander::expandUnion( $codebase, - $statements_analyzer, - $input_type, - $i, + $closure_param_type, $context->self, - $context->calling_method_id ?: $context->calling_function_id, + null, + $statements_analyzer->getParentFQCLN(), ); - $closure_type = $closure_type->replaceTemplateTypesWithArgTypes( - $template_result, + $union_comparison_results = new TypeComparisonResult(); + + $type_match_found = UnionTypeComparator::isContainedBy( $codebase, + $input_type, + $closure_param_type, + $input_type->ignore_nullable_issues, + $input_type->ignore_falsable_issues, + $union_comparison_results, ); - } - $closure_param_type = TypeExpander::expandUnion( - $codebase, - $closure_param_type, - $context->self, - null, - $statements_analyzer->getParentFQCLN(), - ); - - $union_comparison_results = new TypeComparisonResult(); - - $type_match_found = UnionTypeComparator::isContainedBy( - $codebase, - $input_type, - $closure_param_type, - $input_type->ignore_nullable_issues, - $input_type->ignore_falsable_issues, - $union_comparison_results, - ); - - if ($union_comparison_results->type_coerced) { - if ($union_comparison_results->type_coerced_from_mixed) { - IssueBuffer::maybeAdd( - new MixedArgumentTypeCoercion( - 'Parameter ' . ($i + 1) . ' of closure passed to function ' . $method_id . ' expects ' . + if ($union_comparison_results->type_coerced) { + if ($union_comparison_results->type_coerced_from_mixed) { + IssueBuffer::maybeAdd( + new MixedArgumentTypeCoercion( + 'Parameter ' . ($i + 1) . ' of closure passed to function ' . $method_id . ' expects ' . $closure_param_type->getId() . ', but parent type ' . $input_type->getId() . ' provided', - new CodeLocation($statements_analyzer->getSource(), $closure_arg), - $method_id, - ), - $statements_analyzer->getSuppressedIssues(), - ); - } else { - IssueBuffer::maybeAdd( - new ArgumentTypeCoercion( - 'Parameter ' . ($i + 1) . ' of closure passed to function ' . $method_id . ' expects ' . + new CodeLocation($statements_analyzer->getSource(), $closure_arg), + $method_id, + ), + $statements_analyzer->getSuppressedIssues(), + ); + } else { + IssueBuffer::maybeAdd( + new ArgumentTypeCoercion( + 'Parameter ' . ($i + 1) . ' of closure passed to function ' . $method_id . ' expects ' . $closure_param_type->getId() . ', but parent type ' . $input_type->getId() . ' provided', - new CodeLocation($statements_analyzer->getSource(), $closure_arg), - $method_id, - ), - $statements_analyzer->getSuppressedIssues(), - ); + new CodeLocation($statements_analyzer->getSource(), $closure_arg), + $method_id, + ), + $statements_analyzer->getSuppressedIssues(), + ); + } } - } - if (!$union_comparison_results->type_coerced && !$type_match_found) { - $types_can_be_identical = UnionTypeComparator::canExpressionTypesBeIdentical( - $codebase, - $input_type, - $closure_param_type, - ); + if (!$union_comparison_results->type_coerced && !$type_match_found) { + $types_can_be_identical = UnionTypeComparator::canExpressionTypesBeIdentical( + $codebase, + $input_type, + $closure_param_type, + ); - if ($union_comparison_results->scalar_type_match_found) { - IssueBuffer::maybeAdd( - new InvalidScalarArgument( - 'Parameter ' . ($i + 1) . ' of closure passed to function ' . $method_id . ' expects ' . + if ($union_comparison_results->scalar_type_match_found) { + IssueBuffer::maybeAdd( + new InvalidScalarArgument( + 'Parameter ' . ($i + 1) . ' of closure passed to function ' . $method_id . ' expects ' . $closure_param_type->getId() . ', but ' . $input_type->getId() . ' provided', - new CodeLocation($statements_analyzer->getSource(), $closure_arg), - $method_id, - ), - $statements_analyzer->getSuppressedIssues(), - ); - } elseif ($types_can_be_identical) { - IssueBuffer::maybeAdd( - new PossiblyInvalidArgument( - 'Parameter ' . ($i + 1) . ' of closure passed to function ' . $method_id . ' expects ' + new CodeLocation($statements_analyzer->getSource(), $closure_arg), + $method_id, + ), + $statements_analyzer->getSuppressedIssues(), + ); + } elseif ($types_can_be_identical) { + IssueBuffer::maybeAdd( + new PossiblyInvalidArgument( + 'Parameter ' . ($i + 1) . ' of closure passed to function ' . $method_id . ' expects ' . $closure_param_type->getId() . ', but possibly different type ' . $input_type->getId() . ' provided', - new CodeLocation($statements_analyzer->getSource(), $closure_arg), - $method_id, - ), - $statements_analyzer->getSuppressedIssues(), - ); - } else { - IssueBuffer::maybeAdd( - new InvalidArgument( - 'Parameter ' . ($i + 1) . ' of closure passed to function ' . $method_id . ' expects ' . - $closure_param_type->getId() . ', but ' . $input_type->getId() . ' provided', - new CodeLocation($statements_analyzer->getSource(), $closure_arg), - $method_id, - ), - $statements_analyzer->getSuppressedIssues(), - ); + new CodeLocation($statements_analyzer->getSource(), $closure_arg), + $method_id, + ), + $statements_analyzer->getSuppressedIssues(), + ); + } else { + IssueBuffer::maybeAdd( + new InvalidArgument( + 'Parameter ' . ($i + 1) . ' of closure passed to function ' . $method_id . ' expects ' . + $closure_param_type->getId() . ', but ' . $input_type->getId() . ' provided', + new CodeLocation($statements_analyzer->getSource(), $closure_arg), + $method_id, + ), + $statements_analyzer->getSuppressedIssues(), + ); + } } } } diff --git a/src/Psalm/Type/Atomic/TArray.php b/src/Psalm/Type/Atomic/TArray.php index f10a3de6798..a2f8b03e3ac 100644 --- a/src/Psalm/Type/Atomic/TArray.php +++ b/src/Psalm/Type/Atomic/TArray.php @@ -7,8 +7,8 @@ use Psalm\Codebase; use Psalm\Internal\Analyzer\StatementsAnalyzer; use Psalm\Internal\Type\TemplateResult; -use Psalm\Type; use Psalm\Storage\UnserializeMemoryUsageSuppressionTrait; +use Psalm\Type; use Psalm\Type\Atomic; use Psalm\Type\Union; diff --git a/src/Psalm/Type/UnionTrait.php b/src/Psalm/Type/UnionTrait.php index bcbfd7125e4..107771ef0f2 100644 --- a/src/Psalm/Type/UnionTrait.php +++ b/src/Psalm/Type/UnionTrait.php @@ -412,7 +412,7 @@ public function hasArray(): bool } /** - * @return Generator + * @return Generator */ public function getArrays(): Generator {