Skip to content

Instantly share code, notes, and snippets.

@rvanvelzen
Created November 17, 2022 12:47
Show Gist options
  • Save rvanvelzen/ff2d187490aa3a810902a4731e350995 to your computer and use it in GitHub Desktop.
Save rvanvelzen/ff2d187490aa3a810902a4731e350995 to your computer and use it in GitHub Desktop.
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