From 2a022b29edf98677743868140f44c88ba4388e6d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 28 Apr 2023 10:10:18 +0200 Subject: [PATCH 01/13] Un-extract variable --- tests/PHPStan/Printer/PrinterTest.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/PHPStan/Printer/PrinterTest.php b/tests/PHPStan/Printer/PrinterTest.php index b689b440..ee07ef6d 100644 --- a/tests/PHPStan/Printer/PrinterTest.php +++ b/tests/PHPStan/Printer/PrinterTest.php @@ -1177,10 +1177,9 @@ public function testPrintFormatPreserving(string $phpDoc, string $expectedResult $newPhpDoc = $printer->printFormatPreserving($newNode, $phpDocNode, $tokens); $this->assertSame($expectedResult, $newPhpDoc); - $newTokens = new TokenIterator($lexer->tokenize($newPhpDoc)); $this->assertEquals( $this->unsetAttributes($newNode), - $this->unsetAttributes($phpDocParser->parse($newTokens)) + $this->unsetAttributes($phpDocParser->parse(new TokenIterator($lexer->tokenize($newPhpDoc)))) ); } From 68d46b98b080c1652b64ed4b91d10f38f5ac8a5d Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 28 Apr 2023 10:21:22 +0200 Subject: [PATCH 02/13] Extract creating PhpDocParser to setUp --- tests/PHPStan/Printer/PrinterTest.php | 29 +++++++++++++++++---------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/tests/PHPStan/Printer/PrinterTest.php b/tests/PHPStan/Printer/PrinterTest.php index ee07ef6d..b9c2cbe4 100644 --- a/tests/PHPStan/Printer/PrinterTest.php +++ b/tests/PHPStan/Printer/PrinterTest.php @@ -42,6 +42,22 @@ class PrinterTest extends TestCase { + /** @var PhpDocParser */ + private $phpDocParser; + + protected function setUp(): void + { + $usedAttributes = ['lines' => true, 'indexes' => true]; + $constExprParser = new ConstExprParser(true, true, $usedAttributes); + $this->phpDocParser = new PhpDocParser( + new TypeParser($constExprParser, true, $usedAttributes), + $constExprParser, + true, + true, + $usedAttributes + ); + } + /** * @return iterable */ @@ -1153,18 +1169,9 @@ public function enterNode(Node $node) */ public function testPrintFormatPreserving(string $phpDoc, string $expectedResult, NodeVisitor $visitor): void { - $usedAttributes = ['lines' => true, 'indexes' => true]; - $constExprParser = new ConstExprParser(true, true, $usedAttributes); - $phpDocParser = new PhpDocParser( - new TypeParser($constExprParser, true, $usedAttributes), - $constExprParser, - true, - true, - $usedAttributes - ); $lexer = new Lexer(); $tokens = new TokenIterator($lexer->tokenize($phpDoc)); - $phpDocNode = $phpDocParser->parse($tokens); + $phpDocNode = $this->phpDocParser->parse($tokens); $cloningTraverser = new NodeTraverser([new NodeVisitor\CloningVisitor()]); $newNodes = $cloningTraverser->traverse([$phpDocNode]); @@ -1179,7 +1186,7 @@ public function testPrintFormatPreserving(string $phpDoc, string $expectedResult $this->assertEquals( $this->unsetAttributes($newNode), - $this->unsetAttributes($phpDocParser->parse(new TokenIterator($lexer->tokenize($newPhpDoc)))) + $this->unsetAttributes($this->phpDocParser->parse(new TokenIterator($lexer->tokenize($newPhpDoc)))) ); } From f5a51457371da8b9674fc5c57b13deb834a22b31 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 28 Apr 2023 10:10:27 +0200 Subject: [PATCH 03/13] Test Printer::print() --- tests/PHPStan/Printer/PrinterTest.php | 65 ++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/tests/PHPStan/Printer/PrinterTest.php b/tests/PHPStan/Printer/PrinterTest.php index b9c2cbe4..0e8d7d65 100644 --- a/tests/PHPStan/Printer/PrinterTest.php +++ b/tests/PHPStan/Printer/PrinterTest.php @@ -42,6 +42,9 @@ class PrinterTest extends TestCase { + /** @var TypeParser */ + private $typeParser; + /** @var PhpDocParser */ private $phpDocParser; @@ -49,8 +52,9 @@ protected function setUp(): void { $usedAttributes = ['lines' => true, 'indexes' => true]; $constExprParser = new ConstExprParser(true, true, $usedAttributes); + $this->typeParser = new TypeParser($constExprParser, true, $usedAttributes); $this->phpDocParser = new PhpDocParser( - new TypeParser($constExprParser, true, $usedAttributes), + $this->typeParser, $constExprParser, true, true, @@ -1190,7 +1194,7 @@ public function testPrintFormatPreserving(string $phpDoc, string $expectedResult ); } - private function unsetAttributes(PhpDocNode $node): PhpDocNode + private function unsetAttributes(Node $node): Node { $visitor = new class extends AbstractNodeVisitor { @@ -1213,4 +1217,61 @@ public function enterNode(Node $node) return $traverser->traverse([$node])[0]; } + public function dataPrintType(): iterable + { + yield [ + new IdentifierTypeNode('int'), + 'int', + ]; + } + + /** + * @dataProvider dataPrintType + */ + public function testPrintType(TypeNode $node, string $expectedResult): void + { + $printer = new Printer(); + $phpDoc = $printer->print($node); + $this->assertSame($expectedResult, $phpDoc); + + $lexer = new Lexer(); + $this->assertEquals( + $this->unsetAttributes($node), + $this->unsetAttributes($this->typeParser->parse(new TokenIterator($lexer->tokenize($phpDoc)))) + ); + } + + public function dataPrintPhpDocNode(): iterable + { + yield [ + new PhpDocNode([ + new PhpDocTagNode('@param', new ParamTagValueNode( + new IdentifierTypeNode('int'), + false, + '$a', + '' + )), + ]), + '/** + * @param int $a + */', + ]; + } + + /** + * @dataProvider dataPrintPhpDocNode + */ + public function testPrintPhpDocNode(PhpDocNode $node, string $expectedResult): void + { + $printer = new Printer(); + $phpDoc = $printer->print($node); + $this->assertSame($expectedResult, $phpDoc); + + $lexer = new Lexer(); + $this->assertEquals( + $this->unsetAttributes($node), + $this->unsetAttributes($this->phpDocParser->parse(new TokenIterator($lexer->tokenize($phpDoc)))) + ); + } + } From 2ebed2c5e10979e2af44679cd1f3b9c3e9273eb1 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 2 May 2023 10:09:35 +0200 Subject: [PATCH 04/13] Open 1.20.x --- .github/workflows/backward-compatibility.yml | 2 +- .github/workflows/build.yml | 2 +- .github/workflows/test-slevomat-coding-standard.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/backward-compatibility.yml b/.github/workflows/backward-compatibility.yml index a7abd897..8619d234 100644 --- a/.github/workflows/backward-compatibility.yml +++ b/.github/workflows/backward-compatibility.yml @@ -6,7 +6,7 @@ on: pull_request: push: branches: - - "1.9.x" + - "1.20.x" jobs: backward-compatibility: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b6ed9990..bfdb0d0a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,7 +6,7 @@ on: pull_request: push: branches: - - "1.9.x" + - "1.20.x" jobs: lint: diff --git a/.github/workflows/test-slevomat-coding-standard.yml b/.github/workflows/test-slevomat-coding-standard.yml index cc9e1b0b..1e76e59c 100644 --- a/.github/workflows/test-slevomat-coding-standard.yml +++ b/.github/workflows/test-slevomat-coding-standard.yml @@ -6,7 +6,7 @@ on: pull_request: push: branches: - - "1.9.x" + - "1.20.x" jobs: tests: From e9514dc3a1633bca6ef3b4497ce9bec184f2d83e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 2 May 2023 10:10:48 +0200 Subject: [PATCH 05/13] Open 1.21.x --- .github/workflows/backward-compatibility.yml | 2 +- .github/workflows/build.yml | 2 +- .github/workflows/test-slevomat-coding-standard.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/backward-compatibility.yml b/.github/workflows/backward-compatibility.yml index 8619d234..1cd62723 100644 --- a/.github/workflows/backward-compatibility.yml +++ b/.github/workflows/backward-compatibility.yml @@ -6,7 +6,7 @@ on: pull_request: push: branches: - - "1.20.x" + - "1.21.x" jobs: backward-compatibility: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bfdb0d0a..19401af3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,7 +6,7 @@ on: pull_request: push: branches: - - "1.20.x" + - "1.21.x" jobs: lint: diff --git a/.github/workflows/test-slevomat-coding-standard.yml b/.github/workflows/test-slevomat-coding-standard.yml index 1e76e59c..ab90c303 100644 --- a/.github/workflows/test-slevomat-coding-standard.yml +++ b/.github/workflows/test-slevomat-coding-standard.yml @@ -6,7 +6,7 @@ on: pull_request: push: branches: - - "1.20.x" + - "1.21.x" jobs: tests: From 69bc46ffa5250e61b0681e98e716865b71b90c3d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 May 2023 02:00:04 +0000 Subject: [PATCH 06/13] Update dependency slevomat/coding-standard to v8.11.1 --- build-cs/composer.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/build-cs/composer.lock b/build-cs/composer.lock index 0cea3f9e..c59ffc5f 100644 --- a/build-cs/composer.lock +++ b/build-cs/composer.lock @@ -154,16 +154,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.20.2", + "version": "1.20.3", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "90490bd8fd8530a272043c4950c180b6d0cf5f81" + "reference": "6c04009f6cae6eda2f040745b6b846080ef069c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/90490bd8fd8530a272043c4950c180b6d0cf5f81", - "reference": "90490bd8fd8530a272043c4950c180b6d0cf5f81", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6c04009f6cae6eda2f040745b6b846080ef069c2", + "reference": "6c04009f6cae6eda2f040745b6b846080ef069c2", "shasum": "" }, "require": { @@ -193,22 +193,22 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.20.2" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.20.3" }, - "time": "2023-04-22T12:59:35+00:00" + "time": "2023-04-25T09:01:03+00:00" }, { "name": "slevomat/coding-standard", - "version": "8.11.0", + "version": "8.11.1", "source": { "type": "git", "url": "https://github.com/slevomat/coding-standard.git", - "reference": "91428d5bcf7db93a842bcf97f465edf62527f3ea" + "reference": "af87461316b257e46e15bb041dca6fca3796d822" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/91428d5bcf7db93a842bcf97f465edf62527f3ea", - "reference": "91428d5bcf7db93a842bcf97f465edf62527f3ea", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/af87461316b257e46e15bb041dca6fca3796d822", + "reference": "af87461316b257e46e15bb041dca6fca3796d822", "shasum": "" }, "require": { @@ -248,7 +248,7 @@ ], "support": { "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/8.11.0" + "source": "https://github.com/slevomat/coding-standard/tree/8.11.1" }, "funding": [ { @@ -260,7 +260,7 @@ "type": "tidelift" } ], - "time": "2023-04-21T15:51:44+00:00" + "time": "2023-04-24T08:19:01+00:00" }, { "name": "squizlabs/php_codesniffer", From 97d18ea02586436b36899b4312c6e1f9ed50e540 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 2 May 2023 10:17:01 +0200 Subject: [PATCH 07/13] Fix build --- tests/PHPStan/Printer/PrinterTest.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/PHPStan/Printer/PrinterTest.php b/tests/PHPStan/Printer/PrinterTest.php index 0e8d7d65..ea236b75 100644 --- a/tests/PHPStan/Printer/PrinterTest.php +++ b/tests/PHPStan/Printer/PrinterTest.php @@ -26,6 +26,7 @@ use PHPStan\PhpDocParser\Ast\Type\ObjectShapeItemNode; use PHPStan\PhpDocParser\Ast\Type\ObjectShapeNode; use PHPStan\PhpDocParser\Ast\Type\OffsetAccessTypeNode; +use PHPStan\PhpDocParser\Ast\Type\TypeNode; use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode; use PHPStan\PhpDocParser\Lexer\Lexer; use PHPStan\PhpDocParser\Parser\ConstExprParser; @@ -1217,6 +1218,9 @@ public function enterNode(Node $node) return $traverser->traverse([$node])[0]; } + /** + * @return iterable + */ public function dataPrintType(): iterable { yield [ @@ -1241,6 +1245,9 @@ public function testPrintType(TypeNode $node, string $expectedResult): void ); } + /** + * @return iterable + */ public function dataPrintPhpDocNode(): iterable { yield [ From d60fa73f5c3215d122391a13f9c89dce86f34a82 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 2 May 2023 10:32:46 +0200 Subject: [PATCH 08/13] CallableTypeNode - support object shape in return type --- src/Parser/TypeParser.php | 18 +++++++++++------- tests/PHPStan/Parser/TypeParserTest.php | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/Parser/TypeParser.php b/src/Parser/TypeParser.php index d59d0f1f..ba71a2c3 100644 --- a/src/Parser/TypeParser.php +++ b/src/Parser/TypeParser.php @@ -542,13 +542,17 @@ private function parseCallableReturnType(TokenIterator $tokens): Ast\Type\TypeNo ) ); - } elseif (in_array($type->name, ['array', 'list'], true) && $tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET) && !$tokens->isPrecededByHorizontalWhitespace()) { - $type = $this->parseArrayShape($tokens, $this->enrichWithAttributes( - $tokens, - $type, - $startLine, - $startIndex - ), $type->name); + } 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); + } } } diff --git a/tests/PHPStan/Parser/TypeParserTest.php b/tests/PHPStan/Parser/TypeParserTest.php index 09edabb3..248b10bc 100644 --- a/tests/PHPStan/Parser/TypeParserTest.php +++ b/tests/PHPStan/Parser/TypeParserTest.php @@ -1939,6 +1939,24 @@ public function provideParseData(): array 'callable(): ?int', new CallableTypeNode(new IdentifierTypeNode('callable'), [], new NullableTypeNode(new IdentifierTypeNode('int'))), ], + [ + 'callable(): object{foo: int}', + new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ObjectShapeNode([ + new ObjectShapeItemNode(new IdentifierTypeNode('foo'), false, new IdentifierTypeNode('int')), + ])), + ], + [ + 'callable(): object{foo: int}[]', + new CallableTypeNode( + new IdentifierTypeNode('callable'), + [], + new ArrayTypeNode( + new ObjectShapeNode([ + new ObjectShapeItemNode(new IdentifierTypeNode('foo'), false, new IdentifierTypeNode('int')), + ]) + ) + ), + ], ]; } From 421d3f32e02543e2a99259788d0dbb60f9c75332 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 2 May 2023 10:38:49 +0200 Subject: [PATCH 09/13] CallableTypeNode - support `$this` in return type --- src/Parser/TypeParser.php | 3 ++- tests/PHPStan/Parser/TypeParserTest.php | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Parser/TypeParser.php b/src/Parser/TypeParser.php index ba71a2c3..31ef6c19 100644 --- a/src/Parser/TypeParser.php +++ b/src/Parser/TypeParser.php @@ -526,7 +526,8 @@ private function parseCallableReturnType(TokenIterator $tokens): Ast\Type\TypeNo } elseif ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) { $type = $this->parse($tokens); $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES); - + } elseif ($tokens->tryConsumeTokenType(Lexer::TOKEN_THIS_VARIABLE)) { + $type = new Ast\Type\ThisTypeNode(); } else { $type = new Ast\Type\IdentifierTypeNode($tokens->currentTokenValue()); $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); diff --git a/tests/PHPStan/Parser/TypeParserTest.php b/tests/PHPStan/Parser/TypeParserTest.php index 248b10bc..e8b36a5e 100644 --- a/tests/PHPStan/Parser/TypeParserTest.php +++ b/tests/PHPStan/Parser/TypeParserTest.php @@ -1957,6 +1957,14 @@ public function provideParseData(): array ) ), ], + [ + 'callable(): $this', + new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ThisTypeNode()), + ], + [ + 'callable(): $this[]', + new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ArrayTypeNode(new ThisTypeNode())), + ], ]; } From a7e969872e2e34d796ddc49014032adc32f6ff14 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 2 May 2023 10:46:29 +0200 Subject: [PATCH 10/13] 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', '*'))), + ], ]; } From 7d568c87a9df9c5f7e8b5f075fc469aa8cb0a4cd Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 2 May 2023 11:19:37 +0200 Subject: [PATCH 11/13] Simplify implementation --- src/Parser/TypeParser.php | 12 +----------- tests/PHPStan/Parser/TypeParserTest.php | 12 ++++++++++++ 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/Parser/TypeParser.php b/src/Parser/TypeParser.php index 9e8956f5..ffe10bb5 100644 --- a/src/Parser/TypeParser.php +++ b/src/Parser/TypeParser.php @@ -521,17 +521,7 @@ private function parseCallableReturnType(TokenIterator $tokens): Ast\Type\TypeNo $startLine = $tokens->currentTokenLine(); $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; + return $this->parseNullable($tokens); } elseif ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) { $type = $this->parse($tokens); diff --git a/tests/PHPStan/Parser/TypeParserTest.php b/tests/PHPStan/Parser/TypeParserTest.php index 0fcb0e3b..b16f7b01 100644 --- a/tests/PHPStan/Parser/TypeParserTest.php +++ b/tests/PHPStan/Parser/TypeParserTest.php @@ -2014,6 +2014,18 @@ public function provideParseData(): array 'callable(): Foo::*', new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ConstTypeNode(new ConstFetchNode('Foo', '*'))), ], + [ + '?Foo[]', + new NullableTypeNode(new ArrayTypeNode(new IdentifierTypeNode('Foo'))), + ], + [ + 'callable(): ?Foo', + new CallableTypeNode(new IdentifierTypeNode('callable'), [], new NullableTypeNode(new IdentifierTypeNode('Foo'))), + ], + [ + 'callable(): ?Foo[]', + new CallableTypeNode(new IdentifierTypeNode('callable'), [], new NullableTypeNode(new ArrayTypeNode(new IdentifierTypeNode('Foo')))), + ], ]; } From ced520a95793731fa3e58d126af8058dacb6fff7 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 16 May 2023 21:42:58 +0200 Subject: [PATCH 12/13] Micro optimize parseParamTagValue() Prevent unnecessary function calls on a hot path --- src/Parser/PhpDocParser.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Parser/PhpDocParser.php b/src/Parser/PhpDocParser.php index 7f0e4a44..d3eed465 100644 --- a/src/Parser/PhpDocParser.php +++ b/src/Parser/PhpDocParser.php @@ -333,9 +333,7 @@ public function parseTagValue(TokenIterator $tokens, string $tag): Ast\PhpDoc\Ph private function parseParamTagValue(TokenIterator $tokens): Ast\PhpDoc\PhpDocTagValueNode { if ( - $tokens->isCurrentTokenType(Lexer::TOKEN_REFERENCE) - || $tokens->isCurrentTokenType(Lexer::TOKEN_VARIADIC) - || $tokens->isCurrentTokenType(Lexer::TOKEN_VARIABLE) + $tokens->isCurrentTokenType(Lexer::TOKEN_REFERENCE, Lexer::TOKEN_VARIADIC, Lexer::TOKEN_VARIABLE) ) { $type = null; } else { From bfdb616431f63262277ea703ba604218a4cda841 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 17 May 2023 04:58:37 +0000 Subject: [PATCH 13/13] Update root-composer --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 30b879b7..aab3969f 100644 --- a/composer.json +++ b/composer.json @@ -12,8 +12,8 @@ "phpstan/phpstan": "^1.5", "phpstan/phpstan-phpunit": "^1.1", "phpstan/phpstan-strict-rules": "^1.0", - "phpunit/phpunit": "^9.5", - "symfony/process": "^5.2" + "phpunit/phpunit": "^10.0", + "symfony/process": "^6.0" }, "config": { "platform": {