1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-30 04:39:00 +01:00

refactor: resolve TTypeAlias in intersections

This also merges `TKeyedArray` into a one single keyed array. Therefore, this is not limited to aliases anymore.

Signed-off-by: Maximilian Bösing <2189546+boesing@users.noreply.github.com>
This commit is contained in:
Maximilian Bösing 2023-04-11 23:22:04 +02:00
parent 094df271a4
commit 1071257245
No known key found for this signature in database
GPG Key ID: 9A8988C93CEC81A3
3 changed files with 171 additions and 55 deletions

View File

@ -410,7 +410,6 @@
<PossiblyUndefinedIntArrayOffset>
<code>$const_name</code>
<code>$const_name</code>
<code>$intersection_types[0]</code>
<code><![CDATA[$parse_tree->children[0]]]></code>
<code><![CDATA[$parse_tree->condition->children[0]]]></code>
<code>array_keys($offset_template_data)[0]</code>

View File

@ -1109,6 +1109,10 @@ class TypeParser
$intersection_types[$name] = $atomic_type;
}
if ($intersection_types === []) {
return new TMixed();
}
$first_type = reset($intersection_types);
$last_type = end($intersection_types);
@ -1192,30 +1196,10 @@ class TypeParser
);
}
$keyed_intersection_types = [];
if ($intersection_types[0] instanceof TTypeAlias) {
foreach ($intersection_types as $intersection_type) {
if (!$intersection_type instanceof TTypeAlias) {
throw new TypeParseTreeException(
'Intersection types with a type alias can only be comprised of other type aliases, '
. get_class($intersection_type) . ' provided',
$keyed_intersection_types = self::extractKeyedIntersectionTypes(
$codebase,
$intersection_types,
);
}
$keyed_intersection_types[$intersection_type->getKey()] = $intersection_type;
}
$first_type = array_shift($keyed_intersection_types);
if ($keyed_intersection_types) {
return $first_type->setIntersectionTypes($keyed_intersection_types);
}
return $first_type;
}
$keyed_intersection_types = self::extractKeyedIntersectionTypes($codebase, $intersection_types);
$intersect_static = false;
@ -1224,12 +1208,17 @@ class TypeParser
$intersect_static = true;
}
if (!$keyed_intersection_types && $intersect_static) {
if ($keyed_intersection_types === [] && $intersect_static) {
return new TNamedObject('static', false, false, [], $from_docblock);
}
$first_type = array_shift($keyed_intersection_types);
// Keyed array intersection are merged together and are not combinable with object-types
if ($first_type instanceof TKeyedArray) {
return $first_type;
}
if ($intersect_static
&& $first_type instanceof TNamedObject
) {
@ -1237,6 +1226,7 @@ class TypeParser
}
if ($keyed_intersection_types) {
/** @var non-empty-array<string,TIterable|TNamedObject|TCallableObject|TTemplateParam|TObjectWithProperties> $keyed_intersection_types */
return $first_type->setIntersectionTypes($keyed_intersection_types);
}
@ -1532,41 +1522,59 @@ class TypeParser
}
/**
* @param TNamedObject|TObjectWithProperties|TCallableObject|TIterable|TTemplateParam $intersection_type
* @param TNamedObject|TObjectWithProperties|TCallableObject|TIterable|TTemplateParam|TKeyedArray $intersection_type
*/
private static function extractIntersectionKey(Atomic $intersection_type): string
{
return $intersection_type instanceof TIterable
return $intersection_type instanceof TIterable || $intersection_type instanceof TKeyedArray
? $intersection_type->getId()
: $intersection_type->getKey();
}
private static function extractKeyedIntersectionTypes(Codebase $codebase, array $intersection_types): array
{
/**
* @param non-empty-array<Atomic> $intersection_types
* @return non-empty-array<string,TIterable|TNamedObject|TCallableObject|TTemplateParam|TObjectWithProperties|TKeyedArray>
*/
private static function extractKeyedIntersectionTypes(
Codebase $codebase,
array $intersection_types
): array {
$keyed_intersection_types = [];
$keyed_array_intersection_type = null;
$callable_intersection = null;
$any_object_type_found = false;
foreach ($intersection_types as $intersection_type) {
if ($intersection_type instanceof TTypeAlias) {
$replaced_intersection_type = $intersection_type;
$expanded = TypeExpander::expandAtomic(
$normalized_intersection_types = self::resolveTypeAliases(
$codebase,
$replaced_intersection_type,
null,
null,
null,
true,
true,
false,
true,
true,
true,
$intersection_types,
);
$keyed_intersection_types = array_merge(
$keyed_intersection_types,
self::extractKeyedIntersectionTypes($codebase, $expanded),
foreach ($normalized_intersection_types as $intersection_type) {
if ($intersection_type instanceof TKeyedArray
&& !$intersection_type instanceof TCallableKeyedArray
) {
if ($any_object_type_found) {
throw new TypeParseTreeException(
'The intersection type must not mix array and object types!',
);
}
if ($keyed_array_intersection_type === null) {
$keyed_array_intersection_type = $intersection_type;
$keyed_intersection_types[self::extractIntersectionKey($intersection_type)] = $intersection_type;
continue;
}
unset($keyed_intersection_types[self::extractIntersectionKey($keyed_array_intersection_type)]);
$keyed_array_intersection_type = self::mergeKeyedArray(
$codebase,
$keyed_array_intersection_type,
$intersection_type,
);
$keyed_intersection_types[self::extractIntersectionKey($keyed_array_intersection_type)]
= $keyed_array_intersection_type;
continue;
}
@ -1576,6 +1584,8 @@ class TypeParser
|| $intersection_type instanceof TTemplateParam
|| $intersection_type instanceof TObjectWithProperties
) {
$any_object_type_found = true;
$keyed_intersection_types[self::extractIntersectionKey($intersection_type)] = $intersection_type;
continue;
}
@ -1609,6 +1619,117 @@ class TypeParser
$keyed_intersection_types[self::extractIntersectionKey($callable_object_type)] = $callable_object_type;
}
assert($keyed_intersection_types !== []);
return $keyed_intersection_types;
}
/**
* @param array<Atomic> $intersection_types
* @return array<Atomic>
*/
private static function resolveTypeAliases(Codebase $codebase, array $intersection_types): array
{
$normalized_intersection_types = [];
$modified = false;
foreach ($intersection_types as $intersection_type) {
if (!$intersection_type instanceof TTypeAlias) {
$normalized_intersection_types[] = [$intersection_type];
continue;
}
$modified = true;
$normalized_intersection_types[] = TypeExpander::expandAtomic(
$codebase,
$intersection_type,
null,
null,
null,
true,
false,
false,
true,
true,
true,
);
}
if ($modified === false) {
return $intersection_types;
}
return self::resolveTypeAliases(
$codebase,
array_merge(...$normalized_intersection_types),
);
}
private static function mergeKeyedArray(
Codebase $codebase,
TKeyedArray $keyed_array1,
TKeyedArray $keyed_array2
): TKeyedArray {
if ($keyed_array1 instanceof TCallableKeyedArray || $keyed_array2 instanceof TCallableKeyedArray) {
throw new TypeParseTreeException('Callable keyed arrays are not mergeable!');
}
$properties = $keyed_array1->properties;
foreach ($keyed_array2->properties as $array_key => $array_value_type) {
$existing_type = $keyed_array1->properties[$array_key] ?? null;
if ($existing_type === null) {
$properties[$array_key] = $array_value_type;
continue;
}
$properties[$array_key] = Type::combineUnionTypes(
$existing_type,
$array_value_type,
$codebase,
true,
false,
500,
false,
);
}
$fallback_params = null;
if ($keyed_array1->fallback_params !== null || $keyed_array2->fallback_params !== null) {
$all_fallback_params = [0 => [], 1 => []];
$all_fallback_params[0][0] = $keyed_array1->fallback_params[0] ?? null;
$all_fallback_params[0][1] = $keyed_array2->fallback_params[0] ?? null;
$all_fallback_params[1][0] = $keyed_array1->fallback_params[1] ?? null;
$all_fallback_params[1][1] = $keyed_array2->fallback_params[1] ?? null;
$fallback_params[0] = Type::combineUnionTypes(
$all_fallback_params[0][0],
$all_fallback_params[0][1] ?? null,
$codebase,
true,
false,
500,
false,
);
$fallback_params[1] = Type::combineUnionTypes(
$all_fallback_params[1][0] ?? null,
$all_fallback_params[1][1] ?? null,
$codebase,
true,
false,
500,
false,
);
/** @var list{Union,Union} $fallback_params */
}
return new TKeyedArray(
$properties,
null,
$fallback_params,
$keyed_array1->is_list && $keyed_array2->is_list,
$keyed_array1->from_docblock || $keyed_array2->from_docblock,
);
}
}

View File

@ -10,10 +10,6 @@ use Psalm\Internal\Type\TemplateStandinTypeReplacer;
use Psalm\Internal\Type\TypeCombiner;
use Psalm\Type;
use Psalm\Type\Atomic;
use Psalm\Type\Atomic\TArray;
use Psalm\Type\Atomic\TLiteralClassString;
use Psalm\Type\Atomic\TLiteralInt;
use Psalm\Type\Atomic\TNonEmptyArray;
use Psalm\Type\Union;
use UnexpectedValueException;
@ -49,7 +45,7 @@ class TKeyedArray extends Atomic
/**
* If the shape has fallback params then they are here
*
* @var ?list{Union, Union}
* @var array{Union, Union}|null
*/
public $fallback_params;
@ -67,7 +63,7 @@ class TKeyedArray extends Atomic
* Constructs a new instance of a generic type
*
* @param non-empty-array<string|int, Union> $properties
* @param ?list{Union, Union} $fallback_params
* @param array{Union, Union}|null $fallback_params
* @param array<string, bool> $class_strings
*/
public function __construct(