Created
November 17, 2022 12:47
-
-
Save rvanvelzen/ff2d187490aa3a810902a4731e350995 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/src/Ast/PhpDoc/MethodTagValueNode.php b/src/Ast/PhpDoc/MethodTagValueNode.php | |
index 155897b..bc58511 100644 | |
--- a/src/Ast/PhpDoc/MethodTagValueNode.php | |
+++ b/src/Ast/PhpDoc/MethodTagValueNode.php | |
@@ -26,13 +26,17 @@ class MethodTagValueNode implements PhpDocTagValueNode | |
/** @var string (may be empty) */ | |
public $description; | |
- public function __construct(bool $isStatic, ?TypeNode $returnType, string $methodName, array $parameters, string $description) | |
+ /** @var bool */ | |
+ public $returnsReference; | |
+ | |
+ public function __construct(bool $isStatic, ?TypeNode $returnType, string $methodName, array $parameters, string $description, bool $returnsReference = false) | |
{ | |
$this->isStatic = $isStatic; | |
$this->returnType = $returnType; | |
$this->methodName = $methodName; | |
$this->parameters = $parameters; | |
$this->description = $description; | |
+ $this->returnsReference = $returnsReference; | |
} | |
@@ -40,9 +44,10 @@ class MethodTagValueNode implements PhpDocTagValueNode | |
{ | |
$static = $this->isStatic ? 'static ' : ''; | |
$returnType = $this->returnType !== null ? "{$this->returnType} " : ''; | |
+ $returnsReference = $this->returnsReference ? '&' : ''; | |
$parameters = implode(', ', $this->parameters); | |
$description = $this->description !== '' ? " {$this->description}" : ''; | |
- return "{$static}{$returnType}{$this->methodName}({$parameters}){$description}"; | |
+ return "{$static}{$returnType}{$returnsReference}{$this->methodName}({$parameters}){$description}"; | |
} | |
} | |
diff --git a/src/Parser/PhpDocParser.php b/src/Parser/PhpDocParser.php | |
index 9badbe6..4420b7b 100644 | |
--- a/src/Parser/PhpDocParser.php | |
+++ b/src/Parser/PhpDocParser.php | |
@@ -6,6 +6,7 @@ use PHPStan\PhpDocParser\Ast; | |
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; | |
use PHPStan\PhpDocParser\Lexer\Lexer; | |
use PHPStan\ShouldNotHappenException; | |
+use PHPStan\Type\IntersectionType; | |
use function array_key_exists; | |
use function array_values; | |
use function count; | |
@@ -329,6 +330,7 @@ class PhpDocParser | |
private function parseMethodTagValue(TokenIterator $tokens): Ast\PhpDoc\MethodTagValueNode | |
{ | |
$isStatic = $tokens->tryConsumeTokenValue('static'); | |
+ $returnsReference = false; | |
$returnTypeOrMethodName = $this->typeParser->parse($tokens); | |
if ($tokens->isCurrentTokenType(Lexer::TOKEN_IDENTIFIER)) { | |
@@ -341,6 +343,17 @@ class PhpDocParser | |
$methodName = $returnTypeOrMethodName->name; | |
$isStatic = false; | |
+ } elseif ( | |
+ $returnTypeOrMethodName instanceof Ast\Type\IntersectionTypeNode | |
+ && $returnTypeOrMethodName->types[count($returnTypeOrMethodName->types) - 1] instanceof Ast\Type\IdentifierTypeNode | |
+ && $this->isPrecededByReferenceReturn($tokens) | |
+ ) { | |
+ $methodName = $returnTypeOrMethodName->types[count($returnTypeOrMethodName->types) - 1]->name; | |
+ $returnType = count($returnTypeOrMethodName->types) === 2 | |
+ ? $returnTypeOrMethodName->types[0] | |
+ : new Ast\Type\IntersectionTypeNode(array_slice($returnTypeOrMethodName->types, 0, -1)); | |
+ $returnsReference = true; | |
+ | |
} else { | |
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); // will throw exception | |
exit; | |
@@ -357,7 +370,24 @@ class PhpDocParser | |
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES); | |
$description = $this->parseOptionalDescription($tokens); | |
- return new Ast\PhpDoc\MethodTagValueNode($isStatic, $returnType, $methodName, $parameters, $description); | |
+ return new Ast\PhpDoc\MethodTagValueNode($isStatic, $returnType, $methodName, $parameters, $description, $returnsReference); | |
+ } | |
+ | |
+ | |
+ private function isPrecededByReferenceReturn(TokenIterator $tokens): bool | |
+ { | |
+ $tokens->pushSavePoint(); | |
+ $tokens->prev(); | |
+ $tokens->prev(); | |
+ | |
+ $result = $tokens->isPrecededByHorizontalWhitespace() | |
+ && $tokens->tryConsumeTokenType(Lexer::TOKEN_INTERSECTION) | |
+ && !$tokens->isPrecededByHorizontalWhitespace() | |
+ && $tokens->tryConsumeTokenType(Lexer::TOKEN_IDENTIFIER); | |
+ | |
+ $tokens->rollback(); | |
+ | |
+ return $result; | |
} | |
diff --git a/src/Parser/TokenIterator.php b/src/Parser/TokenIterator.php | |
index 569a932..f5b6339 100644 | |
--- a/src/Parser/TokenIterator.php | |
+++ b/src/Parser/TokenIterator.php | |
@@ -179,6 +179,18 @@ class TokenIterator | |
$this->index++; | |
} | |
+ | |
+ public function prev(): void | |
+ { | |
+ $this->index--; | |
+ | |
+ if ($this->tokens[$this->index][Lexer::TYPE_OFFSET] !== Lexer::TOKEN_HORIZONTAL_WS) { | |
+ return; | |
+ } | |
+ | |
+ $this->index--; | |
+ } | |
+ | |
/** @phpstan-impure */ | |
public function forwardToTheEnd(): void | |
{ | |
diff --git a/tests/PHPStan/Parser/PhpDocParserTest.php b/tests/PHPStan/Parser/PhpDocParserTest.php | |
index c81a3a4..2f249d9 100644 | |
--- a/tests/PHPStan/Parser/PhpDocParserTest.php | |
+++ b/tests/PHPStan/Parser/PhpDocParserTest.php | |
@@ -2139,6 +2139,24 @@ class PhpDocParserTest extends TestCase | |
]), | |
]; | |
+ yield [ | |
+ 'OK by-ref return', | |
+ '/** @method static Type &myMethod() description here */', | |
+ new PhpDocNode([ | |
+ new PhpDocTagNode( | |
+ '@method', | |
+ new MethodTagValueNode( | |
+ true, | |
+ new IdentifierTypeNode('Type'), | |
+ 'myMethod', | |
+ [], | |
+ 'description here', | |
+ true | |
+ ) | |
+ ), | |
+ ]), | |
+ ]; | |
+ | |
yield [ | |
'invalid non-static method without parentheses', | |
'/** @method a b */', |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment