Skip to content

Commit 72e51f7

Browse files
authored
TypeParser: Allow multiple newlines and also allow multiline union and intersection types for array shapes
1 parent 81de606 commit 72e51f7

File tree

3 files changed

+309
-42
lines changed

3 files changed

+309
-42
lines changed

src/Parser/TokenIterator.php

+13
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,19 @@ public function tryConsumeTokenType(int $tokenType): bool
205205
}
206206

207207

208+
/** @phpstan-impure */
209+
public function skipNewLineTokens(): void
210+
{
211+
if (!$this->isCurrentTokenType(Lexer::TOKEN_PHPDOC_EOL)) {
212+
return;
213+
}
214+
215+
do {
216+
$foundNewLine = $this->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
217+
} while ($foundNewLine === true);
218+
}
219+
220+
208221
private function detectNewline(): void
209222
{
210223
$value = $this->currentTokenValue();

src/Parser/TypeParser.php

+72-42
Original file line numberDiff line numberDiff line change
@@ -40,17 +40,44 @@ public function parse(TokenIterator $tokens): Ast\Type\TypeNode
4040
} else {
4141
$type = $this->parseAtomic($tokens);
4242

43-
if ($tokens->isCurrentTokenType(Lexer::TOKEN_UNION)) {
44-
$type = $this->parseUnion($tokens, $type);
43+
$tokens->pushSavePoint();
44+
$tokens->skipNewLineTokens();
45+
46+
try {
47+
$enrichedType = $this->enrichTypeOnUnionOrIntersection($tokens, $type);
48+
49+
} catch (ParserException $parserException) {
50+
$enrichedType = null;
51+
}
4552

46-
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_INTERSECTION)) {
47-
$type = $this->parseIntersection($tokens, $type);
53+
if ($enrichedType !== null) {
54+
$type = $enrichedType;
55+
$tokens->dropSavePoint();
56+
57+
} else {
58+
$tokens->rollback();
59+
$type = $this->enrichTypeOnUnionOrIntersection($tokens, $type) ?? $type;
4860
}
4961
}
5062

5163
return $this->enrichWithAttributes($tokens, $type, $startLine, $startIndex);
5264
}
5365

66+
/** @phpstan-impure */
67+
private function enrichTypeOnUnionOrIntersection(TokenIterator $tokens, Ast\Type\TypeNode $type): ?Ast\Type\TypeNode
68+
{
69+
if ($tokens->isCurrentTokenType(Lexer::TOKEN_UNION)) {
70+
return $this->parseUnion($tokens, $type);
71+
72+
}
73+
74+
if ($tokens->isCurrentTokenType(Lexer::TOKEN_INTERSECTION)) {
75+
return $this->parseIntersection($tokens, $type);
76+
}
77+
78+
return null;
79+
}
80+
5481
/**
5582
* @internal
5683
* @template T of Ast\Node
@@ -90,7 +117,7 @@ private function subParse(TokenIterator $tokens): Ast\Type\TypeNode
90117
if ($tokens->isCurrentTokenValue('is')) {
91118
$type = $this->parseConditional($tokens, $type);
92119
} else {
93-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
120+
$tokens->skipNewLineTokens();
94121

95122
if ($tokens->isCurrentTokenType(Lexer::TOKEN_UNION)) {
96123
$type = $this->subParseUnion($tokens, $type);
@@ -112,9 +139,9 @@ private function parseAtomic(TokenIterator $tokens): Ast\Type\TypeNode
112139
$startIndex = $tokens->currentTokenIndex();
113140

114141
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) {
115-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
142+
$tokens->skipNewLineTokens();
116143
$type = $this->subParse($tokens);
117-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
144+
$tokens->skipNewLineTokens();
118145

119146
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES);
120147

@@ -256,9 +283,9 @@ private function subParseUnion(TokenIterator $tokens, Ast\Type\TypeNode $type):
256283
$types = [$type];
257284

258285
while ($tokens->tryConsumeTokenType(Lexer::TOKEN_UNION)) {
259-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
286+
$tokens->skipNewLineTokens();
260287
$types[] = $this->parseAtomic($tokens);
261-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
288+
$tokens->skipNewLineTokens();
262289
}
263290

264291
return new Ast\Type\UnionTypeNode($types);
@@ -284,9 +311,9 @@ private function subParseIntersection(TokenIterator $tokens, Ast\Type\TypeNode $
284311
$types = [$type];
285312

286313
while ($tokens->tryConsumeTokenType(Lexer::TOKEN_INTERSECTION)) {
287-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
314+
$tokens->skipNewLineTokens();
288315
$types[] = $this->parseAtomic($tokens);
289-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
316+
$tokens->skipNewLineTokens();
290317
}
291318

292319
return new Ast\Type\IntersectionTypeNode($types);
@@ -306,15 +333,15 @@ private function parseConditional(TokenIterator $tokens, Ast\Type\TypeNode $subj
306333

307334
$targetType = $this->parse($tokens);
308335

309-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
336+
$tokens->skipNewLineTokens();
310337
$tokens->consumeTokenType(Lexer::TOKEN_NULLABLE);
311-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
338+
$tokens->skipNewLineTokens();
312339

313340
$ifType = $this->parse($tokens);
314341

315-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
342+
$tokens->skipNewLineTokens();
316343
$tokens->consumeTokenType(Lexer::TOKEN_COLON);
317-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
344+
$tokens->skipNewLineTokens();
318345

319346
$elseType = $this->subParse($tokens);
320347

@@ -335,15 +362,15 @@ private function parseConditionalForParameter(TokenIterator $tokens, string $par
335362

336363
$targetType = $this->parse($tokens);
337364

338-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
365+
$tokens->skipNewLineTokens();
339366
$tokens->consumeTokenType(Lexer::TOKEN_NULLABLE);
340-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
367+
$tokens->skipNewLineTokens();
341368

342369
$ifType = $this->parse($tokens);
343370

344-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
371+
$tokens->skipNewLineTokens();
345372
$tokens->consumeTokenType(Lexer::TOKEN_COLON);
346-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
373+
$tokens->skipNewLineTokens();
347374

348375
$elseType = $this->subParse($tokens);
349376

@@ -409,8 +436,11 @@ public function parseGeneric(TokenIterator $tokens, Ast\Type\IdentifierTypeNode
409436
$variances = [];
410437

411438
$isFirst = true;
412-
while ($isFirst || $tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) {
413-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
439+
while (
440+
$isFirst
441+
|| $tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)
442+
) {
443+
$tokens->skipNewLineTokens();
414444

415445
// trailing comma case
416446
if (!$isFirst && $tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET)) {
@@ -419,7 +449,7 @@ public function parseGeneric(TokenIterator $tokens, Ast\Type\IdentifierTypeNode
419449
$isFirst = false;
420450

421451
[$genericTypes[], $variances[]] = $this->parseGenericTypeArgument($tokens);
422-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
452+
$tokens->skipNewLineTokens();
423453
}
424454

425455
$type = new Ast\Type\GenericTypeNode($baseType, $genericTypes, $variances);
@@ -510,19 +540,19 @@ private function parseCallable(TokenIterator $tokens, Ast\Type\IdentifierTypeNod
510540
: [];
511541

512542
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES);
513-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
543+
$tokens->skipNewLineTokens();
514544

515545
$parameters = [];
516546
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PARENTHESES)) {
517547
$parameters[] = $this->parseCallableParameter($tokens);
518-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
548+
$tokens->skipNewLineTokens();
519549
while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) {
520-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
550+
$tokens->skipNewLineTokens();
521551
if ($tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PARENTHESES)) {
522552
break;
523553
}
524554
$parameters[] = $this->parseCallableParameter($tokens);
525-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
555+
$tokens->skipNewLineTokens();
526556
}
527557
}
528558

@@ -550,7 +580,7 @@ private function parseCallableTemplates(TokenIterator $tokens): array
550580

551581
$isFirst = true;
552582
while ($isFirst || $tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) {
553-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
583+
$tokens->skipNewLineTokens();
554584

555585
// trailing comma case
556586
if (!$isFirst && $tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET)) {
@@ -559,7 +589,7 @@ private function parseCallableTemplates(TokenIterator $tokens): array
559589
$isFirst = false;
560590

561591
$templates[] = $this->parseCallableTemplateArgument($tokens);
562-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
592+
$tokens->skipNewLineTokens();
563593
}
564594

565595
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET);
@@ -830,7 +860,7 @@ private function parseArrayShape(TokenIterator $tokens, Ast\Type\TypeNode $type,
830860
$unsealedType = null;
831861

832862
do {
833-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
863+
$tokens->skipNewLineTokens();
834864

835865
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET)) {
836866
return Ast\Type\ArrayShapeNode::createSealed($items, $kind);
@@ -839,14 +869,14 @@ private function parseArrayShape(TokenIterator $tokens, Ast\Type\TypeNode $type,
839869
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_VARIADIC)) {
840870
$sealed = false;
841871

842-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
872+
$tokens->skipNewLineTokens();
843873
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)) {
844874
if ($kind === Ast\Type\ArrayShapeNode::KIND_ARRAY) {
845875
$unsealedType = $this->parseArrayShapeUnsealedType($tokens);
846876
} else {
847877
$unsealedType = $this->parseListShapeUnsealedType($tokens);
848878
}
849-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
879+
$tokens->skipNewLineTokens();
850880
}
851881

852882
$tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA);
@@ -855,10 +885,10 @@ private function parseArrayShape(TokenIterator $tokens, Ast\Type\TypeNode $type,
855885

856886
$items[] = $this->parseArrayShapeItem($tokens);
857887

858-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
888+
$tokens->skipNewLineTokens();
859889
} while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA));
860890

861-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
891+
$tokens->skipNewLineTokens();
862892
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET);
863893

864894
if ($sealed) {
@@ -945,18 +975,18 @@ private function parseArrayShapeUnsealedType(TokenIterator $tokens): Ast\Type\Ar
945975
$startIndex = $tokens->currentTokenIndex();
946976

947977
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET);
948-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
978+
$tokens->skipNewLineTokens();
949979

950980
$valueType = $this->parse($tokens);
951-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
981+
$tokens->skipNewLineTokens();
952982

953983
$keyType = null;
954984
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) {
955-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
985+
$tokens->skipNewLineTokens();
956986

957987
$keyType = $valueType;
958988
$valueType = $this->parse($tokens);
959-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
989+
$tokens->skipNewLineTokens();
960990
}
961991

962992
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET);
@@ -978,10 +1008,10 @@ private function parseListShapeUnsealedType(TokenIterator $tokens): Ast\Type\Arr
9781008
$startIndex = $tokens->currentTokenIndex();
9791009

9801010
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET);
981-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
1011+
$tokens->skipNewLineTokens();
9821012

9831013
$valueType = $this->parse($tokens);
984-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
1014+
$tokens->skipNewLineTokens();
9851015

9861016
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET);
9871017

@@ -1003,18 +1033,18 @@ private function parseObjectShape(TokenIterator $tokens): Ast\Type\ObjectShapeNo
10031033
$items = [];
10041034

10051035
do {
1006-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
1036+
$tokens->skipNewLineTokens();
10071037

10081038
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET)) {
10091039
return new Ast\Type\ObjectShapeNode($items);
10101040
}
10111041

10121042
$items[] = $this->parseObjectShapeItem($tokens);
10131043

1014-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
1044+
$tokens->skipNewLineTokens();
10151045
} while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA));
10161046

1017-
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
1047+
$tokens->skipNewLineTokens();
10181048
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET);
10191049

10201050
return new Ast\Type\ObjectShapeNode($items);

0 commit comments

Comments
 (0)