Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 5ceb0e3

Browse files
authoredAug 29, 2024··
Support typing extra items in unsealed array shapes
1 parent cc2b26c commit 5ceb0e3

File tree

6 files changed

+645
-4
lines changed

6 files changed

+645
-4
lines changed
 

‎src/Ast/Type/ArrayShapeNode.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,24 @@ class ArrayShapeNode implements TypeNode
2222
/** @var self::KIND_* */
2323
public $kind;
2424

25+
/** @var ArrayShapeUnsealedTypeNode|null */
26+
public $unsealedType;
27+
2528
/**
2629
* @param ArrayShapeItemNode[] $items
2730
* @param self::KIND_* $kind
2831
*/
29-
public function __construct(array $items, bool $sealed = true, string $kind = self::KIND_ARRAY)
32+
public function __construct(
33+
array $items,
34+
bool $sealed = true,
35+
string $kind = self::KIND_ARRAY,
36+
?ArrayShapeUnsealedTypeNode $unsealedType = null
37+
)
3038
{
3139
$this->items = $items;
3240
$this->sealed = $sealed;
3341
$this->kind = $kind;
42+
$this->unsealedType = $unsealedType;
3443
}
3544

3645

@@ -39,7 +48,7 @@ public function __toString(): string
3948
$items = $this->items;
4049

4150
if (! $this->sealed) {
42-
$items[] = '...';
51+
$items[] = '...' . $this->unsealedType;
4352
}
4453

4554
return $this->kind . '{' . implode(', ', $items) . '}';
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\PhpDocParser\Ast\Type;
4+
5+
use PHPStan\PhpDocParser\Ast\Node;
6+
use PHPStan\PhpDocParser\Ast\NodeAttributes;
7+
use function sprintf;
8+
9+
class ArrayShapeUnsealedTypeNode implements Node
10+
{
11+
12+
use NodeAttributes;
13+
14+
/** @var TypeNode */
15+
public $valueType;
16+
17+
/** @var TypeNode|null */
18+
public $keyType;
19+
20+
public function __construct(TypeNode $valueType, ?TypeNode $keyType)
21+
{
22+
$this->valueType = $valueType;
23+
$this->keyType = $keyType;
24+
}
25+
26+
public function __toString(): string
27+
{
28+
if ($this->keyType !== null) {
29+
return sprintf('<%s, %s>', $this->keyType, $this->valueType);
30+
}
31+
return sprintf('<%s>', $this->valueType);
32+
}
33+
34+
}

‎src/Parser/TypeParser.php

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -848,6 +848,7 @@ private function parseArrayShape(TokenIterator $tokens, Ast\Type\TypeNode $type,
848848

849849
$items = [];
850850
$sealed = true;
851+
$unsealedType = null;
851852

852853
do {
853854
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
@@ -858,6 +859,17 @@ private function parseArrayShape(TokenIterator $tokens, Ast\Type\TypeNode $type,
858859

859860
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_VARIADIC)) {
860861
$sealed = false;
862+
863+
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
864+
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)) {
865+
if ($kind === Ast\Type\ArrayShapeNode::KIND_ARRAY) {
866+
$unsealedType = $this->parseArrayShapeUnsealedType($tokens);
867+
} else {
868+
$unsealedType = $this->parseListShapeUnsealedType($tokens);
869+
}
870+
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
871+
}
872+
861873
$tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA);
862874
break;
863875
}
@@ -870,7 +882,7 @@ private function parseArrayShape(TokenIterator $tokens, Ast\Type\TypeNode $type,
870882
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
871883
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET);
872884

873-
return new Ast\Type\ArrayShapeNode($items, $sealed, $kind);
885+
return new Ast\Type\ArrayShapeNode($items, $sealed, $kind, $unsealedType);
874886
}
875887

876888

@@ -949,6 +961,63 @@ private function parseArrayShapeKey(TokenIterator $tokens)
949961
);
950962
}
951963

964+
/**
965+
* @phpstan-impure
966+
*/
967+
private function parseArrayShapeUnsealedType(TokenIterator $tokens): Ast\Type\ArrayShapeUnsealedTypeNode
968+
{
969+
$startLine = $tokens->currentTokenLine();
970+
$startIndex = $tokens->currentTokenIndex();
971+
972+
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET);
973+
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
974+
975+
$valueType = $this->parse($tokens);
976+
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
977+
978+
$keyType = null;
979+
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) {
980+
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
981+
982+
$keyType = $valueType;
983+
$valueType = $this->parse($tokens);
984+
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
985+
}
986+
987+
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET);
988+
989+
return $this->enrichWithAttributes(
990+
$tokens,
991+
new Ast\Type\ArrayShapeUnsealedTypeNode($valueType, $keyType),
992+
$startLine,
993+
$startIndex
994+
);
995+
}
996+
997+
/**
998+
* @phpstan-impure
999+
*/
1000+
private function parseListShapeUnsealedType(TokenIterator $tokens): Ast\Type\ArrayShapeUnsealedTypeNode
1001+
{
1002+
$startLine = $tokens->currentTokenLine();
1003+
$startIndex = $tokens->currentTokenIndex();
1004+
1005+
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET);
1006+
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
1007+
1008+
$valueType = $this->parse($tokens);
1009+
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
1010+
1011+
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET);
1012+
1013+
return $this->enrichWithAttributes(
1014+
$tokens,
1015+
new Ast\Type\ArrayShapeUnsealedTypeNode($valueType, null),
1016+
$startLine,
1017+
$startIndex
1018+
);
1019+
}
1020+
9521021
/**
9531022
* @phpstan-impure
9541023
*/

‎src/Printer/Printer.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
4444
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeItemNode;
4545
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode;
46+
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeUnsealedTypeNode;
4647
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
4748
use PHPStan\PhpDocParser\Ast\Type\CallableTypeNode;
4849
use PHPStan\PhpDocParser\Ast\Type\CallableTypeParameterNode;
@@ -229,6 +230,12 @@ function (PhpDocChildNode $child): string {
229230
$isOptional = $node->isOptional ? '=' : '';
230231
return trim("{$type}{$isReference}{$isVariadic}{$node->parameterName}") . $isOptional;
231232
}
233+
if ($node instanceof ArrayShapeUnsealedTypeNode) {
234+
if ($node->keyType !== null) {
235+
return sprintf('<%s, %s>', $this->printType($node->keyType), $this->printType($node->valueType));
236+
}
237+
return sprintf('<%s>', $this->printType($node->valueType));
238+
}
232239
if ($node instanceof DoctrineAnnotation) {
233240
return (string) $node;
234241
}
@@ -366,7 +373,7 @@ private function printType(TypeNode $node): string
366373
}, $node->items);
367374

368375
if (! $node->sealed) {
369-
$items[] = '...';
376+
$items[] = '...' . ($node->unsealedType === null ? '' : $this->print($node->unsealedType));
370377
}
371378

372379
return $node->kind . '{' . implode(', ', $items) . '}';

‎tests/PHPStan/Parser/TypeParserTest.php

Lines changed: 398 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode;
1414
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeItemNode;
1515
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode;
16+
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeUnsealedTypeNode;
1617
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
1718
use PHPStan\PhpDocParser\Ast\Type\CallableTypeNode;
1819
use PHPStan\PhpDocParser\Ast\Type\CallableTypeParameterNode;
@@ -760,6 +761,403 @@ public function provideParseData(): array
760761
ArrayShapeNode::KIND_LIST
761762
),
762763
],
764+
[
765+
'array{...<string>}',
766+
new ArrayShapeNode(
767+
[],
768+
false,
769+
ArrayShapeNode::KIND_ARRAY,
770+
new ArrayShapeUnsealedTypeNode(
771+
new IdentifierTypeNode('string'),
772+
null
773+
)
774+
),
775+
],
776+
[
777+
'array{a: int, b?: int, ...<string>}',
778+
new ArrayShapeNode(
779+
[
780+
new ArrayShapeItemNode(
781+
new IdentifierTypeNode('a'),
782+
false,
783+
new IdentifierTypeNode('int')
784+
),
785+
new ArrayShapeItemNode(
786+
new IdentifierTypeNode('b'),
787+
true,
788+
new IdentifierTypeNode('int')
789+
),
790+
],
791+
false,
792+
ArrayShapeNode::KIND_ARRAY,
793+
new ArrayShapeUnsealedTypeNode(
794+
new IdentifierTypeNode('string'),
795+
null
796+
)
797+
),
798+
],
799+
[
800+
'array{a:int,b?:int,...<string>}',
801+
new ArrayShapeNode(
802+
[
803+
new ArrayShapeItemNode(
804+
new IdentifierTypeNode('a'),
805+
false,
806+
new IdentifierTypeNode('int')
807+
),
808+
new ArrayShapeItemNode(
809+
new IdentifierTypeNode('b'),
810+
true,
811+
new IdentifierTypeNode('int')
812+
),
813+
],
814+
false,
815+
ArrayShapeNode::KIND_ARRAY,
816+
new ArrayShapeUnsealedTypeNode(
817+
new IdentifierTypeNode('string'),
818+
null
819+
)
820+
),
821+
],
822+
[
823+
'array{a: int, b?: int, ... ' . PHP_EOL
824+
. ' < ' . PHP_EOL
825+
. ' string ' . PHP_EOL
826+
. ' > ' . PHP_EOL
827+
. ' , ' . PHP_EOL
828+
. ' }',
829+
new ArrayShapeNode(
830+
[
831+
new ArrayShapeItemNode(
832+
new IdentifierTypeNode('a'),
833+
false,
834+
new IdentifierTypeNode('int')
835+
),
836+
new ArrayShapeItemNode(
837+
new IdentifierTypeNode('b'),
838+
true,
839+
new IdentifierTypeNode('int')
840+
),
841+
],
842+
false,
843+
ArrayShapeNode::KIND_ARRAY,
844+
new ArrayShapeUnsealedTypeNode(
845+
new IdentifierTypeNode('string'),
846+
null
847+
)
848+
),
849+
],
850+
[
851+
'array{...<int, string>}',
852+
new ArrayShapeNode(
853+
[],
854+
false,
855+
ArrayShapeNode::KIND_ARRAY,
856+
new ArrayShapeUnsealedTypeNode(
857+
new IdentifierTypeNode('string'),
858+
new IdentifierTypeNode('int')
859+
)
860+
),
861+
],
862+
[
863+
'array{a: int, b?: int, ...<int, string>}',
864+
new ArrayShapeNode(
865+
[
866+
new ArrayShapeItemNode(
867+
new IdentifierTypeNode('a'),
868+
false,
869+
new IdentifierTypeNode('int')
870+
),
871+
new ArrayShapeItemNode(
872+
new IdentifierTypeNode('b'),
873+
true,
874+
new IdentifierTypeNode('int')
875+
),
876+
],
877+
false,
878+
ArrayShapeNode::KIND_ARRAY,
879+
new ArrayShapeUnsealedTypeNode(
880+
new IdentifierTypeNode('string'),
881+
new IdentifierTypeNode('int')
882+
)
883+
),
884+
],
885+
[
886+
'array{a:int,b?:int,...<int,string>}',
887+
new ArrayShapeNode(
888+
[
889+
new ArrayShapeItemNode(
890+
new IdentifierTypeNode('a'),
891+
false,
892+
new IdentifierTypeNode('int')
893+
),
894+
new ArrayShapeItemNode(
895+
new IdentifierTypeNode('b'),
896+
true,
897+
new IdentifierTypeNode('int')
898+
),
899+
],
900+
false,
901+
ArrayShapeNode::KIND_ARRAY,
902+
new ArrayShapeUnsealedTypeNode(
903+
new IdentifierTypeNode('string'),
904+
new IdentifierTypeNode('int')
905+
)
906+
),
907+
],
908+
[
909+
'array{a: int, b?: int, ... ' . PHP_EOL
910+
. ' < ' . PHP_EOL
911+
. ' int ' . PHP_EOL
912+
. ' , ' . PHP_EOL
913+
. ' string ' . PHP_EOL
914+
. ' > ' . PHP_EOL
915+
. ' , ' . PHP_EOL
916+
. ' }',
917+
new ArrayShapeNode(
918+
[
919+
new ArrayShapeItemNode(
920+
new IdentifierTypeNode('a'),
921+
false,
922+
new IdentifierTypeNode('int')
923+
),
924+
new ArrayShapeItemNode(
925+
new IdentifierTypeNode('b'),
926+
true,
927+
new IdentifierTypeNode('int')
928+
),
929+
],
930+
false,
931+
ArrayShapeNode::KIND_ARRAY,
932+
new ArrayShapeUnsealedTypeNode(
933+
new IdentifierTypeNode('string'),
934+
new IdentifierTypeNode('int')
935+
)
936+
),
937+
],
938+
[
939+
'list{...<string>}',
940+
new ArrayShapeNode(
941+
[],
942+
false,
943+
ArrayShapeNode::KIND_LIST,
944+
new ArrayShapeUnsealedTypeNode(
945+
new IdentifierTypeNode('string'),
946+
null
947+
)
948+
),
949+
],
950+
[
951+
'list{int, int, ...<string>}',
952+
new ArrayShapeNode(
953+
[
954+
new ArrayShapeItemNode(
955+
null,
956+
false,
957+
new IdentifierTypeNode('int')
958+
),
959+
new ArrayShapeItemNode(
960+
null,
961+
false,
962+
new IdentifierTypeNode('int')
963+
),
964+
],
965+
false,
966+
ArrayShapeNode::KIND_LIST,
967+
new ArrayShapeUnsealedTypeNode(
968+
new IdentifierTypeNode('string'),
969+
null
970+
)
971+
),
972+
],
973+
[
974+
'list{int,int,...<string>}',
975+
new ArrayShapeNode(
976+
[
977+
new ArrayShapeItemNode(
978+
null,
979+
false,
980+
new IdentifierTypeNode('int')
981+
),
982+
new ArrayShapeItemNode(
983+
null,
984+
false,
985+
new IdentifierTypeNode('int')
986+
),
987+
],
988+
false,
989+
ArrayShapeNode::KIND_LIST,
990+
new ArrayShapeUnsealedTypeNode(
991+
new IdentifierTypeNode('string'),
992+
null
993+
)
994+
),
995+
],
996+
[
997+
'list{int, int, ... ' . PHP_EOL
998+
. ' < ' . PHP_EOL
999+
. ' string ' . PHP_EOL
1000+
. ' > ' . PHP_EOL
1001+
. ' , ' . PHP_EOL
1002+
. ' }',
1003+
new ArrayShapeNode(
1004+
[
1005+
new ArrayShapeItemNode(
1006+
null,
1007+
false,
1008+
new IdentifierTypeNode('int')
1009+
),
1010+
new ArrayShapeItemNode(
1011+
null,
1012+
false,
1013+
new IdentifierTypeNode('int')
1014+
),
1015+
],
1016+
false,
1017+
ArrayShapeNode::KIND_LIST,
1018+
new ArrayShapeUnsealedTypeNode(
1019+
new IdentifierTypeNode('string'),
1020+
null
1021+
)
1022+
),
1023+
],
1024+
[
1025+
'list{0: int, 1?: int, ...<string>}',
1026+
new ArrayShapeNode(
1027+
[
1028+
new ArrayShapeItemNode(
1029+
new ConstExprIntegerNode('0'),
1030+
false,
1031+
new IdentifierTypeNode('int')
1032+
),
1033+
new ArrayShapeItemNode(
1034+
new ConstExprIntegerNode('1'),
1035+
true,
1036+
new IdentifierTypeNode('int')
1037+
),
1038+
],
1039+
false,
1040+
ArrayShapeNode::KIND_LIST,
1041+
new ArrayShapeUnsealedTypeNode(
1042+
new IdentifierTypeNode('string'),
1043+
null
1044+
)
1045+
),
1046+
],
1047+
[
1048+
'list{0:int,1?:int,...<string>}',
1049+
new ArrayShapeNode(
1050+
[
1051+
new ArrayShapeItemNode(
1052+
new ConstExprIntegerNode('0'),
1053+
false,
1054+
new IdentifierTypeNode('int')
1055+
),
1056+
new ArrayShapeItemNode(
1057+
new ConstExprIntegerNode('1'),
1058+
true,
1059+
new IdentifierTypeNode('int')
1060+
),
1061+
],
1062+
false,
1063+
ArrayShapeNode::KIND_LIST,
1064+
new ArrayShapeUnsealedTypeNode(
1065+
new IdentifierTypeNode('string'),
1066+
null
1067+
)
1068+
),
1069+
],
1070+
[
1071+
'list{0: int, 1?: int, ... ' . PHP_EOL
1072+
. ' < ' . PHP_EOL
1073+
. ' string ' . PHP_EOL
1074+
. ' > ' . PHP_EOL
1075+
. ' , ' . PHP_EOL
1076+
. ' }',
1077+
new ArrayShapeNode(
1078+
[
1079+
new ArrayShapeItemNode(
1080+
new ConstExprIntegerNode('0'),
1081+
false,
1082+
new IdentifierTypeNode('int')
1083+
),
1084+
new ArrayShapeItemNode(
1085+
new ConstExprIntegerNode('1'),
1086+
true,
1087+
new IdentifierTypeNode('int')
1088+
),
1089+
],
1090+
false,
1091+
ArrayShapeNode::KIND_LIST,
1092+
new ArrayShapeUnsealedTypeNode(
1093+
new IdentifierTypeNode('string'),
1094+
null
1095+
)
1096+
),
1097+
],
1098+
[
1099+
'array{...<>}',
1100+
new ParserException(
1101+
'>',
1102+
Lexer::TOKEN_CLOSE_ANGLE_BRACKET,
1103+
10,
1104+
Lexer::TOKEN_IDENTIFIER
1105+
),
1106+
],
1107+
[
1108+
'array{...<int,>}',
1109+
new ParserException(
1110+
'>',
1111+
Lexer::TOKEN_CLOSE_ANGLE_BRACKET,
1112+
14,
1113+
Lexer::TOKEN_IDENTIFIER
1114+
),
1115+
],
1116+
[
1117+
'array{...<int, string,>}',
1118+
new ParserException(
1119+
',',
1120+
Lexer::TOKEN_COMMA,
1121+
21,
1122+
Lexer::TOKEN_CLOSE_ANGLE_BRACKET
1123+
),
1124+
],
1125+
[
1126+
'array{...<int, string, string>}',
1127+
new ParserException(
1128+
',',
1129+
Lexer::TOKEN_COMMA,
1130+
21,
1131+
Lexer::TOKEN_CLOSE_ANGLE_BRACKET
1132+
),
1133+
],
1134+
[
1135+
'list{...<>}',
1136+
new ParserException(
1137+
'>',
1138+
Lexer::TOKEN_CLOSE_ANGLE_BRACKET,
1139+
9,
1140+
Lexer::TOKEN_IDENTIFIER
1141+
),
1142+
],
1143+
[
1144+
'list{...<int,>}',
1145+
new ParserException(
1146+
',',
1147+
Lexer::TOKEN_COMMA,
1148+
12,
1149+
Lexer::TOKEN_CLOSE_ANGLE_BRACKET
1150+
),
1151+
],
1152+
[
1153+
'list{...<int, string>}',
1154+
new ParserException(
1155+
',',
1156+
Lexer::TOKEN_COMMA,
1157+
12,
1158+
Lexer::TOKEN_CLOSE_ANGLE_BRACKET
1159+
),
1160+
],
7631161
[
7641162
'callable(): Foo',
7651163
new CallableTypeNode(

‎tests/PHPStan/Printer/PrinterTest.php

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
use PHPStan\PhpDocParser\Ast\PhpDoc\TypeAliasImportTagValueNode;
2929
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeItemNode;
3030
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode;
31+
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeUnsealedTypeNode;
3132
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
3233
use PHPStan\PhpDocParser\Ast\Type\CallableTypeNode;
3334
use PHPStan\PhpDocParser\Ast\Type\CallableTypeParameterNode;
@@ -51,6 +52,7 @@
5152
use function array_splice;
5253
use function array_unshift;
5354
use function array_values;
55+
use function assert;
5456
use function count;
5557
use const PHP_EOL;
5658

@@ -1740,6 +1742,128 @@ public function enterNode(Node $node)
17401742

17411743
},
17421744
];
1745+
1746+
yield [
1747+
'/** @return array{foo: int, ...} */',
1748+
'/** @return array{foo: int} */',
1749+
new class extends AbstractNodeVisitor {
1750+
1751+
public function enterNode(Node $node)
1752+
{
1753+
if ($node instanceof ArrayShapeNode) {
1754+
$node->sealed = true;
1755+
}
1756+
1757+
return $node;
1758+
}
1759+
1760+
},
1761+
];
1762+
1763+
yield [
1764+
'/** @return array{foo: int, ...} */',
1765+
'/** @return array{foo: int, ...<string>} */',
1766+
new class extends AbstractNodeVisitor {
1767+
1768+
public function enterNode(Node $node)
1769+
{
1770+
if ($node instanceof ArrayShapeNode) {
1771+
$node->unsealedType = new ArrayShapeUnsealedTypeNode(new IdentifierTypeNode('string'), null);
1772+
}
1773+
1774+
return $node;
1775+
}
1776+
1777+
},
1778+
];
1779+
1780+
yield [
1781+
'/** @return array{foo: int, ...<string>} */',
1782+
'/** @return array{foo: int, ...<int, string>} */',
1783+
new class extends AbstractNodeVisitor {
1784+
1785+
public function enterNode(Node $node)
1786+
{
1787+
if ($node instanceof ArrayShapeNode) {
1788+
assert($node->unsealedType !== null);
1789+
$node->unsealedType->keyType = new IdentifierTypeNode('int');
1790+
}
1791+
1792+
return $node;
1793+
}
1794+
1795+
},
1796+
];
1797+
1798+
yield [
1799+
'/** @return array{foo: int, ...<string>} */',
1800+
'/** @return array{foo: int} */',
1801+
new class extends AbstractNodeVisitor {
1802+
1803+
public function enterNode(Node $node)
1804+
{
1805+
if ($node instanceof ArrayShapeNode) {
1806+
$node->sealed = true;
1807+
$node->unsealedType = null;
1808+
}
1809+
1810+
return $node;
1811+
}
1812+
1813+
},
1814+
];
1815+
1816+
yield [
1817+
'/** @return list{int, ...} */',
1818+
'/** @return list{int} */',
1819+
new class extends AbstractNodeVisitor {
1820+
1821+
public function enterNode(Node $node)
1822+
{
1823+
if ($node instanceof ArrayShapeNode) {
1824+
$node->sealed = true;
1825+
}
1826+
1827+
return $node;
1828+
}
1829+
1830+
},
1831+
];
1832+
1833+
yield [
1834+
'/** @return list{int, ...} */',
1835+
'/** @return list{int, ...<string>} */',
1836+
new class extends AbstractNodeVisitor {
1837+
1838+
public function enterNode(Node $node)
1839+
{
1840+
if ($node instanceof ArrayShapeNode) {
1841+
$node->unsealedType = new ArrayShapeUnsealedTypeNode(new IdentifierTypeNode('string'), null);
1842+
}
1843+
1844+
return $node;
1845+
}
1846+
1847+
},
1848+
];
1849+
1850+
yield [
1851+
'/** @return list{int, ...<string>} */',
1852+
'/** @return list{int} */',
1853+
new class extends AbstractNodeVisitor {
1854+
1855+
public function enterNode(Node $node)
1856+
{
1857+
if ($node instanceof ArrayShapeNode) {
1858+
$node->sealed = true;
1859+
$node->unsealedType = null;
1860+
}
1861+
1862+
return $node;
1863+
}
1864+
1865+
},
1866+
];
17431867
}
17441868

17451869
/**

0 commit comments

Comments
 (0)
Please sign in to comment.