@@ -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 */
0 commit comments