Skip to content

Commit 5f13698

Browse files
rvanvelzenondrejmirtes
authored andcommitted
Allow unparenthesized conditional type in conditional else branch
1 parent 4461548 commit 5f13698

File tree

4 files changed

+227
-3
lines changed

4 files changed

+227
-3
lines changed

doc/grammars/type.abnf

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Intersection
1717
= 1*(TokenIntersection Atomic)
1818

1919
Conditional
20-
= 1*ByteHorizontalWs TokenIs [TokenNot] Atomic TokenNullable Atomic TokenColon Atomic
20+
= 1*ByteHorizontalWs TokenIs [TokenNot] Atomic TokenNullable Type TokenColon ParenthesizedType
2121

2222
Nullable
2323
= TokenNullable Atomic

src/Parser/TypeParser.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ private function parseConditional(TokenIterator $tokens, Ast\Type\TypeNode $subj
242242
$tokens->consumeTokenType(Lexer::TOKEN_COLON);
243243
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
244244

245-
$elseType = $this->parse($tokens);
245+
$elseType = $this->subParse($tokens);
246246

247247
return new Ast\Type\ConditionalTypeNode($subjectType, $targetType, $ifType, $elseType, $negated);
248248
}
@@ -271,7 +271,7 @@ private function parseConditionalForParameter(TokenIterator $tokens, string $par
271271
$tokens->consumeTokenType(Lexer::TOKEN_COLON);
272272
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
273273

274-
$elseType = $this->parse($tokens);
274+
$elseType = $this->subParse($tokens);
275275

276276
return new Ast\Type\ConditionalTypeForParameterNode($parameterName, $targetType, $ifType, $elseType, $negated);
277277
}

tests/PHPStan/Parser/PhpDocParserTest.php

+174
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
use PHPStan\PhpDocParser\Ast\PhpDoc\TypelessParamTagValueNode;
3535
use PHPStan\PhpDocParser\Ast\PhpDoc\UsesTagValueNode;
3636
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
37+
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeItemNode;
38+
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode;
3739
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
3840
use PHPStan\PhpDocParser\Ast\Type\CallableTypeNode;
3941
use PHPStan\PhpDocParser\Ast\Type\CallableTypeParameterNode;
@@ -4381,6 +4383,178 @@ public function provideRealWorldExampleData(): Iterator
43814383
),
43824384
]),
43834385
];
4386+
4387+
yield [
4388+
'complex stub from Psalm',
4389+
'/**' . PHP_EOL .
4390+
' * @psalm-pure' . PHP_EOL .
4391+
' * @template TFlags as int-mask<0, 256, 512>' . PHP_EOL .
4392+
' *' . PHP_EOL .
4393+
' * @param string $pattern' . PHP_EOL .
4394+
' * @param string $subject' . PHP_EOL .
4395+
' * @param mixed $matches' . PHP_EOL .
4396+
' * @param TFlags $flags' . PHP_EOL .
4397+
" * @param-out (TFlags is 256 ? array<array-key, array{string, 0|positive-int}|array{'', -1}> :" . PHP_EOL .
4398+
' * TFlags is 512 ? array<array-key, string|null> :' . PHP_EOL .
4399+
' * TFlags is 768 ? array<array-key, array{string, 0|positive-int}|array{null, -1}> :' . PHP_EOL .
4400+
' * array<array-key, string>' . PHP_EOL .
4401+
' * ) $matches' . PHP_EOL .
4402+
' * @return 1|0|false' . PHP_EOL .
4403+
' * @psalm-ignore-falsable-return' . PHP_EOL .
4404+
' */',
4405+
new PhpDocNode([
4406+
new PhpDocTagNode('@psalm-pure', new GenericTagValueNode('')),
4407+
new PhpDocTagNode(
4408+
'@template',
4409+
new TemplateTagValueNode(
4410+
'TFlags',
4411+
new GenericTypeNode(
4412+
new IdentifierTypeNode('int-mask'),
4413+
[
4414+
new ConstTypeNode(new ConstExprIntegerNode('0')),
4415+
new ConstTypeNode(new ConstExprIntegerNode('256')),
4416+
new ConstTypeNode(new ConstExprIntegerNode('512')),
4417+
]
4418+
),
4419+
''
4420+
)
4421+
),
4422+
new PhpDocTextNode(''),
4423+
new PhpDocTagNode(
4424+
'@param',
4425+
new ParamTagValueNode(
4426+
new IdentifierTypeNode('string'),
4427+
false,
4428+
'$pattern',
4429+
''
4430+
)
4431+
),
4432+
new PhpDocTagNode(
4433+
'@param',
4434+
new ParamTagValueNode(
4435+
new IdentifierTypeNode('string'),
4436+
false,
4437+
'$subject',
4438+
''
4439+
)
4440+
),
4441+
new PhpDocTagNode(
4442+
'@param',
4443+
new ParamTagValueNode(
4444+
new IdentifierTypeNode('mixed'),
4445+
false,
4446+
'$matches',
4447+
''
4448+
)
4449+
),
4450+
new PhpDocTagNode(
4451+
'@param',
4452+
new ParamTagValueNode(
4453+
new IdentifierTypeNode('TFlags'),
4454+
false,
4455+
'$flags',
4456+
''
4457+
)
4458+
),
4459+
new PhpDocTagNode(
4460+
'@param-out',
4461+
new ParamOutTagValueNode(
4462+
new ConditionalTypeNode(
4463+
new IdentifierTypeNode('TFlags'),
4464+
new ConstTypeNode(new ConstExprIntegerNode('256')),
4465+
new GenericTypeNode(
4466+
new IdentifierTypeNode('array'),
4467+
[
4468+
new IdentifierTypeNode('array-key'),
4469+
new UnionTypeNode([
4470+
new ArrayShapeNode([
4471+
new ArrayShapeItemNode(null, false, new IdentifierTypeNode('string')),
4472+
new ArrayShapeItemNode(
4473+
null,
4474+
false,
4475+
new UnionTypeNode([
4476+
new ConstTypeNode(new ConstExprIntegerNode('0')),
4477+
new IdentifierTypeNode('positive-int'),
4478+
])
4479+
),
4480+
]),
4481+
new ArrayShapeNode([
4482+
new ArrayShapeItemNode(null, false, new ConstTypeNode(new ConstExprStringNode(''))),
4483+
new ArrayShapeItemNode(null, false, new ConstTypeNode(new ConstExprIntegerNode('-1'))),
4484+
]),
4485+
]),
4486+
]
4487+
),
4488+
new ConditionalTypeNode(
4489+
new IdentifierTypeNode('TFlags'),
4490+
new ConstTypeNode(new ConstExprIntegerNode('512')),
4491+
new GenericTypeNode(
4492+
new IdentifierTypeNode('array'),
4493+
[
4494+
new IdentifierTypeNode('array-key'),
4495+
new UnionTypeNode([
4496+
new IdentifierTypeNode('string'),
4497+
new IdentifierTypeNode('null'),
4498+
]),
4499+
]
4500+
),
4501+
new ConditionalTypeNode(
4502+
new IdentifierTypeNode('TFlags'),
4503+
new ConstTypeNode(new ConstExprIntegerNode('768')),
4504+
new GenericTypeNode(
4505+
new IdentifierTypeNode('array'),
4506+
[
4507+
new IdentifierTypeNode('array-key'),
4508+
new UnionTypeNode([
4509+
new ArrayShapeNode([
4510+
new ArrayShapeItemNode(null, false, new IdentifierTypeNode('string')),
4511+
new ArrayShapeItemNode(
4512+
null,
4513+
false,
4514+
new UnionTypeNode([
4515+
new ConstTypeNode(new ConstExprIntegerNode('0')),
4516+
new IdentifierTypeNode('positive-int'),
4517+
])
4518+
),
4519+
]),
4520+
new ArrayShapeNode([
4521+
new ArrayShapeItemNode(null, false, new IdentifierTypeNode('null')),
4522+
new ArrayShapeItemNode(null, false, new ConstTypeNode(new ConstExprIntegerNode('-1'))),
4523+
]),
4524+
]),
4525+
]
4526+
),
4527+
new GenericTypeNode(
4528+
new IdentifierTypeNode('array'),
4529+
[
4530+
new IdentifierTypeNode('array-key'),
4531+
new IdentifierTypeNode('string'),
4532+
]
4533+
),
4534+
false
4535+
),
4536+
false
4537+
),
4538+
false
4539+
),
4540+
'$matches',
4541+
''
4542+
)
4543+
),
4544+
new PhpDocTagNode(
4545+
'@return',
4546+
new ReturnTagValueNode(
4547+
new UnionTypeNode([
4548+
new ConstTypeNode(new ConstExprIntegerNode('1')),
4549+
new ConstTypeNode(new ConstExprIntegerNode('0')),
4550+
new IdentifierTypeNode('false'),
4551+
]),
4552+
''
4553+
)
4554+
),
4555+
new PhpDocTagNode('@psalm-ignore-falsable-return', new GenericTagValueNode('')),
4556+
]),
4557+
];
43844558
}
43854559

43864560
public function provideDescriptionWithOrWithoutHtml(): Iterator

tests/PHPStan/Parser/TypeParserTest.php

+50
Original file line numberDiff line numberDiff line change
@@ -1266,6 +1266,56 @@ public function provideParseData(): array
12661266
)
12671267
),
12681268
],
1269+
[
1270+
'(T is Foo ? true : T is Bar ? false : null)',
1271+
new ConditionalTypeNode(
1272+
new IdentifierTypeNode('T'),
1273+
new IdentifierTypeNode('Foo'),
1274+
new IdentifierTypeNode('true'),
1275+
new ConditionalTypeNode(
1276+
new IdentifierTypeNode('T'),
1277+
new IdentifierTypeNode('Bar'),
1278+
new IdentifierTypeNode('false'),
1279+
new IdentifierTypeNode('null'),
1280+
false
1281+
),
1282+
false
1283+
),
1284+
],
1285+
[
1286+
'(T is Foo ? T is Bar ? true : false : null)',
1287+
new ParserException(
1288+
'is',
1289+
Lexer::TOKEN_IDENTIFIER,
1290+
14,
1291+
Lexer::TOKEN_COLON
1292+
),
1293+
],
1294+
[
1295+
'($foo is Foo ? true : $foo is Bar ? false : null)',
1296+
new ConditionalTypeForParameterNode(
1297+
'$foo',
1298+
new IdentifierTypeNode('Foo'),
1299+
new IdentifierTypeNode('true'),
1300+
new ConditionalTypeForParameterNode(
1301+
'$foo',
1302+
new IdentifierTypeNode('Bar'),
1303+
new IdentifierTypeNode('false'),
1304+
new IdentifierTypeNode('null'),
1305+
false
1306+
),
1307+
false
1308+
),
1309+
],
1310+
[
1311+
'($foo is Foo ? $foo is Bar ? true : false : null)',
1312+
new ParserException(
1313+
'$foo',
1314+
Lexer::TOKEN_VARIABLE,
1315+
15,
1316+
Lexer::TOKEN_IDENTIFIER
1317+
),
1318+
],
12691319
];
12701320
}
12711321

0 commit comments

Comments
 (0)