Fix parsing const fetches with wildcard in complex types

This commit is contained in:
Ondrej Mirtes 2020-05-04 22:30:43 +02:00
parent 486b638ff9
commit d840b6956b
No known key found for this signature in database
GPG Key ID: 8E730BA25823D8B5
6 changed files with 55 additions and 65 deletions

View File

@ -18,10 +18,7 @@ class Lexer
public const TOKEN_CLOSE_ANGLE_BRACKET = 7;
public const TOKEN_OPEN_SQUARE_BRACKET = 8;
public const TOKEN_CLOSE_SQUARE_BRACKET = 9;
public const TOKEN_OPEN_CURLY_BRACKET = 30;
public const TOKEN_CLOSE_CURLY_BRACKET = 31;
public const TOKEN_COMMA = 10;
public const TOKEN_COLON = 29;
public const TOKEN_VARIADIC = 11;
public const TOKEN_DOUBLE_COLON = 12;
public const TOKEN_DOUBLE_ARROW = 13;
@ -29,7 +26,6 @@ class Lexer
public const TOKEN_OPEN_PHPDOC = 15;
public const TOKEN_CLOSE_PHPDOC = 16;
public const TOKEN_PHPDOC_TAG = 17;
public const TOKEN_PHPDOC_EOL = 26;
public const TOKEN_FLOAT = 18;
public const TOKEN_INTEGER = 19;
public const TOKEN_SINGLE_QUOTED_STRING = 20;
@ -38,8 +34,13 @@ class Lexer
public const TOKEN_THIS_VARIABLE = 23;
public const TOKEN_VARIABLE = 24;
public const TOKEN_HORIZONTAL_WS = 25;
public const TOKEN_PHPDOC_EOL = 26;
public const TOKEN_OTHER = 27;
public const TOKEN_END = 28;
public const TOKEN_COLON = 29;
public const TOKEN_WILDCARD = 30;
public const TOKEN_OPEN_CURLY_BRACKET = 31;
public const TOKEN_CLOSE_CURLY_BRACKET = 32;
public const TOKEN_LABELS = [
self::TOKEN_REFERENCE => '\'&\'',
@ -74,6 +75,7 @@ class Lexer
self::TOKEN_HORIZONTAL_WS => 'TOKEN_HORIZONTAL_WS',
self::TOKEN_OTHER => 'TOKEN_OTHER',
self::TOKEN_END => 'TOKEN_END',
self::TOKEN_WILDCARD => '*',
];
public const VALUE_OFFSET = 0;
@ -153,6 +155,8 @@ class Lexer
self::TOKEN_HORIZONTAL_WS => '[\\x09\\x20]++',
self::TOKEN_WILDCARD => '\\*',
// anything but TOKEN_CLOSE_PHPDOC or TOKEN_HORIZONTAL_WS or TOKEN_EOL
self::TOKEN_OTHER => '(?:(?!\\*/)[^\\s])++',
];

View File

@ -57,11 +57,11 @@ class ConstExprParser
if ($tokens->currentTokenType() === Lexer::TOKEN_IDENTIFIER) {
$classConstantName .= $tokens->currentTokenValue();
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
if ($tokens->tryConsumeTokenValue('*')) {
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_WILDCARD)) {
$classConstantName .= '*';
}
} else {
$tokens->consumeTokenValue('*');
$tokens->consumeTokenType(Lexer::TOKEN_WILDCARD);
$classConstantName .= '*';
}

View File

@ -19,43 +19,27 @@ class ParserException extends \Exception
/** @var int */
private $expectedTokenType;
/** @var string */
private $expectedTokenValue;
public function __construct(
string $currentTokenValue,
int $currentTokenType,
int $currentOffset,
?int $expectedTokenType,
?string $expectedTokenValue = null
int $expectedTokenType
)
{
$this->currentTokenValue = $currentTokenValue;
$this->currentTokenType = $currentTokenType;
$this->currentOffset = $currentOffset;
$this->expectedTokenType = $expectedTokenType;
$this->expectedTokenValue = $expectedTokenValue;
$json = json_encode($currentTokenValue, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
assert($json !== false);
if ($expectedTokenType !== null) {
parent::__construct(sprintf(
'Unexpected token %s, expected %s at offset %d',
$json,
Lexer::TOKEN_LABELS[$expectedTokenType],
$currentOffset
));
} elseif ($expectedTokenValue !== null) {
parent::__construct(sprintf(
'Unexpected token value %s, expected value %s at offset %d',
$json,
$expectedTokenValue,
$currentOffset
));
} else {
throw new \LogicException();
}
parent::__construct(sprintf(
'Unexpected token %s, expected %s at offset %d',
$json,
Lexer::TOKEN_LABELS[$expectedTokenType],
$currentOffset
));
}
@ -82,9 +66,4 @@ class ParserException extends \Exception
return $this->expectedTokenType;
}
public function getExpectedTokenValue(): string
{
return $this->expectedTokenValue;
}
}

View File

@ -69,25 +69,6 @@ class TokenIterator
return ($this->tokens[$this->index - 1][Lexer::TYPE_OFFSET] ?? -1) === Lexer::TOKEN_HORIZONTAL_WS;
}
/**
* @param string $tokenValue
* @throws \PHPStan\PhpDocParser\Parser\ParserException
*/
public function consumeTokenValue(string $tokenValue): void
{
if ($this->tokens[$this->index][Lexer::VALUE_OFFSET] !== $tokenValue) {
$this->throwError(null, $tokenValue);
}
$this->index++;
if (($this->tokens[$this->index][Lexer::TYPE_OFFSET] ?? -1) !== Lexer::TOKEN_HORIZONTAL_WS) {
return;
}
$this->index++;
}
/**
* @param int $tokenType
@ -194,18 +175,16 @@ class TokenIterator
/**
* @param int|null $expectedTokenType
* @param string|null $expectedTokenValue
* @param int $expectedTokenType
* @throws \PHPStan\PhpDocParser\Parser\ParserException
*/
private function throwError(?int $expectedTokenType, ?string $expectedTokenValue): void
private function throwError(int $expectedTokenType): void
{
throw new \PHPStan\PhpDocParser\Parser\ParserException(
$this->currentTokenValue(),
$this->currentTokenType(),
$this->currentTokenOffset(),
$expectedTokenType,
$expectedTokenValue
$expectedTokenType
);
}

View File

@ -3046,6 +3046,14 @@ chunk. Must be higher that in the previous request.'),
),
]),
];
yield [
'malformed const fetch',
'/** @param Foo::** $a */',
new PhpDocNode([
new PhpDocTagNode('@param', new InvalidTagValueNode('Foo::** $a', new ParserException('*', Lexer::TOKEN_WILDCARD, 17, Lexer::TOKEN_VARIABLE))),
]),
];
}
public function dataParseTagValue(): array

View File

@ -864,13 +864,13 @@ class TypeParserTest extends \PHPUnit\Framework\TestCase
],
[
'Foo::**',
new \PHPStan\PhpDocParser\Parser\ParserException(
'**',
Lexer::TOKEN_END,
5,
null,
'*'
),
new ConstTypeNode(new ConstFetchNode('Foo', '*')), // fails later in PhpDocParser
Lexer::TOKEN_WILDCARD,
],
[
'Foo::*a',
new ConstTypeNode(new ConstFetchNode('Foo', '*')), // fails later in PhpDocParser
Lexer::TOKEN_IDENTIFIER,
],
[
'( "foo" | Foo::FOO_* )',
@ -879,6 +879,26 @@ class TypeParserTest extends \PHPUnit\Framework\TestCase
new ConstTypeNode(new ConstFetchNode('Foo', 'FOO_*')),
]),
],
[
'DateTimeImmutable::*|DateTime::*',
new UnionTypeNode([
new ConstTypeNode(new ConstFetchNode('DateTimeImmutable', '*')),
new ConstTypeNode(new ConstFetchNode('DateTime', '*')),
]),
],
[
'ParameterTier::*|null',
new UnionTypeNode([
new ConstTypeNode(new ConstFetchNode('ParameterTier', '*')),
new IdentifierTypeNode('null'),
]),
],
[
'list<QueueAttributeName::*>',
new GenericTypeNode(new IdentifierTypeNode('list'), [
new ConstTypeNode(new ConstFetchNode('QueueAttributeName', '*')),
]),
],
];
}