From 9089f7717614687b31c52f96ced20fb3dfb98cd1 Mon Sep 17 00:00:00 2001 From: Matt Brown Date: Fri, 27 Nov 2020 11:43:23 -0500 Subject: [PATCH] Turn template bound tuples into object Ref #4714 --- src/Psalm/Internal/Analyzer/ClassAnalyzer.php | 10 +- .../Internal/Analyzer/ClassLikeAnalyzer.php | 2 +- .../Internal/Analyzer/CommentAnalyzer.php | 4 +- src/Psalm/Internal/Analyzer/FileAnalyzer.php | 2 +- .../Analyzer/FunctionLikeAnalyzer.php | 4 +- .../Internal/Analyzer/MethodComparator.php | 2 +- .../Internal/Analyzer/SourceAnalyzer.php | 2 +- .../Statements/Block/ForeachAnalyzer.php | 10 +- .../Assignment/ArrayAssignmentAnalyzer.php | 20 ++-- .../Expression/Call/ArgumentAnalyzer.php | 27 +++--- .../Expression/Call/ArgumentsAnalyzer.php | 34 ++++--- .../Call/ArrayFunctionArgumentsAnalyzer.php | 2 +- .../Call/ClassTemplateParamCollector.php | 38 ++++---- .../Expression/Call/FunctionCallAnalyzer.php | 25 +++-- .../Method/MethodCallReturnTypeFetcher.php | 17 ++-- .../Expression/Call/NewAnalyzer.php | 17 +++- .../ExistingAtomicStaticCallAnalyzer.php | 27 +++--- .../Statements/Expression/CallAnalyzer.php | 92 +++++++++++-------- .../Expression/Fetch/ArrayFetchAnalyzer.php | 50 +++++----- .../Fetch/AtomicPropertyFetchAnalyzer.php | 17 ++-- src/Psalm/Internal/Codebase/Populator.php | 8 +- src/Psalm/Internal/Codebase/Reflection.php | 4 +- .../Reflector/ClassLikeNodeScanner.php | 6 +- .../Reflector/FunctionLikeDocblockScanner.php | 36 +++----- .../Reflector/FunctionLikeNodeScanner.php | 4 +- .../Generator/ClassLikeStubGenerator.php | 4 +- .../Stubs/Generator/StubsGenerator.php | 2 +- .../Internal/Type/AssertionReconciler.php | 6 +- .../Type/NegatedAssertionReconciler.php | 2 +- .../Type/SimpleNegatedAssertionReconciler.php | 2 +- src/Psalm/Internal/Type/TemplateBound.php | 40 ++++++++ src/Psalm/Internal/Type/TemplateResult.php | 24 +++-- src/Psalm/Internal/Type/TypeParser.php | 20 ++-- .../Internal/Type/UnionTemplateHandler.php | 71 +++++++------- .../Internal/TypeVisitor/TypeChecker.php | 2 +- src/Psalm/StatementsSource.php | 2 +- src/Psalm/Storage/Assertion.php | 14 +-- src/Psalm/Storage/ClassLikeStorage.php | 2 +- src/Psalm/Storage/FunctionLikeStorage.php | 2 +- src/Psalm/Type.php | 2 +- src/Psalm/Type/Atomic.php | 4 +- .../Type/Atomic/HasIntersectionTrait.php | 2 +- src/Psalm/Type/Reconciler.php | 2 +- src/Psalm/Type/Union.php | 33 +++---- tests/TypeParseTest.php | 30 +++--- 45 files changed, 392 insertions(+), 334 deletions(-) create mode 100644 src/Psalm/Internal/Type/TemplateBound.php diff --git a/src/Psalm/Internal/Analyzer/ClassAnalyzer.php b/src/Psalm/Internal/Analyzer/ClassAnalyzer.php index 1faaf6812..8ab8974e3 100644 --- a/src/Psalm/Internal/Analyzer/ClassAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/ClassAnalyzer.php @@ -2046,7 +2046,7 @@ class ClassAnalyzer extends ClassLikeAnalyzer $template_params[] = new Type\Union([ new Type\Atomic\TTemplateParam( $param_name, - \reset($template_map)[0], + \reset($template_map), $key ) ]); @@ -2212,8 +2212,8 @@ class ClassAnalyzer extends ClassLikeAnalyzer } } - if (!$template_type[0]->isMixed()) { - $template_type_copy = clone $template_type[0]; + if (!$template_type->isMixed()) { + $template_type_copy = clone $template_type; $template_result = new \Psalm\Internal\Type\TemplateResult( $previous_extended ?: [], @@ -2244,12 +2244,12 @@ class ClassAnalyzer extends ClassLikeAnalyzer } } else { $previous_extended[$template_name] = [ - $declaring_class => [$extended_type] + $declaring_class => $extended_type ]; } } else { $previous_extended[$template_name] = [ - $declaring_class => [$extended_type] + $declaring_class => $extended_type ]; } } diff --git a/src/Psalm/Internal/Analyzer/ClassLikeAnalyzer.php b/src/Psalm/Internal/Analyzer/ClassLikeAnalyzer.php index b7d3bbc1d..8cb36b206 100644 --- a/src/Psalm/Internal/Analyzer/ClassLikeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/ClassLikeAnalyzer.php @@ -455,7 +455,7 @@ abstract class ClassLikeAnalyzer extends SourceAnalyzer } /** - * @return array>|null + * @return array>|null */ public function getTemplateTypeMap(): ?array { diff --git a/src/Psalm/Internal/Analyzer/CommentAnalyzer.php b/src/Psalm/Internal/Analyzer/CommentAnalyzer.php index 863df909b..71398e5b0 100644 --- a/src/Psalm/Internal/Analyzer/CommentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/CommentAnalyzer.php @@ -35,7 +35,7 @@ class CommentAnalyzer public const TYPE_REGEX = '(\??\\\?[\(\)A-Za-z0-9_&\<\.=,\>\[\]\-\{\}:|?\\\\]*|\$[a-zA-Z_0-9_]+)'; /** - * @param array>|null $template_type_map + * @param array>|null $template_type_map * @param array $type_aliases * * @throws DocblockParseException if there was a problem parsing the docblock @@ -62,7 +62,7 @@ class CommentAnalyzer } /** - * @param array>|null $template_type_map + * @param array>|null $template_type_map * @param array $type_aliases * * @return list diff --git a/src/Psalm/Internal/Analyzer/FileAnalyzer.php b/src/Psalm/Internal/Analyzer/FileAnalyzer.php index 14d62585f..6c20a2826 100644 --- a/src/Psalm/Internal/Analyzer/FileAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/FileAnalyzer.php @@ -603,7 +603,7 @@ class FileAnalyzer extends SourceAnalyzer } /** - * @return array>|null + * @return array>|null */ public function getTemplateTypeMap(): ?array { diff --git a/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php b/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php index b057b6a3b..f10ff9241 100644 --- a/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php @@ -224,7 +224,7 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer $template_params[] = new Type\Union([ new Type\Atomic\TTemplateParam( $param_name, - \reset($template_map)[0], + \reset($template_map), $key ) ]); @@ -1874,7 +1874,7 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer } /** - * @return array>|null + * @return array>|null */ public function getTemplateTypeMap(): ?array { diff --git a/src/Psalm/Internal/Analyzer/MethodComparator.php b/src/Psalm/Internal/Analyzer/MethodComparator.php index 62ff319f5..a50d77692 100644 --- a/src/Psalm/Internal/Analyzer/MethodComparator.php +++ b/src/Psalm/Internal/Analyzer/MethodComparator.php @@ -1064,7 +1064,7 @@ class MethodComparator } } - $template_types[$key][$base_class_name] = [$mapped_type]; + $template_types[$key][$base_class_name] = $mapped_type; } } diff --git a/src/Psalm/Internal/Analyzer/SourceAnalyzer.php b/src/Psalm/Internal/Analyzer/SourceAnalyzer.php index 5cbc91af3..eb4aa6647 100644 --- a/src/Psalm/Internal/Analyzer/SourceAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/SourceAnalyzer.php @@ -166,7 +166,7 @@ abstract class SourceAnalyzer implements StatementsSource } /** - * @return array>|null + * @return array>|null */ public function getTemplateTypeMap(): ?array { diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php index eeb18ab2d..c0a08bdfb 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php @@ -1005,13 +1005,9 @@ class ForeachAnalyzer ? $iterator_atomic_type->type_params : array_values( array_map( - /** @param array $arr */ + /** @param array $arr */ function (array $arr) use ($iterator_atomic_type) : Type\Union { - if (isset($arr[$iterator_atomic_type->value])) { - return $arr[$iterator_atomic_type->value][0]; - } - - return Type::getMixed(); + return $arr[$iterator_atomic_type->value] ?? Type::getMixed(); }, $generic_storage->template_types ) @@ -1096,7 +1092,7 @@ class ForeachAnalyzer /** * @param array> $template_type_extends - * @param array> $class_template_types + * @param array> $class_template_types * @param array $calling_type_params */ private static function getExtendedType( diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php index 5b48fe80e..b09d79996 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php @@ -508,17 +508,15 @@ class ArrayAssignmentAnalyzer [], [ $offset_type_part->param_name => [ - $offset_type_part->defining_class => [ - new Type\Union([ - new Type\Atomic\TTemplateParam( - $class_string_map->param_name, - $offset_type_part->as_type - ? new Type\Union([$offset_type_part->as_type]) - : Type::getObject(), - 'class-string-map' - ) - ]) - ] + $offset_type_part->defining_class => new Type\Union([ + new Type\Atomic\TTemplateParam( + $class_string_map->param_name, + $offset_type_part->as_type + ? new Type\Union([$offset_type_part->as_type]) + : Type::getObject(), + 'class-string-map' + ) + ]) ] ] ); diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php index 79d77cc6f..deda0dfab 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentAnalyzer.php @@ -16,6 +16,7 @@ use Psalm\Internal\DataFlow\TaintSink; use Psalm\Internal\DataFlow\DataFlowNode; use Psalm\Internal\Codebase\TaintFlowGraph; use Psalm\Internal\MethodIdentifier; +use Psalm\Internal\Type\TemplateBound; use Psalm\Internal\Type\TemplateResult; use Psalm\Internal\Type\UnionTemplateHandler; use Psalm\CodeLocation; @@ -50,7 +51,7 @@ use function count; class ArgumentAnalyzer { /** - * @param array> $class_generic_params + * @param array> $class_generic_params * @return false|null */ public static function checkArgumentMatches( @@ -190,9 +191,7 @@ class ArgumentAnalyzer } /** - * @param array> $class_generic_params - * @param array> $generic_params - * @param array> $template_types + * @param array> $class_generic_params * @return false|null */ private static function checkFunctionLikeTypeMatches( @@ -317,17 +316,17 @@ class ArgumentAnalyzer [$template_type->param_name] [$template_type->defining_class] )) { - $template_result->upper_bounds[$template_type->param_name][$template_type->defining_class] = [ - clone $template_result->lower_bounds - [$template_type->param_name] - [$template_type->defining_class][0], - 0 - ]; + $template_result->upper_bounds[$template_type->param_name][$template_type->defining_class] + = new TemplateBound( + clone $template_result->lower_bounds + [$template_type->param_name] + [$template_type->defining_class]->type + ); } else { - $template_result->upper_bounds[$template_type->param_name][$template_type->defining_class] = [ - clone $template_type->as, - 0 - ]; + $template_result->upper_bounds[$template_type->param_name][$template_type->defining_class] + = new TemplateBound( + clone $template_type->as + ); } } } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php index 8442e347c..386d2390d 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php @@ -39,6 +39,7 @@ use function count; use function in_array; use function array_reverse; use function is_string; +use function array_map; /** * @internal @@ -48,7 +49,6 @@ class ArgumentsAnalyzer /** * @param list $args * @param array|null $function_params - * @param array>|null $generic_params * * @return false|null */ @@ -246,7 +246,7 @@ class ArgumentsAnalyzer ]) ]); - $template_types = ['ArrayValue' . $argument_offset => [$method_id => [Type::getMixed()]]]; + $template_types = ['ArrayValue' . $argument_offset => [$method_id => Type::getMixed()]]; $replace_template_result = new \Psalm\Internal\Type\TemplateResult( $template_types, @@ -323,7 +323,17 @@ class ArgumentsAnalyzer } $replace_template_result = new \Psalm\Internal\Type\TemplateResult( - $template_result->upper_bounds, + array_map( + function ($template_map) { + return array_map( + function ($bound) { + return $bound->type; + }, + $template_map + ); + }, + $template_result->upper_bounds + ), [] ); @@ -543,9 +553,15 @@ class ArgumentsAnalyzer $template_result = null; - $class_generic_params = $class_template_result - ? $class_template_result->upper_bounds - : []; + $class_generic_params = []; + + if ($class_template_result) { + foreach ($class_template_result->upper_bounds as $template_name => $type_map) { + foreach ($type_map as $class => $bound) { + $class_generic_params[$template_name][$class] = clone $bound->type; + } + } + } if ($function_storage) { $template_types = CallAnalyzer::getTemplateTypesForCall( @@ -612,12 +628,6 @@ class ArgumentsAnalyzer } } - foreach ($class_generic_params as $template_name => $type_map) { - foreach ($type_map as $class => $type) { - $class_generic_params[$template_name][$class][0] = clone $type[0]; - } - } - $function_param_count = count($function_params); if (count($function_params) > count($args) && !$has_packed_var) { diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php index 014abbdcf..2f76a0d84 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php @@ -826,7 +826,7 @@ class ArrayFunctionArgumentsAnalyzer foreach ($closure_param_type->getTemplateTypes() as $template_type) { $template_result->template_types[$template_type->param_name] = [ - ($template_type->defining_class) => [$template_type->as] + ($template_type->defining_class) => $template_type->as ]; } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ClassTemplateParamCollector.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ClassTemplateParamCollector.php index 9a2028288..1e0238272 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ClassTemplateParamCollector.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ClassTemplateParamCollector.php @@ -17,7 +17,7 @@ class ClassTemplateParamCollector { /** * @param lowercase-string $method_name - * @return array>|null + * @return array>|null */ public static function collect( Codebase $codebase, @@ -94,9 +94,8 @@ class ClassTemplateParamCollector foreach ($static_class_storage->template_types as $type_name => $_) { if (isset($lhs_type_part->type_params[$i])) { if ($lhs_var_id !== '$this' || $static_fq_class_name !== $static_class_storage->name) { - $class_template_params[$type_name][$static_class_storage->name] = [ - $lhs_type_part->type_params[$i] - ]; + $class_template_params[$type_name][$static_class_storage->name] + = $lhs_type_part->type_params[$i]; } } @@ -175,46 +174,41 @@ class ClassTemplateParamCollector } if ($lhs_var_id !== '$this' || $static_fq_class_name !== $class_storage->name) { - $class_template_params[$type_name][$class_storage->name] = [ - $output_type_extends ?: Type::getMixed() - ]; + $class_template_params[$type_name][$class_storage->name] + = $output_type_extends ?: Type::getMixed(); } } if (($lhs_var_id !== '$this' || $static_fq_class_name !== $class_storage->name) && !isset($class_template_params[$type_name]) ) { - $class_template_params[$type_name] = [ - $class_storage->name => [Type::getMixed()] - ]; + $class_template_params[$type_name] = [$class_storage->name => Type::getMixed()]; } } } foreach ($template_types as $type_name => $type_map) { - foreach ($type_map as [$type]) { + foreach ($type_map as $type) { foreach ($candidate_class_storages as $candidate_class_storage) { if ($candidate_class_storage !== $static_class_storage && isset($e[$candidate_class_storage->name][$type_name]) && !isset($class_template_params[$type_name][$candidate_class_storage->name]) ) { - $class_template_params[$type_name][$candidate_class_storage->name] = [ - new Type\Union( - self::expandType( - $codebase, - $e[$candidate_class_storage->name][$type_name], - $e, - $static_class_storage->name, - $static_class_storage->template_types - ) + $class_template_params[$type_name][$candidate_class_storage->name] = new Type\Union( + self::expandType( + $codebase, + $e[$candidate_class_storage->name][$type_name], + $e, + $static_class_storage->name, + $static_class_storage->template_types ) - ]; + ); } } if ($lhs_var_id !== '$this') { if (!isset($class_template_params[$type_name])) { - $class_template_params[$type_name][$class_storage->name] = [$type]; + $class_template_params[$type_name][$class_storage->name] = $type; } } } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php index 3f1984a6d..b9674c7e5 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php @@ -53,6 +53,7 @@ use function is_string; use function array_map; use function extension_loaded; use function strpos; +use Psalm\Internal\Type\TemplateBound; use Psalm\Internal\Type\TemplateResult; use Psalm\Storage\FunctionLikeParameter; use function explode; @@ -439,7 +440,7 @@ class FunctionCallAnalyzer extends CallAnalyzer ); if ($function_storage) { - $generic_params = $template_result ? $template_result->upper_bounds : []; + $inferred_upper_bouunds = $template_result ? $template_result->upper_bounds : []; if ($function_storage->assertions && $function_name instanceof PhpParser\Node\Name) { self::applyAssertionsToContext( @@ -447,7 +448,7 @@ class FunctionCallAnalyzer extends CallAnalyzer null, $function_storage->assertions, $stmt->args, - $generic_params, + $inferred_upper_bouunds, $context, $statements_analyzer ); @@ -457,8 +458,8 @@ class FunctionCallAnalyzer extends CallAnalyzer $statements_analyzer->node_data->setIfTrueAssertions( $stmt, array_map( - function (Assertion $assertion) use ($generic_params) : Assertion { - return $assertion->getUntemplatedCopy($generic_params ?: [], null); + function (Assertion $assertion) use ($inferred_upper_bouunds) : Assertion { + return $assertion->getUntemplatedCopy($inferred_upper_bouunds ?: [], null); }, $function_storage->if_true_assertions ) @@ -469,8 +470,8 @@ class FunctionCallAnalyzer extends CallAnalyzer $statements_analyzer->node_data->setIfFalseAssertions( $stmt, array_map( - function (Assertion $assertion) use ($generic_params) : Assertion { - return $assertion->getUntemplatedCopy($generic_params ?: [], null); + function (Assertion $assertion) use ($inferred_upper_bouunds) : Assertion { + return $assertion->getUntemplatedCopy($inferred_upper_bouunds ?: [], null); }, $function_storage->if_false_assertions ) @@ -980,15 +981,21 @@ class FunctionCallAnalyzer extends CallAnalyzer if (!isset($template_result->upper_bounds[$template_name])) { if ($template_name === 'TFunctionArgCount') { $template_result->upper_bounds[$template_name] = [ - 'fn-' . $function_id => [Type::getInt(false, count($stmt->args)), 0] + 'fn-' . $function_id => new TemplateBound( + Type::getInt(false, count($stmt->args)) + ) ]; } elseif ($template_name === 'TPhpMajorVersion') { $template_result->upper_bounds[$template_name] = [ - 'fn-' . $function_id => [Type::getInt(false, $codebase->php_major_version), 0] + 'fn-' . $function_id => new TemplateBound( + Type::getInt(false, $codebase->php_major_version) + ) ]; } else { $template_result->upper_bounds[$template_name] = [ - 'fn-' . $function_id => [Type::getEmpty(), 0] + 'fn-' . $function_id => new TemplateBound( + Type::getEmpty() + ) ]; } } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallReturnTypeFetcher.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallReturnTypeFetcher.php index 7f5799fdb..00563dd90 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallReturnTypeFetcher.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallReturnTypeFetcher.php @@ -9,6 +9,7 @@ use Psalm\Codebase; use Psalm\CodeLocation; use Psalm\Context; use Psalm\Internal\MethodIdentifier; +use Psalm\Internal\Type\TemplateBound; use Psalm\Internal\Type\TemplateResult; use Psalm\Type; use Psalm\Type\Atomic\TGenericObject; @@ -432,21 +433,19 @@ class MethodCallReturnTypeFetcher ) { if ($template_type->param_name === 'TFunctionArgCount') { $template_result->upper_bounds[$template_type->param_name] = [ - 'fn-' . strtolower((string) $method_id) => [ - Type::getInt(false, $arg_count), - 0 - ] + 'fn-' . strtolower((string) $method_id) => new TemplateBound( + Type::getInt(false, $arg_count) + ) ]; } elseif ($template_type->param_name === 'TPhpMajorVersion') { $template_result->upper_bounds[$template_type->param_name] = [ - 'fn-' . strtolower((string) $method_id) => [ - Type::getInt(false, $codebase->php_major_version), - 0 - ] + 'fn-' . strtolower((string) $method_id) => new TemplateBound( + Type::getInt(false, $codebase->php_major_version) + ) ]; } else { $template_result->upper_bounds[$template_type->param_name] = [ - ($template_type->defining_class) => [Type::getEmpty(), 0] + ($template_type->defining_class) => new TemplateBound(Type::getEmpty()) ]; } } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NewAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NewAnalyzer.php index 43cfc3f50..c96aed4af 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NewAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NewAnalyzer.php @@ -29,6 +29,7 @@ use function strtolower; use function implode; use function array_values; use function is_string; +use function array_map; /** * @internal @@ -628,19 +629,29 @@ class NewAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\CallAna foreach ($storage->template_types as $template_name => $base_type) { if (isset($template_result->upper_bounds[$template_name][$fq_class_name])) { $generic_param_type - = $template_result->upper_bounds[$template_name][$fq_class_name][0]; + = $template_result->upper_bounds[$template_name][$fq_class_name]->type; } elseif ($storage->template_type_extends && $template_result->upper_bounds) { $generic_param_type = self::getGenericParamForOffset( $declaring_fq_class_name, $template_name, $storage->template_type_extends, - $template_result->upper_bounds + array_map( + function ($type_map) { + return array_map( + function ($bound) { + return $bound->type; + }, + $type_map + ); + }, + $template_result->upper_bounds + ) ); } else { if ($fq_class_name === 'SplObjectStorage') { $generic_param_type = Type::getEmpty(); } else { - $generic_param_type = array_values($base_type)[0][0]; + $generic_param_type = array_values($base_type)[0]; } } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php index caf2d188b..371ba3851 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php @@ -10,6 +10,7 @@ use Psalm\CodeLocation; use Psalm\Context; use Psalm\Internal\FileManipulation\FileManipulationBuffer; use Psalm\Internal\MethodIdentifier; +use Psalm\Internal\Type\TemplateBound; use Psalm\Issue\AbstractMethodCall; use Psalm\Issue\ImpureMethodCall; use Psalm\IssueBuffer; @@ -145,7 +146,7 @@ class ExistingAtomicStaticCallAnalyzer } if (isset($found_generic_params[$type_key][$template_fq_class_name])) { - $found_generic_params[$type_key][$template_fq_class_name][0] = clone $extended_type; + $found_generic_params[$type_key][$template_fq_class_name] = clone $extended_type; continue; } @@ -153,13 +154,11 @@ class ExistingAtomicStaticCallAnalyzer if ($t instanceof Type\Atomic\TTemplateParam && isset($found_generic_params[$t->param_name][$t->defining_class]) ) { - $found_generic_params[$type_key][$template_fq_class_name] = [ - $found_generic_params[$t->param_name][$t->defining_class][0] - ]; + $found_generic_params[$type_key][$template_fq_class_name] + = $found_generic_params[$t->param_name][$t->defining_class]; } else { - $found_generic_params[$type_key][$template_fq_class_name] = [ - clone $extended_type - ]; + $found_generic_params[$type_key][$template_fq_class_name] + = clone $extended_type; break; } } @@ -245,21 +244,19 @@ class ExistingAtomicStaticCallAnalyzer )) { if ($template_type->param_name === 'TFunctionArgCount') { $template_result->upper_bounds[$template_type->param_name] = [ - 'fn-' . strtolower((string) $method_id) => [ - Type::getInt(false, count($stmt->args)), - 0 - ] + 'fn-' . strtolower((string) $method_id) => new TemplateBound( + Type::getInt(false, count($stmt->args)) + ) ]; } elseif ($template_type->param_name === 'TPhpMajorVersion') { $template_result->upper_bounds[$template_type->param_name] = [ - 'fn-' . strtolower((string) $method_id) => [ + 'fn-' . strtolower((string) $method_id) => new TemplateBound( Type::getInt(false, $codebase->php_major_version), - 0 - ] + ) ]; } else { $template_result->upper_bounds[$template_type->param_name] = [ - ($template_type->defining_class) => [Type::getEmpty(), 0] + ($template_type->defining_class) => new TemplateBound(Type::getEmpty()) ]; } } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php index a9b5db809..c76db1e28 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php @@ -7,6 +7,7 @@ use Psalm\Internal\Analyzer\ClassLikeAnalyzer; use Psalm\Internal\Analyzer\FunctionLikeAnalyzer; use Psalm\Internal\Analyzer\StatementsAnalyzer; use Psalm\Internal\Type\Comparator\UnionTypeComparator; +use Psalm\Internal\Type\TemplateBound; use Psalm\Internal\Type\TemplateResult; use Psalm\CodeLocation; use Psalm\Context; @@ -30,6 +31,7 @@ use function str_replace; use function is_int; use function substr; use function array_merge; +use function array_map; /** * @internal @@ -339,8 +341,8 @@ class CallAnalyzer } /** - * @return array> - * @param array> $existing_template_types + * @return array> + * @param array> $existing_template_types */ public static function getTemplateTypesForCall( \Psalm\Codebase $codebase, @@ -383,14 +385,14 @@ class CallAnalyzer } } - $template_types[$template_name][$declaring_class_storage->name] = [$output_type]; + $template_types[$template_name][$declaring_class_storage->name] = $output_type; } } } } elseif ($declaring_class_storage->template_types) { foreach ($declaring_class_storage->template_types as $template_name => $type_map) { - foreach ($type_map as $key => [$type]) { - $template_types[$template_name][$key] = [$type]; + foreach ($type_map as $key => $type) { + $template_types[$template_name][$key] = $type; } } } @@ -398,9 +400,9 @@ class CallAnalyzer foreach ($template_types as $key => $type_map) { foreach ($type_map as $class => $type) { - $template_types[$key][$class][0] = \Psalm\Internal\Type\TypeExpander::expandUnion( + $template_types[$key][$class] = \Psalm\Internal\Type\TypeExpander::expandUnion( $codebase, - $type[0], + $type, $appearing_class_name, $calling_class_storage ? $calling_class_storage->name : null, null, @@ -416,7 +418,7 @@ class CallAnalyzer /** * @param array> $template_type_extends - * @param array> $found_generic_params + * @param array> $found_generic_params */ public static function getGenericParamForOffset( string $fq_class_name, @@ -436,7 +438,7 @@ class CallAnalyzer } } - return $found_generic_params[$template_name][$fq_class_name][0]; + return $found_generic_params[$template_name][$fq_class_name]; } foreach ($template_type_extends as $type_map) { @@ -619,7 +621,7 @@ class CallAnalyzer * @param \Psalm\Storage\Assertion[] $assertions * @param string $thisName * @param list $args - * @param array> $template_type_map, + * @param array> $inferred_upper_bouunds, * */ public static function applyAssertionsToContext( @@ -627,7 +629,7 @@ class CallAnalyzer ?string $thisName, array $assertions, array $args, - array $template_type_map, + array $inferred_upper_bouunds, Context $context, StatementsAnalyzer $statements_analyzer ): void { @@ -677,13 +679,13 @@ class CallAnalyzer $rule = substr($rule, 1); } - if (isset($template_type_map[$rule])) { - foreach ($template_type_map[$rule] as $template_map) { - if ($template_map[0]->hasMixed()) { + if (isset($inferred_upper_bouunds[$rule])) { + foreach ($inferred_upper_bouunds[$rule] as $template_map) { + if ($template_map->type->hasMixed()) { continue 2; } - $replacement_atomic_types = $template_map[0]->getAtomicTypes(); + $replacement_atomic_types = $template_map->type->getAtomicTypes(); if (count($replacement_atomic_types) > 1) { continue 2; @@ -791,17 +793,27 @@ class CallAnalyzer } if ($type_assertions) { + $template_type_map = array_map( + function ($type_map) { + return array_map( + function ($bound) { + return $bound->type; + }, + $type_map + ); + }, + $inferred_upper_bouunds + ); + foreach (($statements_analyzer->getTemplateTypeMap() ?: []) as $template_name => $map) { - foreach ($map as $ref => [$type]) { - $template_type_map[$template_name][$ref] = [ - new Type\Union([ - new Type\Atomic\TTemplateParam( - $template_name, - $type, - $ref - ) - ]) - ]; + foreach ($map as $ref => $type) { + $template_type_map[$template_name][$ref] = new Type\Union([ + new Type\Atomic\TTemplateParam( + $template_name, + $type, + $ref + ) + ]); } } @@ -874,21 +886,21 @@ class CallAnalyzer ) : void { if ($template_result->upper_bounds && $template_result->lower_bounds) { foreach ($template_result->lower_bounds as $template_name => $defining_map) { - foreach ($defining_map as $defining_id => [$lower_bound_type]) { + foreach ($defining_map as $defining_id => $lower_bound) { if (isset($template_result->upper_bounds[$template_name][$defining_id])) { - $upper_bound_type = $template_result->upper_bounds[$template_name][$defining_id][0]; + $upper_bound = $template_result->upper_bounds[$template_name][$defining_id]; $union_comparison_result = new \Psalm\Internal\Type\Comparator\TypeComparisonResult(); if (count($template_result->lower_bounds_unintersectable_types) > 1) { - [$upper_bound_type, $lower_bound_type] + [$upper_bound->type, $lower_bound->type] = $template_result->lower_bounds_unintersectable_types; } if (!UnionTypeComparator::isContainedBy( $statements_analyzer->getCodebase(), - $upper_bound_type, - $lower_bound_type, + $upper_bound->type, + $lower_bound->type, false, false, $union_comparison_result @@ -897,8 +909,8 @@ class CallAnalyzer if ($union_comparison_result->type_coerced_from_mixed) { if (IssueBuffer::accepts( new MixedArgumentTypeCoercion( - 'Type ' . $upper_bound_type->getId() . ' should be a subtype of ' - . $lower_bound_type->getId(), + 'Type ' . $upper_bound->type->getId() . ' should be a subtype of ' + . $lower_bound->type->getId(), $code_location, $function_id ), @@ -909,8 +921,8 @@ class CallAnalyzer } else { if (IssueBuffer::accepts( new ArgumentTypeCoercion( - 'Type ' . $upper_bound_type->getId() . ' should be a subtype of ' - . $lower_bound_type->getId(), + 'Type ' . $upper_bound->type->getId() . ' should be a subtype of ' + . $lower_bound->type->getId(), $code_location, $function_id ), @@ -922,8 +934,8 @@ class CallAnalyzer } elseif ($union_comparison_result->scalar_type_match_found) { if (IssueBuffer::accepts( new InvalidScalarArgument( - 'Type ' . $upper_bound_type->getId() . ' should be a subtype of ' - . $lower_bound_type->getId(), + 'Type ' . $upper_bound->type->getId() . ' should be a subtype of ' + . $lower_bound->type->getId(), $code_location, $function_id ), @@ -934,8 +946,8 @@ class CallAnalyzer } else { if (IssueBuffer::accepts( new InvalidArgument( - 'Type ' . $upper_bound_type->getId() . ' should be a subtype of ' - . $lower_bound_type->getId(), + 'Type ' . $upper_bound->type->getId() . ' should be a subtype of ' + . $lower_bound->type->getId(), $code_location, $function_id ), @@ -946,7 +958,9 @@ class CallAnalyzer } } } else { - $template_result->upper_bounds[$template_name][$defining_id][0] = clone $lower_bound_type; + $template_result->upper_bounds[$template_name][$defining_id] = new TemplateBound( + clone $lower_bound->type + ); } } } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php index dee947126..a326d9c34 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php @@ -837,17 +837,15 @@ class ArrayFetchAnalyzer [], [ $type->param_name => [ - 'class-string-map' => [ - new Type\Union([ - new TTemplateParam( - $offset_type_part->param_name, - $offset_type_part->as_type - ? new Type\Union([$offset_type_part->as_type]) - : Type::getObject(), - $offset_type_part->defining_class - ) - ]) - ] + 'class-string-map' => new Type\Union([ + new TTemplateParam( + $offset_type_part->param_name, + $offset_type_part->as_type + ? new Type\Union([$offset_type_part->as_type]) + : Type::getObject(), + $offset_type_part->defining_class + ) + ]) ] ] ); @@ -856,17 +854,15 @@ class ArrayFetchAnalyzer [], [ $offset_type_part->param_name => [ - $offset_type_part->defining_class => [ - new Type\Union([ - new TTemplateParam( - $type->param_name, - $type->as_type - ? new Type\Union([$type->as_type]) - : Type::getObject(), - 'class-string-map' - ) - ]) - ] + $offset_type_part->defining_class => new Type\Union([ + new TTemplateParam( + $type->param_name, + $type->as_type + ? new Type\Union([$type->as_type]) + : Type::getObject(), + 'class-string-map' + ) + ]) ] ] ); @@ -875,12 +871,10 @@ class ArrayFetchAnalyzer [], [ $type->param_name => [ - 'class-string-map' => [ - new Type\Union([ - $offset_type_part->as_type - ?: new Type\Atomic\TObject() - ]) - ] + 'class-string-map' => new Type\Union([ + $offset_type_part->as_type + ?: new Type\Atomic\TObject() + ]) ] ] ); diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/AtomicPropertyFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/AtomicPropertyFetchAnalyzer.php index e45fe6099..c67686645 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/AtomicPropertyFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/AtomicPropertyFetchAnalyzer.php @@ -299,7 +299,7 @@ class AtomicPropertyFetchAnalyzer $type_params = []; foreach ($class_storage->template_types as $type_map) { - $type_params[] = clone array_values($type_map)[0][0]; + $type_params[] = clone array_values($type_map)[0]; } $lhs_type_part = new TGenericObject($lhs_type_part->value, $type_params); @@ -434,7 +434,7 @@ class AtomicPropertyFetchAnalyzer $type_params = []; foreach ($class_storage->template_types as $type_map) { - $type_params[] = clone array_values($type_map)[0][0]; + $type_params[] = clone array_values($type_map)[0]; } $lhs_type_part = new TGenericObject($lhs_type_part->value, $type_params); @@ -625,7 +625,7 @@ class AtomicPropertyFetchAnalyzer $type_params = []; foreach ($declaring_class_storage->template_types as $type_map) { - $type_params[] = clone array_values($type_map)[0][0]; + $type_params[] = clone array_values($type_map)[0]; } $lhs_type_part = new TGenericObject($lhs_type_part->value, $type_params); @@ -716,10 +716,7 @@ class AtomicPropertyFetchAnalyzer $i++; if ($i === $param_offset) { - $template_types[$calling_param_name][$calling_class_storage->name] = [ - $lhs_param_type, - 0 - ]; + $template_types[$calling_param_name][$calling_class_storage->name] = $lhs_param_type; break; } } @@ -747,10 +744,8 @@ class AtomicPropertyFetchAnalyzer } if ($position !== false && isset($lhs_type_part->type_params[$position])) { - $template_types[$type_name][$declaring_class_storage->name] = [ - $lhs_type_part->type_params[$position], - 0 - ]; + $template_types[$type_name][$declaring_class_storage->name] + = $lhs_type_part->type_params[$position]; } } } diff --git a/src/Psalm/Internal/Codebase/Populator.php b/src/Psalm/Internal/Codebase/Populator.php index 9240bfdfb..06cc96b6a 100644 --- a/src/Psalm/Internal/Codebase/Populator.php +++ b/src/Psalm/Internal/Codebase/Populator.php @@ -421,7 +421,7 @@ class Populator foreach ($trait_storage->template_types as $template_name => $template_type_map) { foreach ($template_type_map as $template_type) { - $default_param = clone $template_type[0]; + $default_param = clone $template_type; $default_param->from_docblock = false; $storage->template_type_extends[$trait_storage->name][$template_name] = $default_param; @@ -533,7 +533,7 @@ class Populator foreach ($parent_storage->template_types as $template_name => $template_type_map) { foreach ($template_type_map as $template_type) { - $default_param = clone $template_type[0]; + $default_param = clone $template_type; $default_param->from_docblock = false; $storage->template_type_extends[$parent_storage->name][$template_name] = $default_param; @@ -674,7 +674,7 @@ class Populator foreach ($parent_interface_storage->template_types as $template_name => $template_type_map) { foreach ($template_type_map as $template_type) { - $default_param = clone $template_type[0]; + $default_param = clone $template_type; $default_param->from_docblock = false; $storage->template_type_extends[$parent_interface_storage->name][$template_name] = $default_param; @@ -771,7 +771,7 @@ class Populator foreach ($implemented_interface_storage->template_types as $template_name => $template_type_map) { foreach ($template_type_map as $template_type) { - $default_param = clone $template_type[0]; + $default_param = clone $template_type; $default_param->from_docblock = false; $storage->template_type_extends[$implemented_interface_storage->name][$template_name] = $default_param; diff --git a/src/Psalm/Internal/Codebase/Reflection.php b/src/Psalm/Internal/Codebase/Reflection.php index a722e34f4..09db30ee8 100644 --- a/src/Psalm/Internal/Codebase/Reflection.php +++ b/src/Psalm/Internal/Codebase/Reflection.php @@ -170,8 +170,8 @@ class Reflection if ($class_name_lower === 'generator') { $storage->template_types = [ - 'TKey' => ['Generator' => [Type::getMixed()]], - 'TValue' => ['Generator' => [Type::getMixed()]], + 'TKey' => ['Generator' => Type::getMixed()], + 'TValue' => ['Generator' => Type::getMixed()], ]; } diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php b/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php index ea74b7fde..c0dbf3757 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php @@ -79,7 +79,7 @@ class ClassLikeNodeScanner private $classlike_type_aliases = []; /** - * @var array> + * @var array> */ public $class_template_types = []; @@ -334,7 +334,7 @@ class ClassLikeNodeScanner } $storage->template_types[$template_name] = [ - $fq_classlike_name => [$template_type], + $fq_classlike_name => $template_type, ]; } else { $storage->docblock_issues[] = new InvalidDocblock( @@ -344,7 +344,7 @@ class ClassLikeNodeScanner } } else { /** @psalm-suppress PropertyTypeCoercion due to a Psalm bug */ - $storage->template_types[$template_name][$fq_classlike_name] = [Type::getMixed()]; + $storage->template_types[$template_name][$fq_classlike_name] = Type::getMixed(); } $storage->template_covariants[$i] = $template_map[3]; diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockScanner.php b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockScanner.php index 7806d7b38..16375fe2e 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockScanner.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockScanner.php @@ -35,7 +35,7 @@ use function strlen; class FunctionLikeDocblockScanner { /** - * @param array> $existing_function_template_types + * @param array> $existing_function_template_types * @param array $type_aliases */ public static function addDocblockInfo( @@ -217,7 +217,7 @@ class FunctionLikeDocblockScanner ); } else { $storage->template_types[$template_name] = [ - 'fn-' . strtolower($cased_function_id) => [$template_type], + 'fn-' . strtolower($cased_function_id) => $template_type, ]; } @@ -615,7 +615,7 @@ class FunctionLikeDocblockScanner if (isset($template_types[$template_typeof['template_type']])) { foreach ($template_types[$template_typeof['template_type']] as $class => $map) { - $template_type = $map[0]; + $template_type = $map; $template_class = $class; } } @@ -753,11 +753,11 @@ class FunctionLikeDocblockScanner } /** - * @param array> $template_types + * @param array> $template_types * @param array|null $type_aliases - * @param array> $function_template_types + * @param array> $function_template_types * - * @return array{array, array>} + * @return array{array, array>} */ private static function getConditionalSanitizedTypeTokens( string $docblock_return_type, @@ -796,9 +796,7 @@ class FunctionLikeDocblockScanner : Type::getMixed(); $storage->template_types[$template_name] = [ - $template_function_id => [ - $template_as_type - ], + $template_function_id => $template_as_type, ]; $function_template_types[$template_name] @@ -834,9 +832,7 @@ class FunctionLikeDocblockScanner $template_name = 'TFunctionArgCount'; $storage->template_types[$template_name] = [ - $template_function_id => [ - Type::getInt() - ], + $template_function_id => Type::getInt(), ]; $function_template_types[$template_name] @@ -849,9 +845,7 @@ class FunctionLikeDocblockScanner $template_name = 'TPhpMajorVersion'; $storage->template_types[$template_name] = [ - $template_function_id => [ - Type::getInt() - ], + $template_function_id => Type::getInt(), ]; $function_template_types[$template_name] @@ -865,8 +859,8 @@ class FunctionLikeDocblockScanner } /** - * @param array> $class_template_types - * @param array> $function_template_types + * @param array> $class_template_types + * @param array> $function_template_types * @param array $type_aliases * @return non-empty-list|null */ @@ -964,8 +958,8 @@ class FunctionLikeDocblockScanner } /** - * @param array> $class_template_types - * @param array> $function_template_types + * @param array> $class_template_types + * @param array> $function_template_types * @param array $type_aliases * @param array $docblock_params */ @@ -1096,9 +1090,9 @@ class FunctionLikeDocblockScanner if ($storage->template_types) { foreach ($storage->template_types as $t => $type_map) { - foreach ($type_map as $obj => [$type]) { + foreach ($type_map as $obj => $type) { if ($type->isMixed() && $docblock_param['type'] === 'class-string<' . $t . '>') { - $storage->template_types[$t][$obj] = [Type::getObject()]; + $storage->template_types[$t][$obj] = Type::getObject(); if (isset($function_template_types[$t])) { $function_template_types[$t][$obj] = $storage->template_types[$t][$obj]; diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeNodeScanner.php b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeNodeScanner.php index a74e56911..a500f1d5c 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeNodeScanner.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeNodeScanner.php @@ -70,7 +70,7 @@ class FunctionLikeNodeScanner private $classlike_storage; /** - * @var array> + * @var array> */ private $existing_function_template_types; @@ -90,7 +90,7 @@ class FunctionLikeNodeScanner public $storage; /** - * @param array> $existing_function_template_types + * @param array> $existing_function_template_types * @param array $type_aliases */ public function __construct( diff --git a/src/Psalm/Internal/Stubs/Generator/ClassLikeStubGenerator.php b/src/Psalm/Internal/Stubs/Generator/ClassLikeStubGenerator.php index 803ea08de..5a8be544a 100644 --- a/src/Psalm/Internal/Stubs/Generator/ClassLikeStubGenerator.php +++ b/src/Psalm/Internal/Stubs/Generator/ClassLikeStubGenerator.php @@ -31,7 +31,7 @@ class ClassLikeStubGenerator $template_offset = 0; foreach ($storage->template_types ?: [] as $template_name => $map) { - $type = array_values($map)[0][0]; + $type = array_values($map)[0]; $key = isset($storage->template_covariants[$template_offset]) ? 'template-covariant' : 'template'; @@ -226,7 +226,7 @@ class ClassLikeStubGenerator $docblock = new ParsedDocblock('', []); foreach ($method_storage->template_types ?: [] as $template_name => $map) { - $type = array_values($map)[0][0]; + $type = array_values($map)[0]; $docblock->tags['template'][] = $template_name . ' as ' . $type->toNamespacedString( $namespace_name, diff --git a/src/Psalm/Internal/Stubs/Generator/StubsGenerator.php b/src/Psalm/Internal/Stubs/Generator/StubsGenerator.php index 91dab630e..aba15dbb1 100644 --- a/src/Psalm/Internal/Stubs/Generator/StubsGenerator.php +++ b/src/Psalm/Internal/Stubs/Generator/StubsGenerator.php @@ -178,7 +178,7 @@ class StubsGenerator $docblock = new ParsedDocblock('', []); foreach ($function_storage->template_types ?: [] as $template_name => $map) { - $type = array_values($map)[0][0]; + $type = array_values($map)[0]; $docblock->tags['template'][] = $template_name . ' as ' . $type->toNamespacedString( $namespace_name, diff --git a/src/Psalm/Internal/Type/AssertionReconciler.php b/src/Psalm/Internal/Type/AssertionReconciler.php index a04dbc41c..1c10d016c 100644 --- a/src/Psalm/Internal/Type/AssertionReconciler.php +++ b/src/Psalm/Internal/Type/AssertionReconciler.php @@ -43,7 +43,7 @@ class AssertionReconciler extends \Psalm\Type\Reconciler * - notEmpty(Object|false) => Object * * @param string[] $suppressed_issues - * @param array> $template_type_map + * @param array> $template_type_map * @param-out 0|1|2 $failed_reconciliation */ public static function reconcile( @@ -362,7 +362,7 @@ class AssertionReconciler extends \Psalm\Type\Reconciler /** * @param 0|1|2 $failed_reconciliation * @param string[] $suppressed_issues - * @param array> $template_type_map + * @param array> $template_type_map * @param-out 0|1|2 $failed_reconciliation */ private static function refine( @@ -671,7 +671,7 @@ class AssertionReconciler extends \Psalm\Type\Reconciler /** - * @param array> $template_type_map + * @param array> $template_type_map */ private static function filterTypeWithAnother( Codebase $codebase, diff --git a/src/Psalm/Internal/Type/NegatedAssertionReconciler.php b/src/Psalm/Internal/Type/NegatedAssertionReconciler.php index 8b440d30b..7f0fb3988 100644 --- a/src/Psalm/Internal/Type/NegatedAssertionReconciler.php +++ b/src/Psalm/Internal/Type/NegatedAssertionReconciler.php @@ -26,7 +26,7 @@ use function substr; class NegatedAssertionReconciler extends Reconciler { /** - * @param array> $template_type_map + * @param array> $template_type_map * @param string[] $suppressed_issues * @param 0|1|2 $failed_reconciliation * diff --git a/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php b/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php index fdba1c82b..b1b766dc8 100644 --- a/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php +++ b/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php @@ -30,7 +30,7 @@ use function substr; class SimpleNegatedAssertionReconciler extends Reconciler { /** - * @param array> $template_type_map + * @param array> $template_type_map * @param string[] $suppressed_issues * @param 0|1|2 $failed_reconciliation * diff --git a/src/Psalm/Internal/Type/TemplateBound.php b/src/Psalm/Internal/Type/TemplateBound.php new file mode 100644 index 000000000..4c9b6202f --- /dev/null +++ b/src/Psalm/Internal/Type/TemplateBound.php @@ -0,0 +1,40 @@ +>> the type T appears at three different depths. + * + * The shallowest-appearance of the template takes prominence when inferring the type of T. + * + * @var ?int + */ + public $appearance_depth; + + /** + * The argument offset where this template was set + * + * In the type Foo the type appears at argument offsets 0 and 2 + * + * @var ?int + */ + public $arg_offset; + + public function __construct(Union $type, ?int $appearance_depth = null, ?int $arg_offset = null) + { + $this->type = $type; + $this->appearance_depth = $appearance_depth; + $this->arg_offset = $arg_offset; + } +} diff --git a/src/Psalm/Internal/Type/TemplateResult.php b/src/Psalm/Internal/Type/TemplateResult.php index 3593d1028..d327c7837 100644 --- a/src/Psalm/Internal/Type/TemplateResult.php +++ b/src/Psalm/Internal/Type/TemplateResult.php @@ -3,21 +3,22 @@ namespace Psalm\Internal\Type; use Psalm\Type\Union; +use function array_map; class TemplateResult { /** - * @var array> + * @var array> */ public $template_types; /** - * @var array> + * @var array> */ public $upper_bounds; /** - * @var array> + * @var array> */ public $lower_bounds = []; @@ -27,12 +28,23 @@ class TemplateResult public $lower_bounds_unintersectable_types = []; /** - * @param array> $template_types - * @param array> $upper_bounds + * @param array> $template_types + * @param array> $upper_bounds */ public function __construct(array $template_types, array $upper_bounds) { $this->template_types = $template_types; - $this->upper_bounds = $upper_bounds; + + $this->upper_bounds = array_map( + function ($type_map) { + return array_map( + function ($type) { + return new TemplateBound($type); + }, + $type_map + ); + }, + $upper_bounds + ); } } diff --git a/src/Psalm/Internal/Type/TypeParser.php b/src/Psalm/Internal/Type/TypeParser.php index 1baa4e90a..e05f50720 100644 --- a/src/Psalm/Internal/Type/TypeParser.php +++ b/src/Psalm/Internal/Type/TypeParser.php @@ -58,7 +58,7 @@ class TypeParser * * @param list $type_tokens * @param array{int,int}|null $php_version - * @param array> $template_type_map + * @param array> $template_type_map * @param array $type_aliases * */ @@ -110,7 +110,7 @@ class TypeParser /** * @param array{int,int}|null $php_version - * @param array> $template_type_map + * @param array> $template_type_map * @param array $type_aliases * * @return Atomic|Union @@ -140,9 +140,9 @@ class TypeParser && $i === 0 ) { if ($tree_type instanceof TTemplateParam) { - $template_type_map[$tree_type->param_name] = ['class-string-map' => [$tree_type->as]]; + $template_type_map[$tree_type->param_name] = ['class-string-map' => $tree_type->as]; } elseif ($tree_type instanceof TNamedObject) { - $template_type_map[$tree_type->value] = ['class-string-map' => [\Psalm\Type::getObject()]]; + $template_type_map[$tree_type->value] = ['class-string-map' => \Psalm\Type::getObject()]; } } @@ -234,7 +234,7 @@ class TypeParser return self::getGenericParamClass( $class_name, - $template_type_map[$class_name][$first_class][0], + $template_type_map[$class_name][$first_class], $first_class ); } @@ -299,7 +299,7 @@ class TypeParser return new Atomic\TTemplateKeyOf( $param_name, $defining_class, - $template_type_map[$param_name][$defining_class][0] + $template_type_map[$param_name][$defining_class] ); } @@ -886,9 +886,9 @@ class TypeParser if (!$offset_defining_class && isset($offset_template_data['']) - && $offset_template_data[''][0]->isSingle() + && $offset_template_data['']->isSingle() ) { - $offset_template_type = array_values($offset_template_data[''][0]->getAtomicTypes())[0]; + $offset_template_type = array_values($offset_template_data['']->getAtomicTypes())[0]; if ($offset_template_type instanceof Atomic\TTemplateKeyOf) { $offset_defining_class = $offset_template_type->defining_class; @@ -970,7 +970,7 @@ class TypeParser return new Atomic\TConditional( $template_param_name, $first_class, - $template_type_map[$template_param_name][$first_class][0], + $template_type_map[$template_param_name][$first_class], $conditional_type, $if_type, $else_type @@ -993,7 +993,7 @@ class TypeParser return self::getGenericParamClass( $fq_classlike_name, - $template_type_map[$fq_classlike_name][$first_class][0], + $template_type_map[$fq_classlike_name][$first_class], $first_class ); } diff --git a/src/Psalm/Internal/Type/UnionTemplateHandler.php b/src/Psalm/Internal/Type/UnionTemplateHandler.php index 4763696ad..5e365f94c 100644 --- a/src/Psalm/Internal/Type/UnionTemplateHandler.php +++ b/src/Psalm/Internal/Type/UnionTemplateHandler.php @@ -155,11 +155,11 @@ class UnionTemplateHandler && !empty($template_result->upper_bounds[$atomic_type->offset_param_name]) ) { $array_template_type - = $template_result->template_types[$atomic_type->array_param_name][$atomic_type->defining_class][0]; + = $template_result->template_types[$atomic_type->array_param_name][$atomic_type->defining_class]; $offset_template_type = array_values( $template_result->upper_bounds[$atomic_type->offset_param_name] - )[0][0]; + )[0]->type; if ($array_template_type->isSingle() && $offset_template_type->isSingle() @@ -204,7 +204,7 @@ class UnionTemplateHandler if (isset($template_result->template_types[$atomic_type->param_name][$atomic_type->defining_class])) { $template_type - = $template_result->template_types[$atomic_type->param_name][$atomic_type->defining_class][0]; + = $template_result->template_types[$atomic_type->param_name][$atomic_type->defining_class]; if ($template_type->isSingle()) { $template_type = array_values($template_type->getAtomicTypes())[0]; @@ -473,8 +473,7 @@ class UnionTemplateHandler ) : array { $template_type = $template_result->template_types [$atomic_type->param_name] - [$atomic_type->defining_class] - [0]; + [$atomic_type->defining_class]; if ($template_type->getId() === $key) { return array_values($template_type->getAtomicTypes()); @@ -561,9 +560,9 @@ class UnionTemplateHandler // @codingStandardsIgnoreStart if ($replacement_atomic_type instanceof Atomic\TTemplateKeyOf - && isset($template_result->template_types[$replacement_atomic_type->param_name][$replacement_atomic_type->defining_class][0]) + && isset($template_result->template_types[$replacement_atomic_type->param_name][$replacement_atomic_type->defining_class]) ) { - $keyed_template = $template_result->template_types[$replacement_atomic_type->param_name][$replacement_atomic_type->defining_class][0]; + $keyed_template = $template_result->template_types[$replacement_atomic_type->param_name][$replacement_atomic_type->defining_class]; if ($keyed_template->isSingle()) { $keyed_template = array_values($keyed_template->getAtomicTypes())[0]; @@ -587,7 +586,7 @@ class UnionTemplateHandler $atomic_types[] = clone $key_type_atomic; } - $template_result->upper_bounds[$atomic_type->param_name][$atomic_type->defining_class][0] + $template_result->upper_bounds[$atomic_type->param_name][$atomic_type->defining_class]->type = clone $key_type; } } @@ -674,14 +673,14 @@ class UnionTemplateHandler $generic_param->setFromDocblock(); if (isset( - $template_result->upper_bounds[$param_name_key][$atomic_type->defining_class][0] + $template_result->upper_bounds[$param_name_key][$atomic_type->defining_class] )) { $existing_generic_param = $template_result->upper_bounds [$param_name_key] [$atomic_type->defining_class]; - $existing_depth = $existing_generic_param[1] ?? -1; - $existing_arg_offset = $existing_generic_param[2] ?? $input_arg_offset; + $existing_depth = $existing_generic_param->appearance_depth ?? -1; + $existing_arg_offset = $existing_generic_param->arg_offset ?? $input_arg_offset; if ($existing_depth > $depth && $input_arg_offset === $existing_arg_offset) { return $atomic_types ?: [$atomic_type]; @@ -691,19 +690,18 @@ class UnionTemplateHandler $generic_param = \Psalm\Type::combineUnionTypes( $template_result->upper_bounds [$param_name_key] - [$atomic_type->defining_class] - [0], + [$atomic_type->defining_class]->type, $generic_param, $codebase ); } } - $template_result->upper_bounds[$param_name_key][$atomic_type->defining_class] = [ + $template_result->upper_bounds[$param_name_key][$atomic_type->defining_class] = new TemplateBound( $generic_param, $depth, $input_arg_offset - ]; + ); } foreach ($atomic_types as $atomic_type) { @@ -744,18 +742,18 @@ class UnionTemplateHandler } } - if (isset($template_result->lower_bounds[$param_name_key][$atomic_type->defining_class][0])) { + if (isset($template_result->lower_bounds[$param_name_key][$atomic_type->defining_class])) { if (!UnionTypeComparator::isContainedBy( $codebase, - $template_result->lower_bounds[$param_name_key][$atomic_type->defining_class][0], + $template_result->lower_bounds[$param_name_key][$atomic_type->defining_class]->type, $generic_param ) || !UnionTypeComparator::isContainedBy( $codebase, $generic_param, - $template_result->lower_bounds[$param_name_key][$atomic_type->defining_class][0] + $template_result->lower_bounds[$param_name_key][$atomic_type->defining_class]->type )) { $intersection_type = \Psalm\Type::intersectUnionTypes( - $template_result->lower_bounds[$param_name_key][$atomic_type->defining_class][0], + $template_result->lower_bounds[$param_name_key][$atomic_type->defining_class]->type, $generic_param, $codebase ); @@ -764,19 +762,20 @@ class UnionTemplateHandler } if ($intersection_type) { - $template_result->lower_bounds[$param_name_key][$atomic_type->defining_class][0] + $template_result->lower_bounds[$param_name_key][$atomic_type->defining_class]->type = $intersection_type; } else { $template_result->lower_bounds_unintersectable_types[] - = $template_result->lower_bounds[$param_name_key][$atomic_type->defining_class][0]; + = $template_result->lower_bounds[$param_name_key][$atomic_type->defining_class]->type; $template_result->lower_bounds_unintersectable_types[] = $generic_param; - $template_result->lower_bounds[$param_name_key][$atomic_type->defining_class][0] + $template_result->lower_bounds[$param_name_key][$atomic_type->defining_class]->type = \Psalm\Type::getMixed(); } } else { - $template_result->lower_bounds[$param_name_key][$atomic_type->defining_class][0] - = $generic_param; + $template_result->lower_bounds[$param_name_key][$atomic_type->defining_class] = new TemplateBound( + $generic_param + ); } } } @@ -845,19 +844,19 @@ class UnionTemplateHandler if ($generic_param) { if (isset($template_result->upper_bounds[$atomic_type->param_name][$atomic_type->defining_class])) { - $template_result->upper_bounds[$atomic_type->param_name][$atomic_type->defining_class] = [ + $template_result->upper_bounds[$atomic_type->param_name][$atomic_type->defining_class] = new TemplateBound( \Psalm\Type::combineUnionTypes( $generic_param, - $template_result->upper_bounds[$atomic_type->param_name][$atomic_type->defining_class][0] + $template_result->upper_bounds[$atomic_type->param_name][$atomic_type->defining_class]->type ), $depth - ]; + ); } else { - $template_result->upper_bounds[$atomic_type->param_name][$atomic_type->defining_class] = [ + $template_result->upper_bounds[$atomic_type->param_name][$atomic_type->defining_class] = new TemplateBound( $generic_param, $depth, $input_arg_offset - ]; + ); } } } @@ -866,29 +865,27 @@ class UnionTemplateHandler } /** - * @param array> $template_types - * - * @return array{Union, 1?:int}|null + * @param array> $template_types */ public static function getRootTemplateType( array $template_types, string $param_name, string $defining_class, array $visited_classes = [] - ) : ?array { + ) : ?Union { if (isset($visited_classes[$defining_class])) { return null; } if (isset($template_types[$param_name][$defining_class])) { - $mapped_type = $template_types[$param_name][$defining_class][0]; + $mapped_type = $template_types[$param_name][$defining_class]->type; $mapped_type_atomic_types = array_values($mapped_type->getAtomicTypes()); if (count($mapped_type_atomic_types) > 1 || !$mapped_type_atomic_types[0] instanceof Atomic\TTemplateParam ) { - return $template_types[$param_name][$defining_class]; + return $template_types[$param_name][$defining_class]->type; } $first_template = $mapped_type_atomic_types[0]; @@ -898,7 +895,7 @@ class UnionTemplateHandler $first_template->param_name, $first_template->defining_class, $visited_classes + [$defining_class => true] - ) ?? $template_types[$param_name][$defining_class]; + ) ?? $template_types[$param_name][$defining_class]->type; } return null; @@ -942,7 +939,7 @@ class UnionTemplateHandler break; } - $replacement_templates[$template_name][$input_type_part->value] = [$input_type_params[$i]]; + $replacement_templates[$template_name][$input_type_part->value] = $input_type_params[$i]; $i++; } diff --git a/src/Psalm/Internal/TypeVisitor/TypeChecker.php b/src/Psalm/Internal/TypeVisitor/TypeChecker.php index b9972fd64..b4c3106a6 100644 --- a/src/Psalm/Internal/TypeVisitor/TypeChecker.php +++ b/src/Psalm/Internal/TypeVisitor/TypeChecker.php @@ -250,7 +250,7 @@ class TypeChecker extends NodeVisitor && empty($expected_param_covariants[$i]); if (isset(\array_values($expected_type_params)[$i])) { - $expected_type_param = \reset(\array_values($expected_type_params)[$i])[0]; + $expected_type_param = \reset(\array_values($expected_type_params)[$i]); $expected_type_param = \Psalm\Internal\Type\TypeExpander::expandUnion( $codebase, diff --git a/src/Psalm/StatementsSource.php b/src/Psalm/StatementsSource.php index cb7fdaee9..35b2939d9 100644 --- a/src/Psalm/StatementsSource.php +++ b/src/Psalm/StatementsSource.php @@ -22,7 +22,7 @@ interface StatementsSource extends FileSource public function getParentFQCLN(): ?string; /** - * @return array>|null + * @return array>|null */ public function getTemplateTypeMap(): ?array; diff --git a/src/Psalm/Storage/Assertion.php b/src/Psalm/Storage/Assertion.php index 02a0ca55f..ed95719da 100644 --- a/src/Psalm/Storage/Assertion.php +++ b/src/Psalm/Storage/Assertion.php @@ -28,9 +28,9 @@ class Assertion } /** - * @param array> $template_type_map + * @param array> $inferred_upper_bounds */ - public function getUntemplatedCopy(array $template_type_map, ?string $this_var_id) : self + public function getUntemplatedCopy(array $inferred_upper_bounds, ?string $this_var_id) : self { return new Assertion( \is_string($this->var_id) && $this_var_id @@ -42,20 +42,20 @@ class Assertion * * @return array{0: string} */ - function (array $rules) use ($template_type_map) : array { + function (array $rules) use ($inferred_upper_bounds) : array { $first_rule = $rules[0]; - if ($template_type_map) { + if ($inferred_upper_bounds) { $rule_tokens = \Psalm\Internal\Type\TypeTokenizer::tokenize($first_rule); $substitute = false; foreach ($rule_tokens as &$rule_token) { - if (isset($template_type_map[$rule_token[0]])) { - foreach ($template_type_map[$rule_token[0]] as [$type]) { + if (isset($inferred_upper_bounds[$rule_token[0]])) { + foreach ($inferred_upper_bounds[$rule_token[0]] as $bound) { $substitute = true; - $first_type = \array_values($type->getAtomicTypes())[0]; + $first_type = \array_values($bound->type->getAtomicTypes())[0]; if ($first_type instanceof \Psalm\Type\Atomic\TTemplateParam) { $rule_token[0] = $first_type->param_name; diff --git a/src/Psalm/Storage/ClassLikeStorage.php b/src/Psalm/Storage/ClassLikeStorage.php index 30ac3ba25..92f6738aa 100644 --- a/src/Psalm/Storage/ClassLikeStorage.php +++ b/src/Psalm/Storage/ClassLikeStorage.php @@ -292,7 +292,7 @@ class ClassLikeStorage public $overridden_property_ids = []; /** - * @var array>|null + * @var array>|null */ public $template_types; diff --git a/src/Psalm/Storage/FunctionLikeStorage.php b/src/Psalm/Storage/FunctionLikeStorage.php index 6e538f485..d0db6d940 100644 --- a/src/Psalm/Storage/FunctionLikeStorage.php +++ b/src/Psalm/Storage/FunctionLikeStorage.php @@ -102,7 +102,7 @@ abstract class FunctionLikeStorage public $global_types = []; /** - * @var array>|null + * @var array>|null */ public $template_types; diff --git a/src/Psalm/Type.php b/src/Psalm/Type.php index 5240daadf..8c1ec6493 100644 --- a/src/Psalm/Type.php +++ b/src/Psalm/Type.php @@ -54,7 +54,7 @@ abstract class Type * Parses a string type representation * * @param array{int,int}|null $php_version - * @param array> $template_type_map + * @param array> $template_type_map */ public static function parseString( string $type_string, diff --git a/src/Psalm/Type/Atomic.php b/src/Psalm/Type/Atomic.php index 4c08ed279..66ce7397e 100644 --- a/src/Psalm/Type/Atomic.php +++ b/src/Psalm/Type/Atomic.php @@ -89,7 +89,7 @@ abstract class Atomic implements TypeNode /** * @param array{int,int}|null $php_version - * @param array> $template_type_map + * @param array> $template_type_map * @param array $type_aliases */ public static function create( @@ -265,7 +265,7 @@ abstract class Atomic implements TypeNode return new TTemplateParam( $value, - $template_type_map[$value][$first_class][0], + $template_type_map[$value][$first_class], $first_class ); } diff --git a/src/Psalm/Type/Atomic/HasIntersectionTrait.php b/src/Psalm/Type/Atomic/HasIntersectionTrait.php index 24652be2c..4551917e7 100644 --- a/src/Psalm/Type/Atomic/HasIntersectionTrait.php +++ b/src/Psalm/Type/Atomic/HasIntersectionTrait.php @@ -85,7 +85,7 @@ trait HasIntersectionTrait && isset($template_result->upper_bounds[$extra_type->param_name][$extra_type->defining_class]) ) { $template_type = clone $template_result->upper_bounds - [$extra_type->param_name][$extra_type->defining_class][0]; + [$extra_type->param_name][$extra_type->defining_class]->type; foreach ($template_type->getAtomicTypes() as $template_type_part) { if ($template_type_part instanceof TNamedObject) { diff --git a/src/Psalm/Type/Reconciler.php b/src/Psalm/Type/Reconciler.php index e257497c8..8965eb053 100644 --- a/src/Psalm/Type/Reconciler.php +++ b/src/Psalm/Type/Reconciler.php @@ -50,7 +50,7 @@ class Reconciler * @param array $existing_types * @param array $changed_var_ids * @param array $referenced_var_ids - * @param array> $template_type_map + * @param array> $template_type_map * * @return array */ diff --git a/src/Psalm/Type/Union.php b/src/Psalm/Type/Union.php index 122411b5e..7a801f5ef 100644 --- a/src/Psalm/Type/Union.php +++ b/src/Psalm/Type/Union.php @@ -1035,7 +1035,7 @@ class Union implements TypeNode $is_mixed = false; - $found_generic_params = $template_result->upper_bounds ?: []; + $inferred_upper_bounds = $template_result->upper_bounds ?: []; foreach ($this->types as $key => $atomic_type) { $atomic_type->replaceTemplateTypesWithArgTypes($template_result, $codebase); @@ -1044,13 +1044,13 @@ class Union implements TypeNode $template_type = null; $traversed_type = \Psalm\Internal\Type\UnionTemplateHandler::getRootTemplateType( - $found_generic_params, + $inferred_upper_bounds, $atomic_type->param_name, $atomic_type->defining_class ); if ($traversed_type) { - $template_type = $traversed_type[0]; + $template_type = $traversed_type; if (!$atomic_type->as->isMixed() && $template_type->isMixed()) { $template_type = clone $atomic_type->as; @@ -1082,7 +1082,7 @@ class Union implements TypeNode } } } elseif ($codebase) { - foreach ($found_generic_params as $template_type_map) { + foreach ($inferred_upper_bounds as $template_type_map) { foreach ($template_type_map as $template_class => $_) { if (substr($template_class, 0, 3) === 'fn-') { continue; @@ -1098,11 +1098,12 @@ class Union implements TypeNode $param_map = $classlike_storage->template_type_extends[$defining_class]; if (isset($param_map[$key]) - && isset($found_generic_params[(string) $param_map[$key]][$template_class]) + && isset($inferred_upper_bounds[(string) $param_map[$key]][$template_class]) ) { + $template_name = (string) $param_map[$key]; + $template_type - = clone $found_generic_params - [(string) $param_map[$key]][$template_class][0]; + = clone $inferred_upper_bounds[$template_name][$template_class]->type; } } } @@ -1124,8 +1125,8 @@ class Union implements TypeNode } } } elseif ($atomic_type instanceof Type\Atomic\TTemplateParamClass) { - $template_type = isset($found_generic_params[$atomic_type->param_name][$atomic_type->defining_class]) - ? clone $found_generic_params[$atomic_type->param_name][$atomic_type->defining_class][0] + $template_type = isset($inferred_upper_bounds[$atomic_type->param_name][$atomic_type->defining_class]) + ? clone $inferred_upper_bounds[$atomic_type->param_name][$atomic_type->defining_class]->type : null; $class_template_type = null; @@ -1163,15 +1164,15 @@ class Union implements TypeNode $template_type = null; - if (isset($found_generic_params[$atomic_type->array_param_name][$atomic_type->defining_class]) - && !empty($found_generic_params[$atomic_type->offset_param_name]) + if (isset($inferred_upper_bounds[$atomic_type->array_param_name][$atomic_type->defining_class]) + && !empty($inferred_upper_bounds[$atomic_type->offset_param_name]) ) { $array_template_type - = $found_generic_params[$atomic_type->array_param_name][$atomic_type->defining_class][0]; + = $inferred_upper_bounds[$atomic_type->array_param_name][$atomic_type->defining_class]->type; $offset_template_type = array_values( - $found_generic_params[$atomic_type->offset_param_name] - )[0][0]; + $inferred_upper_bounds[$atomic_type->offset_param_name] + )[0]->type; if ($array_template_type->isSingle() && $offset_template_type->isSingle() @@ -1205,8 +1206,8 @@ class Union implements TypeNode } elseif ($atomic_type instanceof Type\Atomic\TConditional && $codebase ) { - $template_type = isset($found_generic_params[$atomic_type->param_name][$atomic_type->defining_class]) - ? clone $found_generic_params[$atomic_type->param_name][$atomic_type->defining_class][0] + $template_type = isset($inferred_upper_bounds[$atomic_type->param_name][$atomic_type->defining_class]) + ? clone $inferred_upper_bounds[$atomic_type->param_name][$atomic_type->defining_class]->type : null; $class_template_type = null; diff --git a/tests/TypeParseTest.php b/tests/TypeParseTest.php index 4aa682510..78a1dc05b 100644 --- a/tests/TypeParseTest.php +++ b/tests/TypeParseTest.php @@ -492,7 +492,7 @@ class TypeParseTest extends TestCase { $this->assertSame( '(T is string ? string : int)', - (string) Type::parseString('(T is string ? string : int)', null, ['T' => ['' => [Type::getArray()]]]) + (string) Type::parseString('(T is string ? string : int)', null, ['T' => ['' => Type::getArray()]]) ); } @@ -500,7 +500,7 @@ class TypeParseTest extends TestCase { $this->assertSame( '(T is string|true ? int|string : int)', - (string) Type::parseString('(T is "hello"|true ? string|int : int)', null, ['T' => ['' => [Type::getArray()]]]) + (string) Type::parseString('(T is "hello"|true ? string|int : int)', null, ['T' => ['' => Type::getArray()]]) ); } @@ -508,7 +508,7 @@ class TypeParseTest extends TestCase { $this->assertSame( '(T is array{a: string} ? string : int)', - (string) Type::parseString('(T is array{a: string} ? string : int)', null, ['T' => ['' => [Type::getArray()]]]) + (string) Type::parseString('(T is array{a: string} ? string : int)', null, ['T' => ['' => Type::getArray()]]) ); } @@ -516,7 +516,7 @@ class TypeParseTest extends TestCase { $this->assertSame( '(T is array ? string : int)', - (string) Type::parseString('(T is array ? string : int)', null, ['T' => ['' => [Type::getArray()]]]) + (string) Type::parseString('(T is array ? string : int)', null, ['T' => ['' => Type::getArray()]]) ); } @@ -524,7 +524,7 @@ class TypeParseTest extends TestCase { $this->assertSame( '(T is A&B ? string : int)', - (string) Type::parseString('(T is A&B ? string : int)', null, ['T' => ['' => [Type::getArray()]]]) + (string) Type::parseString('(T is A&B ? string : int)', null, ['T' => ['' => Type::getArray()]]) ); } @@ -532,21 +532,21 @@ class TypeParseTest extends TestCase { $this->assertSame( '(T is string ? string : int)', - (string) Type::parseString('(T is string?string:int)', null, ['T' => ['' => [Type::getArray()]]]) + (string) Type::parseString('(T is string?string:int)', null, ['T' => ['' => Type::getArray()]]) ); } public function testConditionalTypeWithCallableElseBool(): void { $this->expectException(\Psalm\Exception\TypeParseTreeException::class); - Type::parseString('(T is string ? callable() : bool)', null, ['T' => ['' => [Type::getArray()]]]); + Type::parseString('(T is string ? callable() : bool)', null, ['T' => ['' => Type::getArray()]]); } public function testConditionalTypeWithCallableReturningBoolElseBool(): void { $this->assertSame( '(T is string ? callable():bool : bool)', - (string) Type::parseString('(T is string ? (callable() : bool) : bool)', null, ['T' => ['' => [Type::getArray()]]]) + (string) Type::parseString('(T is string ? (callable() : bool) : bool)', null, ['T' => ['' => Type::getArray()]]) ); } @@ -557,7 +557,7 @@ class TypeParseTest extends TestCase (string) Type::parseString( '(T is string ? string : array)', null, - ['T' => ['' => [Type::getArray()]]] + ['T' => ['' => Type::getArray()]] ) ); } @@ -569,7 +569,7 @@ class TypeParseTest extends TestCase (string) Type::parseString( '(T is string ? (callable(string, string):string) : (callable(mixed...):mixed))', null, - ['T' => ['' => [Type::getArray()]]] + ['T' => ['' => Type::getArray()]] ) ); } @@ -581,7 +581,7 @@ class TypeParseTest extends TestCase (string) Type::parseString( '(T is string ? callable(string, string):string : callable(mixed...):mixed)', null, - ['T' => ['' => [Type::getArray()]]] + ['T' => ['' => Type::getArray()]] ) ); } @@ -742,7 +742,7 @@ class TypeParseTest extends TestCase { $this->assertSame( 'key-of', - (string)Type::parseString('key-of', null, ['T' => ['' => [Type::getArray()]]]) + (string)Type::parseString('key-of', null, ['T' => ['' => Type::getArray()]]) ); } @@ -754,10 +754,10 @@ class TypeParseTest extends TestCase 'T[K]', null, [ - 'T' => ['' => [Type::getArray()]], - 'K' => ['' => [new Type\Union([ + 'T' => ['' => Type::getArray()], + 'K' => ['' => new Type\Union([ new Type\Atomic\TTemplateKeyOf('T', 'fn-foo', Type::getMixed()) - ])]], + ])], ] ) );