From a7e969872e2e34d796ddc49014032adc32f6ff14 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 2 May 2023 10:46:29 +0200 Subject: [PATCH] CallableTypeNode - support ConstTypeNode in return type --- src/Parser/TypeParser.php | 144 +++++++++++++++++++----- tests/PHPStan/Parser/TypeParserTest.php | 49 ++++++++ 2 files changed, 163 insertions(+), 30 deletions(-) diff --git a/src/Parser/TypeParser.php b/src/Parser/TypeParser.php index 31ef6c19..9e8956f5 100644 --- a/src/Parser/TypeParser.php +++ b/src/Parser/TypeParser.php @@ -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; + } } diff --git a/tests/PHPStan/Parser/TypeParserTest.php b/tests/PHPStan/Parser/TypeParserTest.php index e8b36a5e..0fcb0e3b 100644 --- a/tests/PHPStan/Parser/TypeParserTest.php +++ b/tests/PHPStan/Parser/TypeParserTest.php @@ -770,6 +770,22 @@ public function provideParseData(): array ) ), ], + [ + 'callable(): Foo[]', + new CallableTypeNode( + new IdentifierTypeNode('callable'), + [], + new ArrayTypeNode(new GenericTypeNode( + new IdentifierTypeNode('Foo'), + [ + new IdentifierTypeNode('Bar'), + ], + [ + GenericTypeNode::VARIANCE_INVARIANT, + ] + )) + ), + ], [ 'callable(): Foo|Bar', new UnionTypeNode([ @@ -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', '*'))), + ], ]; }