mirror of
https://github.com/danog/psalm.git
synced 2025-01-21 21:31:13 +01:00
parent
45d058c2dd
commit
9089f77176
@ -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
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -455,7 +455,7 @@ abstract class ClassLikeAnalyzer extends SourceAnalyzer
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array<string, array{Type\Union}>>|null
|
||||
* @return array<string, array<string, Type\Union>>|null
|
||||
*/
|
||||
public function getTemplateTypeMap(): ?array
|
||||
{
|
||||
|
@ -35,7 +35,7 @@ class CommentAnalyzer
|
||||
public const TYPE_REGEX = '(\??\\\?[\(\)A-Za-z0-9_&\<\.=,\>\[\]\-\{\}:|?\\\\]*|\$[a-zA-Z_0-9_]+)';
|
||||
|
||||
/**
|
||||
* @param array<string, array<string, array{Type\Union}>>|null $template_type_map
|
||||
* @param array<string, array<string, Type\Union>>|null $template_type_map
|
||||
* @param array<string, TypeAlias> $type_aliases
|
||||
*
|
||||
* @throws DocblockParseException if there was a problem parsing the docblock
|
||||
@ -62,7 +62,7 @@ class CommentAnalyzer
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, array<string, array{Type\Union}>>|null $template_type_map
|
||||
* @param array<string, array<string, Type\Union>>|null $template_type_map
|
||||
* @param array<string, TypeAlias> $type_aliases
|
||||
*
|
||||
* @return list<VarDocblockComment>
|
||||
|
@ -603,7 +603,7 @@ class FileAnalyzer extends SourceAnalyzer
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array<string, array{Type\Union}>>|null
|
||||
* @return array<string, array<string, Type\Union>>|null
|
||||
*/
|
||||
public function getTemplateTypeMap(): ?array
|
||||
{
|
||||
|
@ -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<string, array<string, array{Type\Union}>>|null
|
||||
* @return array<string, array<string, Type\Union>>|null
|
||||
*/
|
||||
public function getTemplateTypeMap(): ?array
|
||||
{
|
||||
|
@ -1064,7 +1064,7 @@ class MethodComparator
|
||||
}
|
||||
}
|
||||
|
||||
$template_types[$key][$base_class_name] = [$mapped_type];
|
||||
$template_types[$key][$base_class_name] = $mapped_type;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -166,7 +166,7 @@ abstract class SourceAnalyzer implements StatementsSource
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array<string, array{Type\Union}>>|null
|
||||
* @return array<string, array<string, Type\Union>>|null
|
||||
*/
|
||||
public function getTemplateTypeMap(): ?array
|
||||
{
|
||||
|
@ -1005,13 +1005,9 @@ class ForeachAnalyzer
|
||||
? $iterator_atomic_type->type_params
|
||||
: array_values(
|
||||
array_map(
|
||||
/** @param array<string, array{0:Type\Union}> $arr */
|
||||
/** @param array<string, Type\Union> $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<string, array<int|string, Type\Union>> $template_type_extends
|
||||
* @param array<string, array<string, array{Type\Union}>> $class_template_types
|
||||
* @param array<string, array<string, Type\Union>> $class_template_types
|
||||
* @param array<int, Type\Union> $calling_type_params
|
||||
*/
|
||||
private static function getExtendedType(
|
||||
|
@ -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'
|
||||
)
|
||||
])
|
||||
]
|
||||
]
|
||||
);
|
||||
|
@ -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<string, array<string, array{Type\Union, 1?:int}>> $class_generic_params
|
||||
* @param array<string, array<string, Type\Union>> $class_generic_params
|
||||
* @return false|null
|
||||
*/
|
||||
public static function checkArgumentMatches(
|
||||
@ -190,9 +191,7 @@ class ArgumentAnalyzer
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, array<string, array{Type\Union, 1?:int}>> $class_generic_params
|
||||
* @param array<string, array<string, array{Type\Union, 1?:int}>> $generic_params
|
||||
* @param array<string, array<string, array{Type\Union}>> $template_types
|
||||
* @param array<string, array<string, Type\Union>> $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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<PhpParser\Node\Arg> $args
|
||||
* @param array<int, FunctionLikeParameter>|null $function_params
|
||||
* @param array<string, array<string, array{Type\Union, 1?:int}>>|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) {
|
||||
|
@ -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
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,7 @@ class ClassTemplateParamCollector
|
||||
{
|
||||
/**
|
||||
* @param lowercase-string $method_name
|
||||
* @return array<string, array<string, array{Type\Union, 1?:int}>>|null
|
||||
* @return array<string, array<string, Type\Union>>|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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -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())
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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())
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -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<string, array<string, array{Type\Union}>>
|
||||
* @param array<string, non-empty-array<string, array{Type\Union}>> $existing_template_types
|
||||
* @return array<string, array<string, Type\Union>>
|
||||
* @param array<string, non-empty-array<string, Type\Union>> $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<string, array<int|string, Type\Union>> $template_type_extends
|
||||
* @param array<string, array<string, array{Type\Union}>> $found_generic_params
|
||||
* @param array<string, array<string, Type\Union>> $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<PhpParser\Node\Arg> $args
|
||||
* @param array<string, array<string, array{Type\Union}>> $template_type_map,
|
||||
* @param array<string, array<string, TemplateBound>> $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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
])
|
||||
]
|
||||
]
|
||||
);
|
||||
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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()],
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -79,7 +79,7 @@ class ClassLikeNodeScanner
|
||||
private $classlike_type_aliases = [];
|
||||
|
||||
/**
|
||||
* @var array<string, array<string, array{Type\Union}>>
|
||||
* @var array<string, array<string, Type\Union>>
|
||||
*/
|
||||
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];
|
||||
|
@ -35,7 +35,7 @@ use function strlen;
|
||||
class FunctionLikeDocblockScanner
|
||||
{
|
||||
/**
|
||||
* @param array<string, non-empty-array<string, array{Type\Union}>> $existing_function_template_types
|
||||
* @param array<string, non-empty-array<string, Type\Union>> $existing_function_template_types
|
||||
* @param array<string, TypeAlias> $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<string, array<string, array{Type\Union}>> $template_types
|
||||
* @param array<string, array<string, Type\Union>> $template_types
|
||||
* @param array<string, TypeAlias>|null $type_aliases
|
||||
* @param array<string, array<string, array{Type\Union}>> $function_template_types
|
||||
* @param array<string, array<string, Type\Union>> $function_template_types
|
||||
*
|
||||
* @return array{array<int, array{0: string, 1: int, 2?: string}>, array<string, array<string, array{Type\Union}>>}
|
||||
* @return array{array<int, array{0: string, 1: int, 2?: string}>, array<string, array<string, Type\Union>>}
|
||||
*/
|
||||
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<string, array<string, array{Type\Union}>> $class_template_types
|
||||
* @param array<string, array<string, array{Type\Union}>> $function_template_types
|
||||
* @param array<string, array<string, Type\Union>> $class_template_types
|
||||
* @param array<string, array<string, Type\Union>> $function_template_types
|
||||
* @param array<string, TypeAlias> $type_aliases
|
||||
* @return non-empty-list<string>|null
|
||||
*/
|
||||
@ -964,8 +958,8 @@ class FunctionLikeDocblockScanner
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, array<string, array{Type\Union}>> $class_template_types
|
||||
* @param array<string, array<string, array{Type\Union}>> $function_template_types
|
||||
* @param array<string, array<string, Type\Union>> $class_template_types
|
||||
* @param array<string, array<string, Type\Union>> $function_template_types
|
||||
* @param array<string, TypeAlias> $type_aliases
|
||||
* @param array<int, array{type:string,name:string,line_number:int,start:int,end:int}> $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];
|
||||
|
@ -70,7 +70,7 @@ class FunctionLikeNodeScanner
|
||||
private $classlike_storage;
|
||||
|
||||
/**
|
||||
* @var array<string, non-empty-array<string, array{Type\Union}>>
|
||||
* @var array<string, non-empty-array<string, Type\Union>>
|
||||
*/
|
||||
private $existing_function_template_types;
|
||||
|
||||
@ -90,7 +90,7 @@ class FunctionLikeNodeScanner
|
||||
public $storage;
|
||||
|
||||
/**
|
||||
* @param array<string, non-empty-array<string, array{Type\Union}>> $existing_function_template_types
|
||||
* @param array<string, non-empty-array<string, Type\Union>> $existing_function_template_types
|
||||
* @param array<string, TypeAlias> $type_aliases
|
||||
*/
|
||||
public function __construct(
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -43,7 +43,7 @@ class AssertionReconciler extends \Psalm\Type\Reconciler
|
||||
* - notEmpty(Object|false) => Object
|
||||
*
|
||||
* @param string[] $suppressed_issues
|
||||
* @param array<string, array<string, array{Type\Union}>> $template_type_map
|
||||
* @param array<string, array<string, Type\Union>> $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<string, array<string, array{Type\Union}>> $template_type_map
|
||||
* @param array<string, array<string, Type\Union>> $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<string, array<string, array{0:Type\Union, 1?: int}>> $template_type_map
|
||||
* @param array<string, array<string, Type\Union>> $template_type_map
|
||||
*/
|
||||
private static function filterTypeWithAnother(
|
||||
Codebase $codebase,
|
||||
|
@ -26,7 +26,7 @@ use function substr;
|
||||
class NegatedAssertionReconciler extends Reconciler
|
||||
{
|
||||
/**
|
||||
* @param array<string, array<string, array{Type\Union}>> $template_type_map
|
||||
* @param array<string, array<string, Type\Union>> $template_type_map
|
||||
* @param string[] $suppressed_issues
|
||||
* @param 0|1|2 $failed_reconciliation
|
||||
*
|
||||
|
@ -30,7 +30,7 @@ use function substr;
|
||||
class SimpleNegatedAssertionReconciler extends Reconciler
|
||||
{
|
||||
/**
|
||||
* @param array<string, array<string, array{Type\Union}>> $template_type_map
|
||||
* @param array<string, array<string, Type\Union>> $template_type_map
|
||||
* @param string[] $suppressed_issues
|
||||
* @param 0|1|2 $failed_reconciliation
|
||||
*
|
||||
|
40
src/Psalm/Internal/Type/TemplateBound.php
Normal file
40
src/Psalm/Internal/Type/TemplateBound.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Psalm\Internal\Type;
|
||||
|
||||
use Psalm\Type\Union;
|
||||
|
||||
class TemplateBound
|
||||
{
|
||||
/**
|
||||
* @var Union
|
||||
*/
|
||||
public $type;
|
||||
|
||||
/**
|
||||
* This is the depth at which the template appears in a given type.
|
||||
*
|
||||
* In the type Foo<T, Bar<T, array<T>>> 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<T, string, T> 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;
|
||||
}
|
||||
}
|
@ -3,21 +3,22 @@
|
||||
namespace Psalm\Internal\Type;
|
||||
|
||||
use Psalm\Type\Union;
|
||||
use function array_map;
|
||||
|
||||
class TemplateResult
|
||||
{
|
||||
/**
|
||||
* @var array<string, array<string, array{0: Union}>>
|
||||
* @var array<string, array<string, Union>>
|
||||
*/
|
||||
public $template_types;
|
||||
|
||||
/**
|
||||
* @var array<string, array<string, array{0: Union, 1?: int, 2?: ?int}>>
|
||||
* @var array<string, array<string, TemplateBound>>
|
||||
*/
|
||||
public $upper_bounds;
|
||||
|
||||
/**
|
||||
* @var array<string, array<string, array{0: Union, 1?: int, 2?: ?int}>>
|
||||
* @var array<string, array<string, TemplateBound>>
|
||||
*/
|
||||
public $lower_bounds = [];
|
||||
|
||||
@ -27,12 +28,23 @@ class TemplateResult
|
||||
public $lower_bounds_unintersectable_types = [];
|
||||
|
||||
/**
|
||||
* @param array<string, array<string, array{0: Union}>> $template_types
|
||||
* @param array<string, array<string, array{0: Union, 1?: int, 2?: ?int}>> $upper_bounds
|
||||
* @param array<string, array<string, Union>> $template_types
|
||||
* @param array<string, array<string, Union>> $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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ class TypeParser
|
||||
*
|
||||
* @param list<array{0: string, 1: int, 2?: string}> $type_tokens
|
||||
* @param array{int,int}|null $php_version
|
||||
* @param array<string, array<string, array{Union}>> $template_type_map
|
||||
* @param array<string, array<string, Union>> $template_type_map
|
||||
* @param array<string, TypeAlias> $type_aliases
|
||||
*
|
||||
*/
|
||||
@ -110,7 +110,7 @@ class TypeParser
|
||||
|
||||
/**
|
||||
* @param array{int,int}|null $php_version
|
||||
* @param array<string, array<string, array{Union}>> $template_type_map
|
||||
* @param array<string, array<string, Union>> $template_type_map
|
||||
* @param array<string, TypeAlias> $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
|
||||
);
|
||||
}
|
||||
|
@ -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<string, array<string, array{Union, 1?:int}>> $template_types
|
||||
*
|
||||
* @return array{Union, 1?:int}|null
|
||||
* @param array<string, array<string, TemplateBound>> $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++;
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -22,7 +22,7 @@ interface StatementsSource extends FileSource
|
||||
public function getParentFQCLN(): ?string;
|
||||
|
||||
/**
|
||||
* @return array<string, array<string, array{Type\Union}>>|null
|
||||
* @return array<string, array<string, Type\Union>>|null
|
||||
*/
|
||||
public function getTemplateTypeMap(): ?array;
|
||||
|
||||
|
@ -28,9 +28,9 @@ class Assertion
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, array<string, array{0:\Psalm\Type\Union}>> $template_type_map
|
||||
* @param array<string, array<string, \Psalm\Internal\Type\TemplateBound>> $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;
|
||||
|
@ -292,7 +292,7 @@ class ClassLikeStorage
|
||||
public $overridden_property_ids = [];
|
||||
|
||||
/**
|
||||
* @var array<string, non-empty-array<string, array{Type\Union}>>|null
|
||||
* @var array<string, non-empty-array<string, Type\Union>>|null
|
||||
*/
|
||||
public $template_types;
|
||||
|
||||
|
@ -102,7 +102,7 @@ abstract class FunctionLikeStorage
|
||||
public $global_types = [];
|
||||
|
||||
/**
|
||||
* @var array<string, non-empty-array<string, array{Type\Union}>>|null
|
||||
* @var array<string, non-empty-array<string, Type\Union>>|null
|
||||
*/
|
||||
public $template_types;
|
||||
|
||||
|
@ -54,7 +54,7 @@ abstract class Type
|
||||
* Parses a string type representation
|
||||
*
|
||||
* @param array{int,int}|null $php_version
|
||||
* @param array<string, array<string, array{Type\Union}>> $template_type_map
|
||||
* @param array<string, array<string, Type\Union>> $template_type_map
|
||||
*/
|
||||
public static function parseString(
|
||||
string $type_string,
|
||||
|
@ -89,7 +89,7 @@ abstract class Atomic implements TypeNode
|
||||
|
||||
/**
|
||||
* @param array{int,int}|null $php_version
|
||||
* @param array<string, array<string, array{Union}>> $template_type_map
|
||||
* @param array<string, array<string, Union>> $template_type_map
|
||||
* @param array<string, TypeAlias> $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
|
||||
);
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -50,7 +50,7 @@ class Reconciler
|
||||
* @param array<string, Type\Union> $existing_types
|
||||
* @param array<string, bool> $changed_var_ids
|
||||
* @param array<string, bool> $referenced_var_ids
|
||||
* @param array<string, array<string, array{Type\Union}>> $template_type_map
|
||||
* @param array<string, array<string, Type\Union>> $template_type_map
|
||||
*
|
||||
* @return array<string, Type\Union>
|
||||
*/
|
||||
|
@ -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;
|
||||
|
@ -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<array-key, string> ? string : int)',
|
||||
(string) Type::parseString('(T is array<string> ? string : int)', null, ['T' => ['' => [Type::getArray()]]])
|
||||
(string) Type::parseString('(T is array<string> ? 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<string, string>)',
|
||||
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<T>',
|
||||
(string)Type::parseString('key-of<T>', null, ['T' => ['' => [Type::getArray()]]])
|
||||
(string)Type::parseString('key-of<T>', 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())
|
||||
])]],
|
||||
])],
|
||||
]
|
||||
)
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user