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

Break out replacement of templated types with their inferred result

This commit is contained in:
Matt Brown 2020-11-29 16:16:16 -05:00 committed by Daniil Gentili
parent e9ec1b28a2
commit 67d319657a
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
25 changed files with 431 additions and 325 deletions

View File

@ -6,6 +6,7 @@ use Psalm\Codebase;
use Psalm\CodeLocation;
use Psalm\Internal\MethodIdentifier;
use Psalm\Internal\PhpVisitor\ParamReplacementVisitor;
use Psalm\Internal\Type\TemplateInferredTypeReplacer;
use Psalm\Internal\Type\Comparator\TypeComparisonResult;
use Psalm\Internal\Type\Comparator\UnionTypeComparator;
use Psalm\Issue\ImplementedParamTypeMismatch;
@ -1068,7 +1069,8 @@ class MethodComparator
$template_result = new \Psalm\Internal\Type\TemplateResult([], $template_types);
$templated_type->replaceTemplateTypesWithArgTypes(
TemplateInferredTypeReplacer::replaceTemplateTypesWithArgTypes(
$templated_type,
$template_result,
$codebase
);

View File

@ -8,6 +8,7 @@ use Psalm\Internal\Analyzer\Statements\Expression\Fetch\ArrayFetchAnalyzer;
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Internal\Codebase\VariableUseGraph;
use Psalm\Internal\Type\TemplateInferredTypeReplacer;
use Psalm\Context;
use Psalm\IssueBuffer;
use Psalm\Issue\InvalidArrayAssignment;
@ -521,7 +522,8 @@ class ArrayAssignmentAnalyzer
]
);
$current_type->replaceTemplateTypesWithArgTypes(
TemplateInferredTypeReplacer::replaceTemplateTypesWithArgTypes(
$current_type,
$template_result,
$codebase
);

View File

@ -18,6 +18,7 @@ use Psalm\Internal\MethodIdentifier;
use Psalm\Internal\Type\TemplateBound;
use Psalm\Internal\Type\TemplateResult;
use Psalm\Internal\Type\UnionTemplateHandler;
use Psalm\Internal\Type\TemplateInferredTypeReplacer;
use Psalm\CodeLocation;
use Psalm\Context;
use Psalm\Issue\ImplicitToStringCast;

View File

@ -17,6 +17,7 @@ use Psalm\Internal\Type\Comparator\UnionTypeComparator;
use Psalm\Internal\MethodIdentifier;
use Psalm\Internal\Type\TemplateResult;
use Psalm\Internal\Type\UnionTemplateHandler;
use Psalm\Internal\Type\TemplateInferredTypeReplacer;
use Psalm\CodeLocation;
use Psalm\Context;
use Psalm\Issue\InvalidPassByReference;
@ -347,7 +348,8 @@ class ArgumentsAnalyzer
'fn-' . ($context->calling_method_id ?: $context->calling_function_id)
);
$replaced_type->replaceTemplateTypesWithArgTypes(
TemplateInferredTypeReplacer::replaceTemplateTypesWithArgTypes(
$replaced_type,
$replace_template_result,
$codebase
);
@ -1001,7 +1003,8 @@ class ArgumentsAnalyzer
);
if ($template_result->upper_bounds) {
$original_by_ref_type->replaceTemplateTypesWithArgTypes(
TemplateInferredTypeReplacer::replaceTemplateTypesWithArgTypes(
$original_by_ref_type,
$template_result,
$codebase
);
@ -1024,7 +1027,8 @@ class ArgumentsAnalyzer
);
if ($template_result->upper_bounds) {
$original_by_ref_out_type->replaceTemplateTypesWithArgTypes(
TemplateInferredTypeReplacer::replaceTemplateTypesWithArgTypes(
$original_by_ref_out_type,
$template_result,
$codebase
);

View File

@ -11,6 +11,7 @@ use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Internal\Codebase\InternalCallMapHandler;
use Psalm\Internal\Type\TypeCombiner;
use Psalm\Internal\Type\UnionTemplateHandler;
use Psalm\Internal\Type\TemplateInferredTypeReplacer;
use Psalm\Internal\Type\Comparator\UnionTypeComparator;
use Psalm\CodeLocation;
use Psalm\Context;
@ -841,7 +842,7 @@ class ArrayFunctionArgumentsAnalyzer
$context->calling_method_id ?: $context->calling_function_id
);
$closure_type->return_type->replaceTemplateTypesWithArgTypes(
$closure_type->replaceTemplateTypesWithArgTypes(
$template_result,
$codebase
);

View File

@ -12,6 +12,7 @@ use Psalm\Internal\DataFlow\TaintSource;
use Psalm\Internal\DataFlow\DataFlowNode;
use Psalm\Internal\Codebase\TaintFlowGraph;
use Psalm\Internal\Type\TypeExpander;
use Psalm\Internal\Type\TemplateInferredTypeReplacer;
use Psalm\Storage\FunctionLikeStorage;
use Psalm\Type;
use Psalm\Type\Atomic\TCallable;
@ -104,7 +105,8 @@ class FunctionCallReturnTypeFetcher
null
);
$return_type->replaceTemplateTypesWithArgTypes(
TemplateInferredTypeReplacer::replaceTemplateTypesWithArgTypes(
$return_type,
$template_result,
$codebase
);
@ -494,7 +496,8 @@ class FunctionCallReturnTypeFetcher
foreach ($function_storage->conditionally_removed_taints as $conditionally_removed_taint) {
$conditionally_removed_taint = clone $conditionally_removed_taint;
$conditionally_removed_taint->replaceTemplateTypesWithArgTypes(
TemplateInferredTypeReplacer::replaceTemplateTypesWithArgTypes(
$conditionally_removed_taint,
$template_result,
$codebase
);

View File

@ -11,6 +11,7 @@ use Psalm\Context;
use Psalm\Internal\MethodIdentifier;
use Psalm\Internal\Type\TemplateBound;
use Psalm\Internal\Type\TemplateResult;
use Psalm\Internal\Type\TemplateInferredTypeReplacer;
use Psalm\Type;
use Psalm\Type\Atomic\TGenericObject;
use function strtolower;
@ -461,7 +462,8 @@ class MethodCallReturnTypeFetcher
null
);
$return_type_candidate->replaceTemplateTypesWithArgTypes(
TemplateInferredTypeReplacer::replaceTemplateTypesWithArgTypes(
$return_type_candidate,
$template_result,
$codebase
);

View File

@ -9,6 +9,7 @@ use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\CodeLocation;
use Psalm\Context;
use Psalm\Internal\MethodIdentifier;
use Psalm\Internal\Type\TemplateInferredTypeReplacer;
use Psalm\Issue\NonStaticSelfCall;
use Psalm\Issue\ParentNotFound;
use Psalm\IssueBuffer;
@ -280,7 +281,8 @@ class StaticCallAnalyzer extends CallAnalyzer
foreach ($method_storage->conditionally_removed_taints as $conditionally_removed_taint) {
$conditionally_removed_taint = clone $conditionally_removed_taint;
$conditionally_removed_taint->replaceTemplateTypesWithArgTypes(
TemplateInferredTypeReplacer::replaceTemplateTypesWithArgTypes(
$conditionally_removed_taint,
$template_result,
$codebase
);

View File

@ -11,6 +11,7 @@ use Psalm\Context;
use Psalm\Internal\FileManipulation\FileManipulationBuffer;
use Psalm\Internal\MethodIdentifier;
use Psalm\Internal\Type\TemplateBound;
use Psalm\Internal\Type\TemplateInferredTypeReplacer;
use Psalm\Issue\AbstractMethodCall;
use Psalm\Issue\ImpureMethodCall;
use Psalm\IssueBuffer;
@ -282,7 +283,8 @@ class ExistingAtomicStaticCallAnalyzer
null
);
$return_type_candidate->replaceTemplateTypesWithArgTypes(
TemplateInferredTypeReplacer::replaceTemplateTypesWithArgTypes(
$return_type_candidate,
$template_result,
$codebase
);

View File

@ -7,6 +7,7 @@ use Psalm\Internal\Analyzer\Statements\Expression\ExpressionIdentifier;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Internal\Type\Comparator\UnionTypeComparator;
use Psalm\Internal\Codebase\TaintFlowGraph;
use Psalm\Internal\Type\TemplateInferredTypeReplacer;
use Psalm\CodeLocation;
use Psalm\Context;
use Psalm\Issue\EmptyArrayAccess;
@ -1329,7 +1330,8 @@ class ArrayFetchAnalyzer
$expected_value_param_get = clone $type->value_param;
$expected_value_param_get->replaceTemplateTypesWithArgTypes(
TemplateInferredTypeReplacer::replaceTemplateTypesWithArgTypes(
$expected_value_param_get,
$template_result_get,
$codebase
);
@ -1337,7 +1339,8 @@ class ArrayFetchAnalyzer
if ($replacement_type) {
$expected_value_param_set = clone $type->value_param;
$replacement_type->replaceTemplateTypesWithArgTypes(
TemplateInferredTypeReplacer::replaceTemplateTypesWithArgTypes(
$replacement_type,
$template_result_set,
$codebase
);

View File

@ -11,6 +11,7 @@ use Psalm\Internal\Analyzer\Statements\Expression\ExpressionIdentifier;
use Psalm\Internal\Analyzer\Statements\Expression\Assignment\InstancePropertyAssignmentAnalyzer;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Internal\Type\TemplateResult;
use Psalm\Internal\Type\TemplateInferredTypeReplacer;
use Psalm\CodeLocation;
use Psalm\Context;
use Psalm\Issue\DeprecatedProperty;
@ -751,7 +752,8 @@ class AtomicPropertyFetchAnalyzer
}
}
$class_property_type->replaceTemplateTypesWithArgTypes(
TemplateInferredTypeReplacer::replaceTemplateTypesWithArgTypes(
$class_property_type,
new TemplateResult([], $template_types),
$codebase
);

View File

@ -17,6 +17,7 @@ use Psalm\Exception\DocblockParseException;
use Psalm\Internal\DataFlow\DataFlowNode;
use Psalm\Internal\Codebase\TaintFlowGraph;
use Psalm\Internal\Type\TemplateResult;
use Psalm\Internal\Type\TemplateInferredTypeReplacer;
use Psalm\Issue\FalsableReturnStatement;
use Psalm\Issue\InvalidDocblock;
use Psalm\Issue\InvalidReturnStatement;
@ -268,7 +269,8 @@ class ReturnAnalyzer
$local_return_type = clone $local_return_type;
$local_return_type->replaceTemplateTypesWithArgTypes(
TemplateInferredTypeReplacer::replaceTemplateTypesWithArgTypes(
$local_return_type,
new TemplateResult([], $found_generic_params),
$codebase
);

View File

@ -737,7 +737,8 @@ class Populator
$mapped_name = $parent_template_type_names[$i] ?? null;
if ($mapped_name) {
$storage->template_extended_params[$implemented_interface_storage->name][$mapped_name] = $type;
$storage->template_extended_params[$implemented_interface_storage->name][$mapped_name]
= $type;
}
}

View File

@ -10,6 +10,7 @@ use Psalm\CodeLocation;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Internal\Analyzer\Statements\Expression\Fetch\VariableFetchAnalyzer;
use Psalm\Internal\Analyzer\TraitAnalyzer;
use Psalm\Internal\Type\TemplateInferredTypeReplacer;
use Psalm\Internal\Type\Comparator\AtomicTypeComparator;
use Psalm\Internal\Type\Comparator\UnionTypeComparator;
use Psalm\Issue\DocblockTypeContradiction;
@ -832,7 +833,8 @@ class AssertionReconciler extends \Psalm\Type\Reconciler
);
if ($template_type_map) {
$new_param->replaceTemplateTypesWithArgTypes(
TemplateInferredTypeReplacer::replaceTemplateTypesWithArgTypes(
$new_param,
new TemplateResult([], $template_type_map),
$codebase
);
@ -880,7 +882,8 @@ class AssertionReconciler extends \Psalm\Type\Reconciler
);
if ($template_type_map) {
$new_param->replaceTemplateTypesWithArgTypes(
TemplateInferredTypeReplacer::replaceTemplateTypesWithArgTypes(
$new_param,
new TemplateResult([], $template_type_map),
$codebase
);

View File

@ -50,7 +50,7 @@ class GenericTypeComparator
if (!empty($class_storage->template_extended_params[$container_class])) {
$input_type_part = new TGenericObject(
$input_type_part->value,
array_values($class_storage->template_extended_params[$container_class])
\array_values($class_storage->template_extended_params[$container_class])
);
}
}

View File

@ -0,0 +1,331 @@
<?php
namespace Psalm\Internal\Type;
use Psalm\Codebase;
use Psalm\CodeLocation;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Internal\Type\Comparator\CallableTypeComparator;
use Psalm\Internal\Type\Comparator\UnionTypeComparator;
use Psalm\Internal\Type\TemplateResult;
use Psalm\Internal\Type\TypeCombiner;
use Psalm\StatementsSource;
use Psalm\Storage\FileStorage;
use Psalm\Type;
use Psalm\Type\Atomic;
use Psalm\Type\Atomic\TFloat;
use Psalm\Type\Atomic\TInt;
use Psalm\Type\Atomic\TIterable;
use Psalm\Type\Atomic\TLiteralFloat;
use Psalm\Type\Atomic\TLiteralInt;
use Psalm\Type\Atomic\TLiteralString;
use Psalm\Type\Atomic\TNamedObject;
use Psalm\Type\Atomic\TString;
use Psalm\Type\Atomic\TTemplateParam;
use Psalm\Type\Union;
use function array_filter;
use function array_merge;
use function array_shift;
use function array_unique;
use function array_values;
use function count;
use function get_class;
use function implode;
use function is_string;
use function reset;
use function sort;
use function strpos;
use function strval;
use function substr;
class TemplateInferredTypeReplacer
{
/**
* This replaces template types in unions with the inferred types they should be
*/
public static function replaceTemplateTypesWithArgTypes(
Union $union,
TemplateResult $template_result,
?Codebase $codebase
) : void {
$keys_to_unset = [];
$new_types = [];
$is_mixed = false;
$inferred_upper_bounds = $template_result->upper_bounds ?: [];
foreach ($union->getAtomicTypes() as $key => $atomic_type) {
$atomic_type->replaceTemplateTypesWithArgTypes($template_result, $codebase);
if ($atomic_type instanceof Atomic\TTemplateParam) {
$template_type = null;
$traversed_type = \Psalm\Internal\Type\UnionTemplateHandler::getRootTemplateType(
$inferred_upper_bounds,
$atomic_type->param_name,
$atomic_type->defining_class
);
if ($traversed_type) {
$template_type = $traversed_type;
if (!$atomic_type->as->isMixed() && $template_type->isMixed()) {
$template_type = clone $atomic_type->as;
} else {
$template_type = clone $template_type;
}
if ($atomic_type->extra_types) {
foreach ($template_type->getAtomicTypes() as $template_type_key => $atomic_template_type) {
if ($atomic_template_type instanceof TNamedObject
|| $atomic_template_type instanceof TTemplateParam
|| $atomic_template_type instanceof TIterable
|| $atomic_template_type instanceof Atomic\TObjectWithProperties
) {
$atomic_template_type->extra_types = array_merge(
$atomic_type->extra_types,
$atomic_template_type->extra_types ?: []
);
} elseif ($atomic_template_type instanceof Atomic\TObject) {
$first_atomic_type = array_shift($atomic_type->extra_types);
if ($atomic_type->extra_types) {
$first_atomic_type->extra_types = $atomic_type->extra_types;
}
$template_type->removeType($template_type_key);
$template_type->addType($first_atomic_type);
}
}
}
} elseif ($codebase) {
foreach ($inferred_upper_bounds as $template_type_map) {
foreach ($template_type_map as $template_class => $_) {
if (substr($template_class, 0, 3) === 'fn-') {
continue;
}
try {
$classlike_storage = $codebase->classlike_storage_provider->get($template_class);
if ($classlike_storage->template_extended_params) {
$defining_class = $atomic_type->defining_class;
if (isset($classlike_storage->template_extended_params[$defining_class])) {
$param_map = $classlike_storage->template_extended_params[$defining_class];
if (isset($param_map[$key])
&& isset($inferred_upper_bounds[(string) $param_map[$key]][$template_class])
) {
$template_name = (string) $param_map[$key];
$template_type
= clone $inferred_upper_bounds[$template_name][$template_class]->type;
}
}
}
} catch (\InvalidArgumentException $e) {
}
}
}
}
if ($template_type) {
$keys_to_unset[] = $key;
foreach ($template_type->getAtomicTypes() as $template_type_part) {
if ($template_type_part instanceof Atomic\TMixed) {
$is_mixed = true;
}
$new_types[$template_type_part->getKey()] = $template_type_part;
}
}
} elseif ($atomic_type instanceof Atomic\TTemplateParamClass) {
$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;
if ($template_type) {
foreach ($template_type->getAtomicTypes() as $template_type_part) {
if ($template_type_part instanceof Atomic\TMixed
|| $template_type_part instanceof Atomic\TObject
) {
$class_template_type = new Atomic\TClassString();
} elseif ($template_type_part instanceof Atomic\TNamedObject) {
$class_template_type = new Atomic\TClassString(
$template_type_part->value,
$template_type_part
);
} elseif ($template_type_part instanceof Atomic\TTemplateParam) {
$first_atomic_type = array_values($template_type_part->as->getAtomicTypes())[0];
$class_template_type = new Atomic\TTemplateParamClass(
$template_type_part->param_name,
$template_type_part->as->getId(),
$first_atomic_type instanceof TNamedObject ? $first_atomic_type : null,
$template_type_part->defining_class
);
}
}
}
if ($class_template_type) {
$keys_to_unset[] = $key;
$new_types[$class_template_type->getKey()] = $class_template_type;
}
} elseif ($atomic_type instanceof Atomic\TTemplateIndexedAccess) {
$keys_to_unset[] = $key;
$template_type = null;
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
= $inferred_upper_bounds[$atomic_type->array_param_name][$atomic_type->defining_class]->type;
$offset_template_type
= array_values(
$inferred_upper_bounds[$atomic_type->offset_param_name]
)[0]->type;
if ($array_template_type->isSingle()
&& $offset_template_type->isSingle()
&& !$array_template_type->isMixed()
&& !$offset_template_type->isMixed()
) {
$array_template_type = array_values($array_template_type->getAtomicTypes())[0];
$offset_template_type = array_values($offset_template_type->getAtomicTypes())[0];
if ($array_template_type instanceof Atomic\TKeyedArray
&& ($offset_template_type instanceof Atomic\TLiteralString
|| $offset_template_type instanceof Atomic\TLiteralInt)
&& isset($array_template_type->properties[$offset_template_type->value])
) {
$template_type = clone $array_template_type->properties[$offset_template_type->value];
}
}
}
if ($template_type) {
foreach ($template_type->getAtomicTypes() as $template_type_part) {
if ($template_type_part instanceof Atomic\TMixed) {
$is_mixed = true;
}
$new_types[$template_type_part->getKey()] = $template_type_part;
}
} else {
$new_types[$key] = new Atomic\TMixed();
}
} elseif ($atomic_type instanceof Atomic\TConditional
&& $codebase
) {
$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;
$atomic_type = clone $atomic_type;
if ($template_type) {
self::replaceTemplateTypesWithArgTypes(
$atomic_type->as_type,
$template_result,
$codebase
);
if ($atomic_type->as_type->isNullable() && $template_type->isVoid()) {
$template_type = Type::getNull();
}
if (UnionTypeComparator::isContainedBy(
$codebase,
$template_type,
$atomic_type->conditional_type
)) {
$class_template_type = clone $atomic_type->if_type;
self::replaceTemplateTypesWithArgTypes(
$class_template_type,
$template_result,
$codebase
);
} elseif (UnionTypeComparator::isContainedBy(
$codebase,
$template_type,
$atomic_type->as_type
)
&& !UnionTypeComparator::isContainedBy(
$codebase,
$atomic_type->as_type,
$template_type
)
) {
$class_template_type = clone $atomic_type->else_type;
self::replaceTemplateTypesWithArgTypes(
$class_template_type,
$template_result,
$codebase
);
}
}
if (!$class_template_type) {
self::replaceTemplateTypesWithArgTypes(
$atomic_type->if_type,
$template_result,
$codebase
);
self::replaceTemplateTypesWithArgTypes(
$atomic_type->else_type,
$template_result,
$codebase
);
$class_template_type = Type::combineUnionTypes(
$atomic_type->if_type,
$atomic_type->else_type,
$codebase
);
}
$keys_to_unset[] = $key;
foreach ($class_template_type->getAtomicTypes() as $class_template_atomic_type) {
$new_types[$class_template_atomic_type->getKey()] = $class_template_atomic_type;
}
}
}
$union->bustCache();
if ($is_mixed) {
if (!$new_types) {
throw new \UnexpectedValueException('This array should be full');
}
$union->replaceTypes($new_types);
return;
}
foreach ($keys_to_unset as $key) {
$union->removeType($key);
}
$atomic_types = array_values(array_merge($union->getAtomicTypes(), $new_types));
$union->replaceTypes(
TypeCombiner::combine(
$atomic_types,
$codebase
)->getAtomicTypes()
);
}
}

View File

@ -8,6 +8,7 @@ use Psalm\Internal\Type\Comparator\UnionTypeComparator;
use Psalm\Type\Union;
use Psalm\Type\Atomic;
use Psalm\Internal\Type\Comparator\CallableTypeComparator;
use Psalm\Internal\Type\TemplateInferredTypeReplacer;
use function array_merge;
use function array_values;
use function count;
@ -17,6 +18,9 @@ use function substr;
class UnionTemplateHandler
{
/**
* This replaces template types in unions with standins (normally the template as type)
*/
public static function replaceTemplateTypesWithStandins(
Union $union_type,
TemplateResult $template_result,
@ -974,7 +978,9 @@ class UnionTemplateHandler
}
$new_input_param = clone $new_input_param;
$new_input_param->replaceTemplateTypesWithArgTypes(
TemplateInferredTypeReplacer::replaceTemplateTypesWithArgTypes(
$new_input_param,
new TemplateResult([], $replacement_templates),
$codebase
);

View File

@ -8,6 +8,7 @@ use Psalm\Codebase;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Internal\Type\TemplateResult;
use Psalm\Internal\Type\UnionTemplateHandler;
use Psalm\Internal\Type\TemplateInferredTypeReplacer;
use Psalm\Storage\FunctionLikeParameter;
use Psalm\Type\Atomic;
use Psalm\Type\Union;
@ -252,12 +253,20 @@ trait CallableTrait
continue;
}
$param->type->replaceTemplateTypesWithArgTypes($template_result, $codebase);
TemplateInferredTypeReplacer::replaceTemplateTypesWithArgTypes(
$param->type,
$template_result,
$codebase
);
}
}
if ($this->return_type) {
$this->return_type->replaceTemplateTypesWithArgTypes($template_result, $codebase);
TemplateInferredTypeReplacer::replaceTemplateTypesWithArgTypes(
$this->return_type,
$template_result,
$codebase
);
}
}

View File

@ -7,6 +7,7 @@ use Psalm\Codebase;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Internal\Type\TemplateResult;
use Psalm\Internal\Type\UnionTemplateHandler;
use Psalm\Internal\Type\TemplateInferredTypeReplacer;
use Psalm\Type;
use Psalm\Type\Atomic;
use Psalm\Type\Union;
@ -224,7 +225,11 @@ trait GenericTrait
?Codebase $codebase
) : void {
foreach ($this->type_params as $offset => $type_param) {
$type_param->replaceTemplateTypesWithArgTypes($template_result, $codebase);
TemplateInferredTypeReplacer::replaceTemplateTypesWithArgTypes(
$type_param,
$template_result,
$codebase
);
if ($this instanceof Atomic\TArray && $offset === 0 && $type_param->isMixed()) {
$this->type_params[0] = \Psalm\Type::getArrayKey();

View File

@ -6,6 +6,7 @@ use Psalm\Codebase;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Internal\Type\TemplateResult;
use Psalm\Internal\Type\UnionTemplateHandler;
use Psalm\Internal\Type\TemplateInferredTypeReplacer;
use Psalm\Type;
use Psalm\Type\Atomic;
use Psalm\Type\Union;
@ -196,7 +197,11 @@ class TClassStringMap extends \Psalm\Type\Atomic
TemplateResult $template_result,
?Codebase $codebase
) : void {
$this->value_param->replaceTemplateTypesWithArgTypes($template_result, $codebase);
TemplateInferredTypeReplacer::replaceTemplateTypesWithArgTypes(
$this->value_param,
$template_result,
$codebase
);
}
public function getChildNodes() : array

View File

@ -3,6 +3,7 @@ namespace Psalm\Type\Atomic;
use Psalm\Codebase;
use Psalm\Internal\Type\TemplateResult;
use Psalm\Internal\Type\TemplateInferredTypeReplacer;
use Psalm\Type\Union;
class TConditional extends \Psalm\Type\Atomic
@ -133,6 +134,10 @@ class TConditional extends \Psalm\Type\Atomic
TemplateResult $template_result,
?Codebase $codebase
) : void {
$this->conditional_type->replaceTemplateTypesWithArgTypes($template_result, $codebase);
TemplateInferredTypeReplacer::replaceTemplateTypesWithArgTypes(
$this->conditional_type,
$template_result,
$codebase
);
}
}

View File

@ -13,6 +13,7 @@ use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Internal\Type\TypeCombiner;
use Psalm\Internal\Type\TemplateResult;
use Psalm\Internal\Type\UnionTemplateHandler;
use Psalm\Internal\Type\TemplateInferredTypeReplacer;
use Psalm\Type;
use Psalm\Type\Atomic;
use Psalm\Type\Union;
@ -352,7 +353,8 @@ class TKeyedArray extends \Psalm\Type\Atomic
?Codebase $codebase
) : void {
foreach ($this->properties as $property) {
$property->replaceTemplateTypesWithArgTypes(
TemplateInferredTypeReplacer::replaceTemplateTypesWithArgTypes(
$property,
$template_result,
$codebase
);

View File

@ -6,6 +6,7 @@ use Psalm\Codebase;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Internal\Type\TemplateResult;
use Psalm\Internal\Type\UnionTemplateHandler;
use Psalm\Internal\Type\TemplateInferredTypeReplacer;
use Psalm\Type;
use Psalm\Type\Atomic;
use Psalm\Type\Union;
@ -166,7 +167,11 @@ class TList extends \Psalm\Type\Atomic
TemplateResult $template_result,
?Codebase $codebase
) : void {
$this->type_param->replaceTemplateTypesWithArgTypes($template_result, $codebase);
TemplateInferredTypeReplacer::replaceTemplateTypesWithArgTypes(
$this->type_param,
$template_result,
$codebase
);
}
public function equals(Atomic $other_type): bool

View File

@ -11,6 +11,7 @@ use Psalm\Type\Union;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Internal\Type\TemplateResult;
use Psalm\Internal\Type\UnionTemplateHandler;
use Psalm\Internal\Type\TemplateInferredTypeReplacer;
use function array_merge;
use function array_values;
@ -258,7 +259,8 @@ class TObjectWithProperties extends TObject
?Codebase $codebase
) : void {
foreach ($this->properties as $property) {
$property->replaceTemplateTypesWithArgTypes(
TemplateInferredTypeReplacer::replaceTemplateTypesWithArgTypes(
$property,
$template_result,
$codebase
);

View File

@ -223,13 +223,11 @@ class Union implements TypeNode
}
/**
* @return array<string, Atomic>
* @deprecated in favour of getAtomicTypes()
* @psalm-suppress PossiblyUnusedMethod
* @param non-empty-array<string, Atomic> $types
*/
public function getTypes(): array
public function replaceTypes(array $types) : void
{
return $this->types;
$this->types = $types;
}
/**
@ -1041,298 +1039,6 @@ class Union implements TypeNode
$this->id = null;
}
public function replaceTemplateTypesWithArgTypes(
TemplateResult $template_result,
?Codebase $codebase
) : void {
$keys_to_unset = [];
$new_types = [];
$is_mixed = false;
$inferred_upper_bounds = $template_result->upper_bounds ?: [];
foreach ($this->types as $key => $atomic_type) {
$atomic_type->replaceTemplateTypesWithArgTypes($template_result, $codebase);
if ($atomic_type instanceof Type\Atomic\TTemplateParam) {
$template_type = null;
$traversed_type = \Psalm\Internal\Type\UnionTemplateHandler::getRootTemplateType(
$inferred_upper_bounds,
$atomic_type->param_name,
$atomic_type->defining_class
);
if ($traversed_type) {
$template_type = $traversed_type;
if (!$atomic_type->as->isMixed() && $template_type->isMixed()) {
$template_type = clone $atomic_type->as;
} else {
$template_type = clone $template_type;
}
if ($atomic_type->extra_types) {
foreach ($template_type->getAtomicTypes() as $template_type_key => $atomic_template_type) {
if ($atomic_template_type instanceof TNamedObject
|| $atomic_template_type instanceof TTemplateParam
|| $atomic_template_type instanceof TIterable
|| $atomic_template_type instanceof Type\Atomic\TObjectWithProperties
) {
$atomic_template_type->extra_types = array_merge(
$atomic_type->extra_types,
$atomic_template_type->extra_types ?: []
);
} elseif ($atomic_template_type instanceof Type\Atomic\TObject) {
$first_atomic_type = array_shift($atomic_type->extra_types);
if ($atomic_type->extra_types) {
$first_atomic_type->extra_types = $atomic_type->extra_types;
}
$template_type->removeType($template_type_key);
$template_type->addType($first_atomic_type);
}
}
}
} elseif ($codebase) {
foreach ($inferred_upper_bounds as $template_type_map) {
foreach ($template_type_map as $template_class => $_) {
if (substr($template_class, 0, 3) === 'fn-') {
continue;
}
try {
$classlike_storage = $codebase->classlike_storage_provider->get($template_class);
if ($classlike_storage->template_extended_params) {
$defining_class = $atomic_type->defining_class;
if (isset($classlike_storage->template_extended_params[$defining_class])) {
$param_map = $classlike_storage->template_extended_params[$defining_class];
if (isset($param_map[$key])
&& isset($inferred_upper_bounds[(string) $param_map[$key]][$template_class])
) {
$template_name = (string) $param_map[$key];
$template_type
= clone $inferred_upper_bounds[$template_name][$template_class]->type;
}
}
}
} catch (\InvalidArgumentException $e) {
}
}
}
}
if ($template_type) {
$keys_to_unset[] = $key;
foreach ($template_type->types as $template_type_part) {
if ($template_type_part instanceof Type\Atomic\TMixed) {
$is_mixed = true;
}
$new_types[$template_type_part->getKey()] = $template_type_part;
}
}
} elseif ($atomic_type instanceof Type\Atomic\TTemplateParamClass) {
$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;
if ($template_type) {
foreach ($template_type->types as $template_type_part) {
if ($template_type_part instanceof Type\Atomic\TMixed
|| $template_type_part instanceof Type\Atomic\TObject
) {
$class_template_type = new Type\Atomic\TClassString();
} elseif ($template_type_part instanceof Type\Atomic\TNamedObject) {
$class_template_type = new Type\Atomic\TClassString(
$template_type_part->value,
$template_type_part
);
} elseif ($template_type_part instanceof Type\Atomic\TTemplateParam) {
$first_atomic_type = array_values($template_type_part->as->types)[0];
$class_template_type = new Type\Atomic\TTemplateParamClass(
$template_type_part->param_name,
$template_type_part->as->getId(),
$first_atomic_type instanceof TNamedObject ? $first_atomic_type : null,
$template_type_part->defining_class
);
}
}
}
if ($class_template_type) {
$keys_to_unset[] = $key;
$new_types[$class_template_type->getKey()] = $class_template_type;
}
} elseif ($atomic_type instanceof Type\Atomic\TTemplateIndexedAccess) {
$keys_to_unset[] = $key;
$template_type = null;
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
= $inferred_upper_bounds[$atomic_type->array_param_name][$atomic_type->defining_class]->type;
$offset_template_type
= array_values(
$inferred_upper_bounds[$atomic_type->offset_param_name]
)[0]->type;
if ($array_template_type->isSingle()
&& $offset_template_type->isSingle()
&& !$array_template_type->isMixed()
&& !$offset_template_type->isMixed()
) {
$array_template_type = array_values($array_template_type->types)[0];
$offset_template_type = array_values($offset_template_type->types)[0];
if ($array_template_type instanceof Type\Atomic\TKeyedArray
&& ($offset_template_type instanceof Type\Atomic\TLiteralString
|| $offset_template_type instanceof Type\Atomic\TLiteralInt)
&& isset($array_template_type->properties[$offset_template_type->value])
) {
$template_type = clone $array_template_type->properties[$offset_template_type->value];
}
}
}
if ($template_type) {
foreach ($template_type->types as $template_type_part) {
if ($template_type_part instanceof Type\Atomic\TMixed) {
$is_mixed = true;
}
$new_types[$template_type_part->getKey()] = $template_type_part;
}
} else {
$new_types[$key] = new Type\Atomic\TMixed();
}
} elseif ($atomic_type instanceof Type\Atomic\TConditional
&& $codebase
) {
$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;
$atomic_type = clone $atomic_type;
if ($template_type) {
$atomic_type->as_type->replaceTemplateTypesWithArgTypes(
$template_result,
$codebase
);
if ($atomic_type->as_type->isNullable() && $template_type->isVoid()) {
$template_type = Type::getNull();
}
if (UnionTypeComparator::isContainedBy(
$codebase,
$template_type,
$atomic_type->conditional_type
)) {
$class_template_type = clone $atomic_type->if_type;
$class_template_type->replaceTemplateTypesWithArgTypes(
$template_result,
$codebase
);
} elseif (UnionTypeComparator::isContainedBy(
$codebase,
$template_type,
$atomic_type->as_type
)
&& !UnionTypeComparator::isContainedBy(
$codebase,
$atomic_type->as_type,
$template_type
)
) {
$class_template_type = clone $atomic_type->else_type;
$class_template_type->replaceTemplateTypesWithArgTypes(
$template_result,
$codebase
);
}
}
if (!$class_template_type) {
$atomic_type->if_type->replaceTemplateTypesWithArgTypes(
$template_result,
$codebase
);
$atomic_type->else_type->replaceTemplateTypesWithArgTypes(
$template_result,
$codebase
);
$class_template_type = Type::combineUnionTypes(
$atomic_type->if_type,
$atomic_type->else_type,
$codebase
);
}
$keys_to_unset[] = $key;
foreach ($class_template_type->getAtomicTypes() as $class_template_atomic_type) {
$new_types[$class_template_atomic_type->getKey()] = $class_template_atomic_type;
}
}
}
$this->id = null;
if ($is_mixed) {
if (!$new_types) {
throw new \UnexpectedValueException('This array should be full');
}
$this->types = $new_types;
return;
}
foreach ($keys_to_unset as $key) {
unset($this->types[$key]);
}
foreach ($new_types as $type) {
if ($type instanceof TLiteralString) {
$this->literal_string_types[$type->getKey()] = $type;
} elseif ($type instanceof TLiteralInt) {
$this->literal_int_types[$type->getKey()] = $type;
} elseif ($type instanceof TLiteralFloat) {
$this->literal_float_types[$type->getKey()] = $type;
}
}
$atomic_types = array_values(array_merge($this->types, $new_types));
if ($atomic_types) {
$this->types = TypeCombiner::combine(
$atomic_types,
$codebase
)->getAtomicTypes();
$this->id = null;
}
}
public function isSingle(): bool
{
$type_count = count($this->types);