Support ConstExprNode as type

This commit is contained in:
Ondrej Mirtes 2020-04-30 15:03:40 +02:00
parent c3aaad7ef5
commit d60ff778be
No known key found for this signature in database
GPG Key ID: 8E730BA25823D8B5
6 changed files with 176 additions and 33 deletions

View File

@ -0,0 +1,23 @@
<?php declare(strict_types = 1);
namespace PHPStan\PhpDocParser\Ast\Type;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprNode;
class ConstTypeNode implements TypeNode
{
/** @var ConstExprNode */
public $constExpr;
public function __construct(ConstExprNode $constExpr)
{
$this->constExpr = $constExpr;
}
public function __toString(): string
{
return $this->constExpr->__toString();
}
}

View File

@ -8,7 +8,7 @@ use PHPStan\PhpDocParser\Lexer\Lexer;
class ConstExprParser
{
public function parse(TokenIterator $tokens): Ast\ConstExpr\ConstExprNode
public function parse(TokenIterator $tokens, bool $trimStrings = false): Ast\ConstExpr\ConstExprNode
{
if ($tokens->isCurrentTokenType(Lexer::TOKEN_FLOAT)) {
$value = $tokens->currentTokenValue();
@ -22,11 +22,17 @@ class ConstExprParser
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_SINGLE_QUOTED_STRING)) {
$value = $tokens->currentTokenValue();
if ($trimStrings) {
$value = trim($tokens->currentTokenValue(), "'");
}
$tokens->next();
return new Ast\ConstExpr\ConstExprStringNode($value);
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_QUOTED_STRING)) {
$value = $tokens->currentTokenValue();
if ($trimStrings) {
$value = trim($tokens->currentTokenValue(), '"');
}
$tokens->next();
return new Ast\ConstExpr\ConstExprStringNode($value);

View File

@ -8,6 +8,17 @@ use PHPStan\PhpDocParser\Lexer\Lexer;
class TypeParser
{
/** @var ConstExprParser|null */
private $constExprParser;
/**
* @param ConstExprParser|null $constExprParser
*/
public function __construct(?ConstExprParser $constExprParser = null)
{
$this->constExprParser = $constExprParser;
}
public function parse(TokenIterator $tokens): Ast\Type\TypeNode
{
if ($tokens->isCurrentTokenType(Lexer::TOKEN_NULLABLE)) {
@ -35,40 +46,75 @@ class TypeParser
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES);
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
$type = $this->tryParseArray($tokens, $type);
return $this->tryParseArray($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->tryParseArray($tokens, $type);
return $this->tryParseArray($tokens, $type);
}
} else {
$type = new Ast\Type\IdentifierTypeNode($tokens->currentTokenValue());
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)) {
$type = $this->parseGeneric($tokens, $type);
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
$type = $this->tryParseArray($tokens, $type);
}
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) {
$type = $this->tryParseCallable($tokens, $type);
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
$type = $this->tryParseArray($tokens, $type);
} elseif ($type->name === 'array' && $tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET) && !$tokens->isPrecededByHorizontalWhitespace()) {
$type = $this->parseArrayShape($tokens, $type);
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
$type = $this->tryParseArray($tokens, $type);
}
}
return $type;
}
return $type;
$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)) {
$tokens->dropSavePoint(); // because of ConstFetchNode
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)) {
$type = $this->parseGeneric($tokens, $type);
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
$type = $this->tryParseArray($tokens, $type);
}
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) {
$type = $this->tryParseCallable($tokens, $type);
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
$type = $this->tryParseArray($tokens, $type);
} elseif ($type->name === 'array' && $tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET) && !$tokens->isPrecededByHorizontalWhitespace()) {
$type = $this->parseArrayShape($tokens, $type);
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
$type = $this->tryParseArray($tokens, $type);
}
}
return $type;
} else {
$tokens->rollback(); // because of ConstFetchNode
}
} else {
$tokens->dropSavePoint(); // because of ConstFetchNode
}
$exception = new \PHPStan\PhpDocParser\Parser\ParserException(
$tokens->currentTokenValue(),
$tokens->currentTokenType(),
$tokens->currentTokenOffset(),
Lexer::TOKEN_IDENTIFIER
);
if ($this->constExprParser === null) {
throw $exception;
}
try {
$constExpr = $this->constExprParser->parse($tokens, true);
if ($constExpr instanceof Ast\ConstExpr\ConstExprArrayNode) {
throw $exception;
}
return new Ast\Type\ConstTypeNode($constExpr);
} catch (\LogicException $e) {
throw $exception;
}
}

View File

@ -22,7 +22,7 @@ class FuzzyTest extends \PHPUnit\Framework\TestCase
{
parent::setUp();
$this->lexer = new Lexer();
$this->typeParser = new TypeParser();
$this->typeParser = new TypeParser(new ConstExprParser());
$this->constExprParser = new ConstExprParser();
}

View File

@ -4,6 +4,7 @@ namespace PHPStan\PhpDocParser\Parser;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprArrayNode;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\DeprecatedTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\ExtendsTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode;
@ -24,6 +25,7 @@ use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
use PHPStan\PhpDocParser\Ast\Type\CallableTypeNode;
use PHPStan\PhpDocParser\Ast\Type\CallableTypeParameterNode;
use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode;
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
@ -42,7 +44,8 @@ class PhpDocParserTest extends \PHPUnit\Framework\TestCase
{
parent::setUp();
$this->lexer = new Lexer();
$this->phpDocParser = new PhpDocParser(new TypeParser(), new ConstExprParser());
$constExprParser = new ConstExprParser();
$this->phpDocParser = new PhpDocParser(new TypeParser($constExprParser), $constExprParser);
}
@ -944,15 +947,37 @@ class PhpDocParserTest extends \PHPUnit\Framework\TestCase
new InvalidTagValueNode(
'A | B < 123',
new \PHPStan\PhpDocParser\Parser\ParserException(
'123',
Lexer::TOKEN_INTEGER,
20,
Lexer::TOKEN_IDENTIFIER
'*/',
Lexer::TOKEN_CLOSE_PHPDOC,
24,
Lexer::TOKEN_CLOSE_ANGLE_BRACKET
)
)
),
]),
];
yield [
'OK with type and const expr as generic type variable',
'/** @return A | B < 123 > */',
new PhpDocNode([
new PhpDocTagNode(
'@return',
new ReturnTagValueNode(
new UnionTypeNode([
new IdentifierTypeNode('A'),
new GenericTypeNode(
new IdentifierTypeNode('B'),
[
new ConstTypeNode(new ConstExprIntegerNode('123')),
]
),
]),
''
)
),
]),
];
}
@ -2901,6 +2926,23 @@ chunk. Must be higher that in the previous request.'),
),
]),
];
yield [
'string literals in @return',
"/** @return 'foo'|'bar' */",
new PhpDocNode([
new PhpDocTagNode(
'@return',
new ReturnTagValueNode(
new UnionTypeNode([
new ConstTypeNode(new ConstExprStringNode('foo')),
new ConstTypeNode(new ConstExprStringNode('bar')),
]),
''
)
),
]),
];
}
}

View File

@ -2,13 +2,16 @@
namespace PHPStan\PhpDocParser\Parser;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprFloatNode;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode;
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstFetchNode;
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeItemNode;
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode;
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
use PHPStan\PhpDocParser\Ast\Type\CallableTypeNode;
use PHPStan\PhpDocParser\Ast\Type\CallableTypeParameterNode;
use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode;
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use PHPStan\PhpDocParser\Ast\Type\IntersectionTypeNode;
@ -31,7 +34,7 @@ class TypeParserTest extends \PHPUnit\Framework\TestCase
{
parent::setUp();
$this->lexer = new Lexer();
$this->typeParser = new TypeParser();
$this->typeParser = new TypeParser(new ConstExprParser());
}
@ -828,6 +831,29 @@ class TypeParserTest extends \PHPUnit\Framework\TestCase
new CallableTypeParameterNode(new IdentifierTypeNode('mixed'), false, true, '', false),
], new IdentifierTypeNode('TReturn')),
],
[
"'foo'|'bar'",
new UnionTypeNode([
new ConstTypeNode(new ConstExprStringNode('foo')),
new ConstTypeNode(new ConstExprStringNode('bar')),
]),
],
[
'Foo::FOO_CONSTANT',
new ConstTypeNode(new ConstFetchNode('Foo', 'FOO_CONSTANT')),
],
[
'123',
new ConstTypeNode(new ConstExprIntegerNode('123')),
],
[
'123.2',
new ConstTypeNode(new ConstExprFloatNode('123.2')),
],
[
'"bar"',
new ConstTypeNode(new ConstExprStringNode('bar')),
],
];
}