From 4c28423945798caca2521564e49ee5d63dda4ad0 Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Tue, 7 May 2024 09:31:59 +0200 Subject: [PATCH] Add basic support for new-in-initializer --- src/Ast/ConstExpr/ConstExprNewNode.php | 34 +++++++++++++ src/Parser/ConstExprParser.php | 26 ++++++++++ src/Parser/TypeParser.php | 4 +- tests/PHPStan/Parser/PhpDocParserTest.php | 61 +++++++++++++++++++++++ 4 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 src/Ast/ConstExpr/ConstExprNewNode.php diff --git a/src/Ast/ConstExpr/ConstExprNewNode.php b/src/Ast/ConstExpr/ConstExprNewNode.php new file mode 100644 index 00000000..50ac064d --- /dev/null +++ b/src/Ast/ConstExpr/ConstExprNewNode.php @@ -0,0 +1,34 @@ +class = $class; + $this->arguments = $arguments; + } + + + public function __toString(): string + { + return 'new ' . $this->class . '(' . implode(', ', $this->arguments) . ')'; + } + +} diff --git a/src/Parser/ConstExprParser.php b/src/Parser/ConstExprParser.php index f6a7306e..091ea791 100644 --- a/src/Parser/ConstExprParser.php +++ b/src/Parser/ConstExprParser.php @@ -183,6 +183,8 @@ public function parse(TokenIterator $tokens, bool $trimStrings = false): Ast\Con case 'array': $tokens->consumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES); return $this->parseArray($tokens, Lexer::TOKEN_CLOSE_PARENTHESES, $startIndex); + case 'new': + return $this->parseNew($tokens, $startIndex); } if ($tokens->tryConsumeTokenType(Lexer::TOKEN_DOUBLE_COLON)) { @@ -269,6 +271,30 @@ private function parseArray(TokenIterator $tokens, int $endToken, int $startInde } + private function parseNew(TokenIterator $tokens, int $startIndex): Ast\ConstExpr\ConstExprNewNode + { + $startLine = $tokens->currentTokenLine(); + + $class = $tokens->currentTokenValue(); + $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); + + $arguments = []; + if ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) { + do { + $arguments[] = $this->parse($tokens); + } while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA) && !$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PARENTHESES)); + $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES); + } + + return $this->enrichWithAttributes( + $tokens, + new Ast\ConstExpr\ConstExprNewNode($class, $arguments), + $startLine, + $startIndex + ); + } + + /** * This method is supposed to be called with TokenIterator after reading TOKEN_DOUBLE_QUOTED_STRING and shifting * to the next token. diff --git a/src/Parser/TypeParser.php b/src/Parser/TypeParser.php index 5669fe45..536bd1cf 100644 --- a/src/Parser/TypeParser.php +++ b/src/Parser/TypeParser.php @@ -221,7 +221,7 @@ private function parseAtomic(TokenIterator $tokens): Ast\Type\TypeNode try { $constExpr = $this->constExprParser->parse($tokens, true); - if ($constExpr instanceof Ast\ConstExpr\ConstExprArrayNode) { + if ($constExpr instanceof Ast\ConstExpr\ConstExprArrayNode || $constExpr instanceof Ast\ConstExpr\ConstExprNewNode) { throw new ParserException( $currentTokenValue, $currentTokenType, @@ -732,7 +732,7 @@ private function parseCallableReturnType(TokenIterator $tokens): Ast\Type\TypeNo try { $constExpr = $this->constExprParser->parse($tokens, true); - if ($constExpr instanceof Ast\ConstExpr\ConstExprArrayNode) { + if ($constExpr instanceof Ast\ConstExpr\ConstExprArrayNode || $constExpr instanceof Ast\ConstExpr\ConstExprNewNode) { throw new ParserException( $currentTokenValue, $currentTokenType, diff --git a/tests/PHPStan/Parser/PhpDocParserTest.php b/tests/PHPStan/Parser/PhpDocParserTest.php index 8085ec64..7c0799b5 100644 --- a/tests/PHPStan/Parser/PhpDocParserTest.php +++ b/tests/PHPStan/Parser/PhpDocParserTest.php @@ -8,6 +8,7 @@ use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprArrayItemNode; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprArrayNode; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode; +use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprNewNode; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode; use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode; use PHPStan\PhpDocParser\Ast\ConstExpr\DoctrineConstExprStringNode; @@ -2762,6 +2763,66 @@ public function provideMethodTagsData(): Iterator ), ]), ]; + + yield [ + 'OK static with parameter using new in initializer', + '/** @method static void myFunction(DateInterval $date = new DateInterval("P1Y")) */', + new PhpDocNode([ + new PhpDocTagNode( + '@method', + new MethodTagValueNode( + true, + new IdentifierTypeNode('void'), + 'myFunction', + [ + new MethodTagValueParameterNode( + new IdentifierTypeNode('DateInterval'), + false, + false, + '$date', + new ConstExprNewNode( + 'DateInterval', + [ + new ConstExprStringNode('"P1Y"'), + ] + ) + ), + ], + '' + ) + ), + ]), + ]; + + yield [ + 'OK static with parameter using new in initializer with nested new', + '/** @method static void myFunction(SomeClass $object = new SomeClass(new SomeClass)) */', + new PhpDocNode([ + new PhpDocTagNode( + '@method', + new MethodTagValueNode( + true, + new IdentifierTypeNode('void'), + 'myFunction', + [ + new MethodTagValueParameterNode( + new IdentifierTypeNode('SomeClass'), + false, + false, + '$object', + new ConstExprNewNode( + 'SomeClass', + [ + new ConstExprNewNode('SomeClass', []), + ] + ) + ), + ], + '' + ) + ), + ]), + ]; }