Skip to content

Commit

Permalink
CallableTypeNode - support ConstTypeNode in return type
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed May 2, 2023
1 parent 421d3f3 commit a7e9698
Show file tree
Hide file tree
Showing 2 changed files with 163 additions and 30 deletions.
144 changes: 114 additions & 30 deletions src/Parser/TypeParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -522,51 +522,135 @@ private function parseCallableReturnType(TokenIterator $tokens): Ast\Type\TypeNo
$startIndex = $tokens->currentTokenIndex();
if ($tokens->isCurrentTokenType(Lexer::TOKEN_NULLABLE)) {
$type = $this->parseNullable($tokens);
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
$type = $this->tryParseArrayOrOffsetAccess($tokens, $this->enrichWithAttributes(
$tokens,
$type,
$startLine,
$startIndex
));
}

return $type;

} elseif ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) {
$type = $this->parse($tokens);
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES);
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
$type = $this->tryParseArrayOrOffsetAccess($tokens, $type);
}

return $type;
} elseif ($tokens->tryConsumeTokenType(Lexer::TOKEN_THIS_VARIABLE)) {
$type = new Ast\Type\ThisTypeNode();
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
$type = $this->tryParseArrayOrOffsetAccess($tokens, $this->enrichWithAttributes(
$tokens,
$type,
$startLine,
$startIndex
));
}

return $type;
} else {
$type = new Ast\Type\IdentifierTypeNode($tokens->currentTokenValue());
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
$currentTokenValue = $tokens->currentTokenValue();
$tokens->pushSavePoint(); // because of ConstFetchNode
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_IDENTIFIER)) {
$type = new Ast\Type\IdentifierTypeNode($currentTokenValue);

if (!$tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_COLON)) {
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)) {
$type = $this->parseGeneric(
$tokens,
$this->enrichWithAttributes(
$tokens,
$type,
$startLine,
$startIndex
)
);
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
$type = $this->tryParseArrayOrOffsetAccess($tokens, $this->enrichWithAttributes(
$tokens,
$type,
$startLine,
$startIndex
));
}

} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
$type = $this->tryParseArrayOrOffsetAccess($tokens, $this->enrichWithAttributes(
$tokens,
$type,
$startLine,
$startIndex
));

} elseif (in_array($type->name, ['array', 'list', 'object'], true) && $tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET) && !$tokens->isPrecededByHorizontalWhitespace()) {
if ($type->name === 'object') {
$type = $this->parseObjectShape($tokens);
} else {
$type = $this->parseArrayShape($tokens, $this->enrichWithAttributes(
$tokens,
$type,
$startLine,
$startIndex
), $type->name);
}

if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
$type = $this->tryParseArrayOrOffsetAccess($tokens, $this->enrichWithAttributes(
$tokens,
$type,
$startLine,
$startIndex
));
}
}

if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)) {
$type = $this->parseGeneric(
$tokens,
$this->enrichWithAttributes(
$tokens,
$type,
$startLine,
$startIndex
)
);

} elseif (in_array($type->name, ['array', 'list', 'object'], true) && $tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET) && !$tokens->isPrecededByHorizontalWhitespace()) {
if ($type->name === 'object') {
$type = $this->parseObjectShape($tokens);
return $type;
} else {
$type = $this->parseArrayShape($tokens, $this->enrichWithAttributes(
$tokens,
$type,
$startLine,
$startIndex
), $type->name);
$tokens->rollback(); // because of ConstFetchNode
}
} else {
$tokens->dropSavePoint(); // because of ConstFetchNode
}
}

if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
$type = $this->tryParseArrayOrOffsetAccess($tokens, $this->enrichWithAttributes(
$tokens,
$type,
$startLine,
$startIndex
));
$exception = new ParserException(
$tokens->currentTokenValue(),
$tokens->currentTokenType(),
$tokens->currentTokenOffset(),
Lexer::TOKEN_IDENTIFIER,
null,
$tokens->currentTokenLine()
);

if ($this->constExprParser === null) {
throw $exception;
}

return $type;
try {
$constExpr = $this->constExprParser->parse($tokens, true);
if ($constExpr instanceof Ast\ConstExpr\ConstExprArrayNode) {
throw $exception;
}

$type = new Ast\Type\ConstTypeNode($constExpr);
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
$type = $this->tryParseArrayOrOffsetAccess($tokens, $this->enrichWithAttributes(
$tokens,
$type,
$startLine,
$startIndex
));
}

return $type;
} catch (LogicException $e) {
throw $exception;
}
}


Expand Down
49 changes: 49 additions & 0 deletions tests/PHPStan/Parser/TypeParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,22 @@ public function provideParseData(): array
)
),
],
[
'callable(): Foo<Bar>[]',
new CallableTypeNode(
new IdentifierTypeNode('callable'),
[],
new ArrayTypeNode(new GenericTypeNode(
new IdentifierTypeNode('Foo'),
[
new IdentifierTypeNode('Bar'),
],
[
GenericTypeNode::VARIANCE_INVARIANT,
]
))
),
],
[
'callable(): Foo|Bar',
new UnionTypeNode([
Expand Down Expand Up @@ -1965,6 +1981,39 @@ public function provideParseData(): array
'callable(): $this[]',
new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ArrayTypeNode(new ThisTypeNode())),
],
[
'2.5|3',
new UnionTypeNode([
new ConstTypeNode(new ConstExprFloatNode('2.5')),
new ConstTypeNode(new ConstExprIntegerNode('3')),
]),
],
[
'callable(): 3.5',
new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ConstTypeNode(new ConstExprFloatNode('3.5'))),
],
[
'callable(): 3.5[]',
new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ArrayTypeNode(
new ConstTypeNode(new ConstExprFloatNode('3.5'))
)),
],
[
'callable(): Foo',
new CallableTypeNode(new IdentifierTypeNode('callable'), [], new IdentifierTypeNode('Foo')),
],
[
'callable(): (Foo)[]',
new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ArrayTypeNode(new IdentifierTypeNode('Foo'))),
],
[
'callable(): Foo::BAR',
new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ConstTypeNode(new ConstFetchNode('Foo', 'BAR'))),
],
[
'callable(): Foo::*',
new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ConstTypeNode(new ConstFetchNode('Foo', '*'))),
],
];
}

Expand Down

0 comments on commit a7e9698

Please sign in to comment.