Skip to content

Commit 950bddf

Browse files
jrmajorondrejmirtes
authored andcommitted
Support unsealed array shapes
1 parent 6ff970a commit 950bddf

File tree

3 files changed

+94
-12
lines changed

3 files changed

+94
-12
lines changed

Diff for: src/Ast/Type/ArrayShapeNode.php

+12-2
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,25 @@ class ArrayShapeNode implements TypeNode
1313
/** @var ArrayShapeItemNode[] */
1414
public $items;
1515

16-
public function __construct(array $items)
16+
/** @var bool */
17+
public $sealed;
18+
19+
public function __construct(array $items, bool $sealed = true)
1720
{
1821
$this->items = $items;
22+
$this->sealed = $sealed;
1923
}
2024

2125

2226
public function __toString(): string
2327
{
24-
return 'array{' . implode(', ', $this->items) . '}';
28+
$items = $this->items;
29+
30+
if ($this->sealed) {
31+
$items[] = '...';
32+
}
33+
34+
return 'array{' . implode(', ', $items) . '}';
2535
}
2636

2737
}

Diff for: src/Parser/TypeParser.php

+13-10
Original file line numberDiff line numberDiff line change
@@ -503,29 +503,32 @@ private function tryParseArrayOrOffsetAccess(TokenIterator $tokens, Ast\Type\Typ
503503
private function parseArrayShape(TokenIterator $tokens, Ast\Type\TypeNode $type): Ast\Type\ArrayShapeNode
504504
{
505505
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET);
506-
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET)) {
507-
return new Ast\Type\ArrayShapeNode([]);
508-
}
509506

510-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
511-
$items = [$this->parseArrayShapeItem($tokens)];
507+
$items = [];
508+
$sealed = true;
512509

513-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
514-
while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) {
510+
do {
515511
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
512+
516513
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET)) {
517-
// trailing comma case
518514
return new Ast\Type\ArrayShapeNode($items);
519515
}
520516

517+
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_VARIADIC)) {
518+
$sealed = false;
519+
$tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA);
520+
break;
521+
}
522+
521523
$items[] = $this->parseArrayShapeItem($tokens);
524+
522525
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
523-
}
526+
} while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA));
524527

525528
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
526529
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET);
527530

528-
return new Ast\Type\ArrayShapeNode($items);
531+
return new Ast\Type\ArrayShapeNode($items, $sealed);
529532
}
530533

531534

Diff for: tests/PHPStan/Parser/TypeParserTest.php

+69
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,75 @@ public function provideParseData(): array
599599
),
600600
]),
601601
],
602+
[
603+
'array{a: int, b: int, ...}',
604+
new ArrayShapeNode([
605+
new ArrayShapeItemNode(
606+
new IdentifierTypeNode('a'),
607+
false,
608+
new IdentifierTypeNode('int')
609+
),
610+
new ArrayShapeItemNode(
611+
new IdentifierTypeNode('b'),
612+
false,
613+
new IdentifierTypeNode('int')
614+
),
615+
], false),
616+
],
617+
[
618+
'array{int, string, ...}',
619+
new ArrayShapeNode([
620+
new ArrayShapeItemNode(
621+
null,
622+
false,
623+
new IdentifierTypeNode('int')
624+
),
625+
new ArrayShapeItemNode(
626+
null,
627+
false,
628+
new IdentifierTypeNode('string')
629+
),
630+
], false),
631+
],
632+
[
633+
'array{...}',
634+
new ArrayShapeNode([], false),
635+
],
636+
[
637+
'array{
638+
* a: int,
639+
* ...
640+
*}',
641+
new ArrayShapeNode([
642+
new ArrayShapeItemNode(
643+
new IdentifierTypeNode('a'),
644+
false,
645+
new IdentifierTypeNode('int')
646+
),
647+
], false),
648+
],
649+
[
650+
'array{
651+
a: int,
652+
...,
653+
}',
654+
new ArrayShapeNode([
655+
new ArrayShapeItemNode(
656+
new IdentifierTypeNode('a'),
657+
false,
658+
new IdentifierTypeNode('int')
659+
),
660+
], false),
661+
],
662+
[
663+
'array{int, ..., string}',
664+
new ParserException(
665+
'string',
666+
Lexer::TOKEN_IDENTIFIER,
667+
16,
668+
Lexer::TOKEN_CLOSE_CURLY_BRACKET
669+
),
670+
],
602671
[
603672
'callable(): Foo',
604673
new CallableTypeNode(

0 commit comments

Comments
 (0)