Skip to content

Commit 4cb3021

Browse files
rvanvelzenondrejmirtes
authored andcommitted
Support multiline parenthesized types
This includes union, intersection, and conditional types
1 parent 0533306 commit 4cb3021

File tree

3 files changed

+237
-9
lines changed

3 files changed

+237
-9
lines changed

src/Parser/TypeParser.php

+50-9
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,17 @@ private function subParse(TokenIterator $tokens): Ast\Type\TypeNode
5151
} else {
5252
$type = $this->parseAtomic($tokens);
5353

54-
if ($tokens->isCurrentTokenType(Lexer::TOKEN_UNION)) {
55-
$type = $this->parseUnion($tokens, $type);
56-
57-
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_INTERSECTION)) {
58-
$type = $this->parseIntersection($tokens, $type);
59-
} elseif ($tokens->isCurrentTokenValue('is')) {
54+
if ($tokens->isCurrentTokenValue('is')) {
6055
$type = $this->parseConditional($tokens, $type);
56+
} else {
57+
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
58+
59+
if ($tokens->isCurrentTokenType(Lexer::TOKEN_UNION)) {
60+
$type = $this->subParseUnion($tokens, $type);
61+
62+
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_INTERSECTION)) {
63+
$type = $this->subParseIntersection($tokens, $type);
64+
}
6165
}
6266
}
6367

@@ -69,7 +73,10 @@ private function subParse(TokenIterator $tokens): Ast\Type\TypeNode
6973
private function parseAtomic(TokenIterator $tokens): Ast\Type\TypeNode
7074
{
7175
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) {
76+
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
7277
$type = $this->subParse($tokens);
78+
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
79+
7380
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES);
7481

7582
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
@@ -169,6 +176,21 @@ private function parseUnion(TokenIterator $tokens, Ast\Type\TypeNode $type): Ast
169176
}
170177

171178

179+
/** @phpstan-impure */
180+
private function subParseUnion(TokenIterator $tokens, Ast\Type\TypeNode $type): Ast\Type\TypeNode
181+
{
182+
$types = [$type];
183+
184+
while ($tokens->tryConsumeTokenType(Lexer::TOKEN_UNION)) {
185+
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
186+
$types[] = $this->parseAtomic($tokens);
187+
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
188+
}
189+
190+
return new Ast\Type\UnionTypeNode($types);
191+
}
192+
193+
172194
/** @phpstan-impure */
173195
private function parseIntersection(TokenIterator $tokens, Ast\Type\TypeNode $type): Ast\Type\TypeNode
174196
{
@@ -182,6 +204,21 @@ private function parseIntersection(TokenIterator $tokens, Ast\Type\TypeNode $typ
182204
}
183205

184206

207+
/** @phpstan-impure */
208+
private function subParseIntersection(TokenIterator $tokens, Ast\Type\TypeNode $type): Ast\Type\TypeNode
209+
{
210+
$types = [$type];
211+
212+
while ($tokens->tryConsumeTokenType(Lexer::TOKEN_INTERSECTION)) {
213+
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
214+
$types[] = $this->parseAtomic($tokens);
215+
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
216+
}
217+
218+
return new Ast\Type\IntersectionTypeNode($types);
219+
}
220+
221+
185222
/** @phpstan-impure */
186223
private function parseConditional(TokenIterator $tokens, Ast\Type\TypeNode $subjectType): Ast\Type\TypeNode
187224
{
@@ -193,15 +230,19 @@ private function parseConditional(TokenIterator $tokens, Ast\Type\TypeNode $subj
193230
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
194231
}
195232

196-
$targetType = $this->parseAtomic($tokens);
233+
$targetType = $this->parse($tokens);
197234

235+
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
198236
$tokens->consumeTokenType(Lexer::TOKEN_NULLABLE);
237+
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
199238

200-
$ifType = $this->parseAtomic($tokens);
239+
$ifType = $this->parse($tokens);
201240

241+
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
202242
$tokens->consumeTokenType(Lexer::TOKEN_COLON);
243+
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
203244

204-
$elseType = $this->parseAtomic($tokens);
245+
$elseType = $this->parse($tokens);
205246

206247
return new Ast\Type\ConditionalTypeNode($subjectType, $targetType, $ifType, $elseType, $negated);
207248
}

tests/PHPStan/Parser/PhpDocParserTest.php

+113
Original file line numberDiff line numberDiff line change
@@ -2818,6 +2818,119 @@ public function provideMultiLinePhpDocData(): array
28182818
),
28192819
]),
28202820
],
2821+
[
2822+
'OK with multiline conditional return type',
2823+
'/**
2824+
* @template TRandKey as array-key
2825+
* @template TRandVal
2826+
* @template TRandList as array<TRandKey, TRandVal>|XIterator<TRandKey, TRandVal>|Traversable<TRandKey, TRandVal>
2827+
*
2828+
* @param TRandList $list
2829+
*
2830+
* @return (
2831+
* TRandList is array ? array<TRandKey, TRandVal> : (
2832+
* TRandList is XIterator ? XIterator<TRandKey, TRandVal> :
2833+
* IteratorIterator<TRandKey, TRandVal>|LimitIterator<TRandKey, TRandVal>
2834+
* ))
2835+
*/',
2836+
new PhpDocNode([
2837+
new PhpDocTagNode(
2838+
'@template',
2839+
new TemplateTagValueNode('TRandKey', new IdentifierTypeNode('array-key'), '')
2840+
),
2841+
new PhpDocTagNode(
2842+
'@template',
2843+
new TemplateTagValueNode('TRandVal', null, '')
2844+
),
2845+
new PhpDocTagNode(
2846+
'@template',
2847+
new TemplateTagValueNode(
2848+
'TRandList',
2849+
new UnionTypeNode([
2850+
new GenericTypeNode(
2851+
new IdentifierTypeNode('array'),
2852+
[
2853+
new IdentifierTypeNode('TRandKey'),
2854+
new IdentifierTypeNode('TRandVal'),
2855+
]
2856+
),
2857+
new GenericTypeNode(
2858+
new IdentifierTypeNode('XIterator'),
2859+
[
2860+
new IdentifierTypeNode('TRandKey'),
2861+
new IdentifierTypeNode('TRandVal'),
2862+
]
2863+
),
2864+
new GenericTypeNode(
2865+
new IdentifierTypeNode('Traversable'),
2866+
[
2867+
new IdentifierTypeNode('TRandKey'),
2868+
new IdentifierTypeNode('TRandVal'),
2869+
]
2870+
),
2871+
]),
2872+
''
2873+
)
2874+
),
2875+
new PhpDocTextNode(''),
2876+
new PhpDocTagNode(
2877+
'@param',
2878+
new ParamTagValueNode(
2879+
new IdentifierTypeNode('TRandList'),
2880+
false,
2881+
'$list',
2882+
''
2883+
)
2884+
),
2885+
new PhpDocTextNode(''),
2886+
new PhpDocTagNode(
2887+
'@return',
2888+
new ReturnTagValueNode(
2889+
new ConditionalTypeNode(
2890+
new IdentifierTypeNode('TRandList'),
2891+
new IdentifierTypeNode('array'),
2892+
new GenericTypeNode(
2893+
new IdentifierTypeNode('array'),
2894+
[
2895+
new IdentifierTypeNode('TRandKey'),
2896+
new IdentifierTypeNode('TRandVal'),
2897+
]
2898+
),
2899+
new ConditionalTypeNode(
2900+
new IdentifierTypeNode('TRandList'),
2901+
new IdentifierTypeNode('XIterator'),
2902+
new GenericTypeNode(
2903+
new IdentifierTypeNode('XIterator'),
2904+
[
2905+
new IdentifierTypeNode('TRandKey'),
2906+
new IdentifierTypeNode('TRandVal'),
2907+
]
2908+
),
2909+
new UnionTypeNode([
2910+
new GenericTypeNode(
2911+
new IdentifierTypeNode('IteratorIterator'),
2912+
[
2913+
new IdentifierTypeNode('TRandKey'),
2914+
new IdentifierTypeNode('TRandVal'),
2915+
]
2916+
),
2917+
new GenericTypeNode(
2918+
new IdentifierTypeNode('LimitIterator'),
2919+
[
2920+
new IdentifierTypeNode('TRandKey'),
2921+
new IdentifierTypeNode('TRandVal'),
2922+
]
2923+
),
2924+
]),
2925+
false
2926+
),
2927+
false
2928+
),
2929+
''
2930+
)
2931+
),
2932+
]),
2933+
],
28212934
];
28222935
}
28232936

tests/PHPStan/Parser/TypeParserTest.php

+74
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,17 @@ public function provideParseData(): array
149149
new IdentifierTypeNode('int'),
150150
]),
151151
],
152+
[
153+
'(' . PHP_EOL .
154+
' string' . PHP_EOL .
155+
' &' . PHP_EOL .
156+
' int' . PHP_EOL .
157+
')',
158+
new IntersectionTypeNode([
159+
new IdentifierTypeNode('string'),
160+
new IdentifierTypeNode('int'),
161+
]),
162+
],
152163
[
153164
'string & int & float',
154165
new IntersectionTypeNode([
@@ -1136,6 +1147,69 @@ public function provideParseData(): array
11361147
false
11371148
),
11381149
],
1150+
[
1151+
'(Foo is Bar|Baz ? never : int|string)',
1152+
new ConditionalTypeNode(
1153+
new IdentifierTypeNode('Foo'),
1154+
new UnionTypeNode([
1155+
new IdentifierTypeNode('Bar'),
1156+
new IdentifierTypeNode('Baz'),
1157+
]),
1158+
new IdentifierTypeNode('never'),
1159+
new UnionTypeNode([
1160+
new IdentifierTypeNode('int'),
1161+
new IdentifierTypeNode('string'),
1162+
]),
1163+
false
1164+
),
1165+
],
1166+
[
1167+
'(' . PHP_EOL .
1168+
' TRandList is array ? array<TRandKey, TRandVal> : (' . PHP_EOL .
1169+
' TRandList is XIterator ? XIterator<TRandKey, TRandVal> :' . PHP_EOL .
1170+
' IteratorIterator<TRandKey, TRandVal>|LimitIterator<TRandKey, TRandVal>' . PHP_EOL .
1171+
'))',
1172+
new ConditionalTypeNode(
1173+
new IdentifierTypeNode('TRandList'),
1174+
new IdentifierTypeNode('array'),
1175+
new GenericTypeNode(
1176+
new IdentifierTypeNode('array'),
1177+
[
1178+
new IdentifierTypeNode('TRandKey'),
1179+
new IdentifierTypeNode('TRandVal'),
1180+
]
1181+
),
1182+
new ConditionalTypeNode(
1183+
new IdentifierTypeNode('TRandList'),
1184+
new IdentifierTypeNode('XIterator'),
1185+
new GenericTypeNode(
1186+
new IdentifierTypeNode('XIterator'),
1187+
[
1188+
new IdentifierTypeNode('TRandKey'),
1189+
new IdentifierTypeNode('TRandVal'),
1190+
]
1191+
),
1192+
new UnionTypeNode([
1193+
new GenericTypeNode(
1194+
new IdentifierTypeNode('IteratorIterator'),
1195+
[
1196+
new IdentifierTypeNode('TRandKey'),
1197+
new IdentifierTypeNode('TRandVal'),
1198+
]
1199+
),
1200+
new GenericTypeNode(
1201+
new IdentifierTypeNode('LimitIterator'),
1202+
[
1203+
new IdentifierTypeNode('TRandKey'),
1204+
new IdentifierTypeNode('TRandVal'),
1205+
]
1206+
),
1207+
]),
1208+
false
1209+
),
1210+
false
1211+
),
1212+
],
11391213
];
11401214
}
11411215

0 commit comments

Comments
 (0)