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:
parent
a97b6b8a5e
commit
d8b85f1c04
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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'));
|
||||
|
Loading…
x
Reference in New Issue
Block a user