Skip to content

Commit

Permalink
Support multiline parenthesized types
Browse files Browse the repository at this point in the history
This includes union, intersection, and conditional types
  • Loading branch information
rvanvelzen authored and ondrejmirtes committed Mar 30, 2022
1 parent 0533306 commit 4cb3021
Show file tree
Hide file tree
Showing 3 changed files with 237 additions and 9 deletions.
59 changes: 50 additions & 9 deletions src/Parser/TypeParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,17 @@ private function subParse(TokenIterator $tokens): Ast\Type\TypeNode
} else {
$type = $this->parseAtomic($tokens);

if ($tokens->isCurrentTokenType(Lexer::TOKEN_UNION)) {
$type = $this->parseUnion($tokens, $type);

} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_INTERSECTION)) {
$type = $this->parseIntersection($tokens, $type);
} elseif ($tokens->isCurrentTokenValue('is')) {
if ($tokens->isCurrentTokenValue('is')) {
$type = $this->parseConditional($tokens, $type);
} else {
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);

if ($tokens->isCurrentTokenType(Lexer::TOKEN_UNION)) {
$type = $this->subParseUnion($tokens, $type);

} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_INTERSECTION)) {
$type = $this->subParseIntersection($tokens, $type);
}
}
}

Expand All @@ -69,7 +73,10 @@ private function subParse(TokenIterator $tokens): Ast\Type\TypeNode
private function parseAtomic(TokenIterator $tokens): Ast\Type\TypeNode
{
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) {
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
$type = $this->subParse($tokens);
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);

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

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


/** @phpstan-impure */
private function subParseUnion(TokenIterator $tokens, Ast\Type\TypeNode $type): Ast\Type\TypeNode
{
$types = [$type];

while ($tokens->tryConsumeTokenType(Lexer::TOKEN_UNION)) {
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
$types[] = $this->parseAtomic($tokens);
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
}

return new Ast\Type\UnionTypeNode($types);
}


/** @phpstan-impure */
private function parseIntersection(TokenIterator $tokens, Ast\Type\TypeNode $type): Ast\Type\TypeNode
{
Expand All @@ -182,6 +204,21 @@ private function parseIntersection(TokenIterator $tokens, Ast\Type\TypeNode $typ
}


/** @phpstan-impure */
private function subParseIntersection(TokenIterator $tokens, Ast\Type\TypeNode $type): Ast\Type\TypeNode
{
$types = [$type];

while ($tokens->tryConsumeTokenType(Lexer::TOKEN_INTERSECTION)) {
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
$types[] = $this->parseAtomic($tokens);
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
}

return new Ast\Type\IntersectionTypeNode($types);
}


/** @phpstan-impure */
private function parseConditional(TokenIterator $tokens, Ast\Type\TypeNode $subjectType): Ast\Type\TypeNode
{
Expand All @@ -193,15 +230,19 @@ private function parseConditional(TokenIterator $tokens, Ast\Type\TypeNode $subj
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
}

$targetType = $this->parseAtomic($tokens);
$targetType = $this->parse($tokens);

$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
$tokens->consumeTokenType(Lexer::TOKEN_NULLABLE);
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);

$ifType = $this->parseAtomic($tokens);
$ifType = $this->parse($tokens);

$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
$tokens->consumeTokenType(Lexer::TOKEN_COLON);
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);

$elseType = $this->parseAtomic($tokens);
$elseType = $this->parse($tokens);

return new Ast\Type\ConditionalTypeNode($subjectType, $targetType, $ifType, $elseType, $negated);
}
Expand Down
113 changes: 113 additions & 0 deletions tests/PHPStan/Parser/PhpDocParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2818,6 +2818,119 @@ public function provideMultiLinePhpDocData(): array
),
]),
],
[
'OK with multiline conditional return type',
'/**
* @template TRandKey as array-key
* @template TRandVal
* @template TRandList as array<TRandKey, TRandVal>|XIterator<TRandKey, TRandVal>|Traversable<TRandKey, TRandVal>
*
* @param TRandList $list
*
* @return (
* TRandList is array ? array<TRandKey, TRandVal> : (
* TRandList is XIterator ? XIterator<TRandKey, TRandVal> :
* IteratorIterator<TRandKey, TRandVal>|LimitIterator<TRandKey, TRandVal>
* ))
*/',
new PhpDocNode([
new PhpDocTagNode(
'@template',
new TemplateTagValueNode('TRandKey', new IdentifierTypeNode('array-key'), '')
),
new PhpDocTagNode(
'@template',
new TemplateTagValueNode('TRandVal', null, '')
),
new PhpDocTagNode(
'@template',
new TemplateTagValueNode(
'TRandList',
new UnionTypeNode([
new GenericTypeNode(
new IdentifierTypeNode('array'),
[
new IdentifierTypeNode('TRandKey'),
new IdentifierTypeNode('TRandVal'),
]
),
new GenericTypeNode(
new IdentifierTypeNode('XIterator'),
[
new IdentifierTypeNode('TRandKey'),
new IdentifierTypeNode('TRandVal'),
]
),
new GenericTypeNode(
new IdentifierTypeNode('Traversable'),
[
new IdentifierTypeNode('TRandKey'),
new IdentifierTypeNode('TRandVal'),
]
),
]),
''
)
),
new PhpDocTextNode(''),
new PhpDocTagNode(
'@param',
new ParamTagValueNode(
new IdentifierTypeNode('TRandList'),
false,
'$list',
''
)
),
new PhpDocTextNode(''),
new PhpDocTagNode(
'@return',
new ReturnTagValueNode(
new ConditionalTypeNode(
new IdentifierTypeNode('TRandList'),
new IdentifierTypeNode('array'),
new GenericTypeNode(
new IdentifierTypeNode('array'),
[
new IdentifierTypeNode('TRandKey'),
new IdentifierTypeNode('TRandVal'),
]
),
new ConditionalTypeNode(
new IdentifierTypeNode('TRandList'),
new IdentifierTypeNode('XIterator'),
new GenericTypeNode(
new IdentifierTypeNode('XIterator'),
[
new IdentifierTypeNode('TRandKey'),
new IdentifierTypeNode('TRandVal'),
]
),
new UnionTypeNode([
new GenericTypeNode(
new IdentifierTypeNode('IteratorIterator'),
[
new IdentifierTypeNode('TRandKey'),
new IdentifierTypeNode('TRandVal'),
]
),
new GenericTypeNode(
new IdentifierTypeNode('LimitIterator'),
[
new IdentifierTypeNode('TRandKey'),
new IdentifierTypeNode('TRandVal'),
]
),
]),
false
),
false
),
''
)
),
]),
],
];
}

Expand Down
74 changes: 74 additions & 0 deletions tests/PHPStan/Parser/TypeParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,17 @@ public function provideParseData(): array
new IdentifierTypeNode('int'),
]),
],
[
'(' . PHP_EOL .
' string' . PHP_EOL .
' &' . PHP_EOL .
' int' . PHP_EOL .
')',
new IntersectionTypeNode([
new IdentifierTypeNode('string'),
new IdentifierTypeNode('int'),
]),
],
[
'string & int & float',
new IntersectionTypeNode([
Expand Down Expand Up @@ -1136,6 +1147,69 @@ public function provideParseData(): array
false
),
],
[
'(Foo is Bar|Baz ? never : int|string)',
new ConditionalTypeNode(
new IdentifierTypeNode('Foo'),
new UnionTypeNode([
new IdentifierTypeNode('Bar'),
new IdentifierTypeNode('Baz'),
]),
new IdentifierTypeNode('never'),
new UnionTypeNode([
new IdentifierTypeNode('int'),
new IdentifierTypeNode('string'),
]),
false
),
],
[
'(' . PHP_EOL .
' TRandList is array ? array<TRandKey, TRandVal> : (' . PHP_EOL .
' TRandList is XIterator ? XIterator<TRandKey, TRandVal> :' . PHP_EOL .
' IteratorIterator<TRandKey, TRandVal>|LimitIterator<TRandKey, TRandVal>' . PHP_EOL .
'))',
new ConditionalTypeNode(
new IdentifierTypeNode('TRandList'),
new IdentifierTypeNode('array'),
new GenericTypeNode(
new IdentifierTypeNode('array'),
[
new IdentifierTypeNode('TRandKey'),
new IdentifierTypeNode('TRandVal'),
]
),
new ConditionalTypeNode(
new IdentifierTypeNode('TRandList'),
new IdentifierTypeNode('XIterator'),
new GenericTypeNode(
new IdentifierTypeNode('XIterator'),
[
new IdentifierTypeNode('TRandKey'),
new IdentifierTypeNode('TRandVal'),
]
),
new UnionTypeNode([
new GenericTypeNode(
new IdentifierTypeNode('IteratorIterator'),
[
new IdentifierTypeNode('TRandKey'),
new IdentifierTypeNode('TRandVal'),
]
),
new GenericTypeNode(
new IdentifierTypeNode('LimitIterator'),
[
new IdentifierTypeNode('TRandKey'),
new IdentifierTypeNode('TRandVal'),
]
),
]),
false
),
false
),
],
];
}

Expand Down

0 comments on commit 4cb3021

Please sign in to comment.