1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-22 05:41:20 +01:00

Implement unsealed array generic syntax

This commit is contained in:
Daniil Gentili 2023-04-21 14:04:47 +02:00
parent a97b6b8a5e
commit d8b85f1c04
3 changed files with 69 additions and 2 deletions

View File

@ -13,9 +13,12 @@ class GenericTree extends ParseTree
public bool $terminated = false;
public function __construct(string $value, ?ParseTree $parent = null)
public bool $is_unsealed_array_shape;
public function __construct(string $value, ?ParseTree $parent = null, bool $is_unsealed_array_shape = false)
{
$this->value = $value;
$this->parent = $parent;
$this->is_unsealed_array_shape = $is_unsealed_array_shape;
}
}

View File

@ -64,11 +64,14 @@ class ParseTreeCreator
$type_token = $this->type_tokens[$this->t];
switch ($type_token[0]) {
case '<':
case '{':
case ']':
throw new TypeParseTreeException('Unexpected token ' . $type_token[0]);
case '<':
$this->handleLessThan();
break;
case '[':
$this->handleOpenSquareBracket();
break;
@ -95,6 +98,11 @@ class ParseTreeCreator
break;
case '}':
if ($this->current_leaf instanceof GenericTree
&& $this->current_leaf->is_unsealed_array_shape
) {
break;
}
do {
if ($this->current_leaf->parent === null) {
throw new TypeParseTreeException('Cannot parse array type');
@ -232,6 +240,57 @@ class ParseTreeCreator
$this->current_leaf = $new_parent_leaf;
}
private function handleLessThan(): void
{
if (!$this->current_leaf instanceof FieldEllipsis) {
throw new TypeParseTreeException('Unexpected token <');
}
$current_parent = $this->current_leaf->parent;
if (!$current_parent instanceof KeyedArrayTree) {
throw new TypeParseTreeException('Unexpected token <');
}
// Pop FieldEllipsis
array_pop($current_parent->children);
// Set the parent to the keyed array tree
$this->current_leaf = $current_parent;
$current_parent = $this->current_leaf->parent;
// Avoid array{a: int, ...<string, string>}&array<int, int>
if ($current_parent instanceof IntersectionTree) {
throw new TypeParseTreeException("Can't intersect an unsealed array with another array!");
}
// Otherwise replace the array tree with an intersection tree
$new_intersection_parent = new IntersectionTree($current_parent);
$this->current_leaf->parent = $new_intersection_parent;
// Append old keyed array tree and new generic array tree to intersection tree
$new_leaf = new GenericTree(
'array',
$new_intersection_parent,
true,
);
$new_intersection_parent->children = [
$this->current_leaf,
$new_leaf,
];
if ($current_parent) {
array_pop($current_parent->children);
$current_parent->children []= $new_intersection_parent;
} else {
$this->parse_tree = $new_intersection_parent;
}
$this->current_leaf = $new_leaf;
}
private function handleOpenSquareBracket(): void
{
if ($this->current_leaf instanceof Root) {

View File

@ -178,6 +178,11 @@ class TypeParseTest extends TestCase
);
}
public function testUnsealedArray(): void
{
$this->assertSame('array{a: int, ...<string, string>}', Type::parseString('array{a: int, ...<string, string>}')->getId());
}
public function testIntersectionAfterGeneric(): void
{
$this->assertSame('Countable&iterable<mixed, int>&I', (string) Type::parseString('Countable&iterable<int>&I'));