2021-05-10 00:23:30 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
declare (strict_types=1);
|
2022-06-06 17:12:56 +00:00
|
|
|
namespace PHPStan\PhpDocParser\Parser;
|
2021-05-10 00:23:30 +00:00
|
|
|
|
2022-03-28 09:17:08 +00:00
|
|
|
use LogicException;
|
2022-06-06 17:12:56 +00:00
|
|
|
use PHPStan\PhpDocParser\Ast;
|
|
|
|
use PHPStan\PhpDocParser\Lexer\Lexer;
|
2022-03-28 09:17:08 +00:00
|
|
|
use function strpos;
|
|
|
|
use function trim;
|
2021-05-10 00:23:30 +00:00
|
|
|
class TypeParser
|
|
|
|
{
|
|
|
|
/** @var ConstExprParser|null */
|
|
|
|
private $constExprParser;
|
2022-06-06 17:12:56 +00:00
|
|
|
public function __construct(?\PHPStan\PhpDocParser\Parser\ConstExprParser $constExprParser = null)
|
2021-05-10 00:23:30 +00:00
|
|
|
{
|
|
|
|
$this->constExprParser = $constExprParser;
|
|
|
|
}
|
2021-12-10 10:22:23 +00:00
|
|
|
/** @phpstan-impure */
|
2022-06-07 08:22:29 +00:00
|
|
|
public function parse(\PHPStan\PhpDocParser\Parser\TokenIterator $tokens) : Ast\Type\TypeNode
|
2021-05-10 00:23:30 +00:00
|
|
|
{
|
2022-06-07 08:22:29 +00:00
|
|
|
if ($tokens->isCurrentTokenType(Lexer::TOKEN_NULLABLE)) {
|
2021-05-10 00:23:30 +00:00
|
|
|
$type = $this->parseNullable($tokens);
|
|
|
|
} else {
|
|
|
|
$type = $this->parseAtomic($tokens);
|
2022-06-07 08:22:29 +00:00
|
|
|
if ($tokens->isCurrentTokenType(Lexer::TOKEN_UNION)) {
|
2021-05-10 00:23:30 +00:00
|
|
|
$type = $this->parseUnion($tokens, $type);
|
2022-06-07 08:22:29 +00:00
|
|
|
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_INTERSECTION)) {
|
2021-05-10 00:23:30 +00:00
|
|
|
$type = $this->parseIntersection($tokens, $type);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $type;
|
|
|
|
}
|
2021-07-05 23:06:30 +00:00
|
|
|
/** @phpstan-impure */
|
2022-06-07 08:22:29 +00:00
|
|
|
private function subParse(\PHPStan\PhpDocParser\Parser\TokenIterator $tokens) : Ast\Type\TypeNode
|
2022-03-28 22:48:45 +00:00
|
|
|
{
|
2022-06-07 08:22:29 +00:00
|
|
|
if ($tokens->isCurrentTokenType(Lexer::TOKEN_NULLABLE)) {
|
2022-03-28 22:48:45 +00:00
|
|
|
$type = $this->parseNullable($tokens);
|
2022-06-07 08:22:29 +00:00
|
|
|
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_VARIABLE)) {
|
2022-03-28 22:48:45 +00:00
|
|
|
$type = $this->parseConditionalForParameter($tokens, $tokens->currentTokenValue());
|
|
|
|
} else {
|
|
|
|
$type = $this->parseAtomic($tokens);
|
2022-03-31 11:04:24 +00:00
|
|
|
if ($tokens->isCurrentTokenValue('is')) {
|
2022-03-28 22:48:45 +00:00
|
|
|
$type = $this->parseConditional($tokens, $type);
|
2022-03-31 11:04:24 +00:00
|
|
|
} else {
|
2022-06-07 08:22:29 +00:00
|
|
|
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
|
|
|
if ($tokens->isCurrentTokenType(Lexer::TOKEN_UNION)) {
|
2022-03-31 11:04:24 +00:00
|
|
|
$type = $this->subParseUnion($tokens, $type);
|
2022-06-07 08:22:29 +00:00
|
|
|
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_INTERSECTION)) {
|
2022-03-31 11:04:24 +00:00
|
|
|
$type = $this->subParseIntersection($tokens, $type);
|
|
|
|
}
|
2022-03-28 22:48:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return $type;
|
|
|
|
}
|
|
|
|
/** @phpstan-impure */
|
2022-06-07 08:22:29 +00:00
|
|
|
private function parseAtomic(\PHPStan\PhpDocParser\Parser\TokenIterator $tokens) : Ast\Type\TypeNode
|
2021-05-10 00:23:30 +00:00
|
|
|
{
|
2022-06-07 08:22:29 +00:00
|
|
|
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) {
|
|
|
|
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
2022-03-28 22:48:45 +00:00
|
|
|
$type = $this->subParse($tokens);
|
2022-06-07 08:22:29 +00:00
|
|
|
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
|
|
|
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES);
|
|
|
|
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
|
2022-05-05 13:58:33 +00:00
|
|
|
return $this->tryParseArrayOrOffsetAccess($tokens, $type);
|
2021-05-10 00:23:30 +00:00
|
|
|
}
|
|
|
|
return $type;
|
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_THIS_VARIABLE)) {
|
|
|
|
$type = new Ast\Type\ThisTypeNode();
|
|
|
|
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
|
2022-05-05 13:58:33 +00:00
|
|
|
return $this->tryParseArrayOrOffsetAccess($tokens, $type);
|
2021-05-10 00:23:30 +00:00
|
|
|
}
|
|
|
|
return $type;
|
|
|
|
}
|
|
|
|
$currentTokenValue = $tokens->currentTokenValue();
|
|
|
|
$tokens->pushSavePoint();
|
|
|
|
// because of ConstFetchNode
|
2022-06-07 08:22:29 +00:00
|
|
|
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_IDENTIFIER)) {
|
|
|
|
$type = new Ast\Type\IdentifierTypeNode($currentTokenValue);
|
|
|
|
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_COLON)) {
|
2021-05-10 00:23:30 +00:00
|
|
|
$tokens->dropSavePoint();
|
|
|
|
// because of ConstFetchNode
|
2022-06-07 08:22:29 +00:00
|
|
|
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)) {
|
2021-05-10 00:23:30 +00:00
|
|
|
$tokens->pushSavePoint();
|
|
|
|
$isHtml = $this->isHtml($tokens);
|
|
|
|
$tokens->rollback();
|
|
|
|
if ($isHtml) {
|
|
|
|
return $type;
|
|
|
|
}
|
|
|
|
$type = $this->parseGeneric($tokens, $type);
|
2022-06-07 08:22:29 +00:00
|
|
|
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
|
2022-05-05 13:58:33 +00:00
|
|
|
$type = $this->tryParseArrayOrOffsetAccess($tokens, $type);
|
2021-05-10 00:23:30 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) {
|
2021-05-10 00:23:30 +00:00
|
|
|
$type = $this->tryParseCallable($tokens, $type);
|
2022-06-07 08:22:29 +00:00
|
|
|
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
|
2022-05-05 13:58:33 +00:00
|
|
|
$type = $this->tryParseArrayOrOffsetAccess($tokens, $type);
|
2022-06-07 08:22:29 +00:00
|
|
|
} elseif ($type->name === 'array' && $tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET) && !$tokens->isPrecededByHorizontalWhitespace()) {
|
2021-05-10 00:23:30 +00:00
|
|
|
$type = $this->parseArrayShape($tokens, $type);
|
2022-06-07 08:22:29 +00:00
|
|
|
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
|
2022-05-05 13:58:33 +00:00
|
|
|
$type = $this->tryParseArrayOrOffsetAccess($tokens, $type);
|
2021-05-10 00:23:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return $type;
|
|
|
|
} else {
|
|
|
|
$tokens->rollback();
|
|
|
|
// because of ConstFetchNode
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$tokens->dropSavePoint();
|
|
|
|
// because of ConstFetchNode
|
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
$exception = new \PHPStan\PhpDocParser\Parser\ParserException($tokens->currentTokenValue(), $tokens->currentTokenType(), $tokens->currentTokenOffset(), Lexer::TOKEN_IDENTIFIER);
|
2021-05-10 00:23:30 +00:00
|
|
|
if ($this->constExprParser === null) {
|
|
|
|
throw $exception;
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
$constExpr = $this->constExprParser->parse($tokens, \true);
|
2022-06-07 08:22:29 +00:00
|
|
|
if ($constExpr instanceof Ast\ConstExpr\ConstExprArrayNode) {
|
2021-05-10 00:23:30 +00:00
|
|
|
throw $exception;
|
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
return new Ast\Type\ConstTypeNode($constExpr);
|
|
|
|
} catch (LogicException $e) {
|
2021-05-10 00:23:30 +00:00
|
|
|
throw $exception;
|
|
|
|
}
|
|
|
|
}
|
2021-07-05 23:06:30 +00:00
|
|
|
/** @phpstan-impure */
|
2022-06-07 08:22:29 +00:00
|
|
|
private function parseUnion(\PHPStan\PhpDocParser\Parser\TokenIterator $tokens, Ast\Type\TypeNode $type) : Ast\Type\TypeNode
|
2021-05-10 00:23:30 +00:00
|
|
|
{
|
|
|
|
$types = [$type];
|
2022-06-07 08:22:29 +00:00
|
|
|
while ($tokens->tryConsumeTokenType(Lexer::TOKEN_UNION)) {
|
2021-05-10 00:23:30 +00:00
|
|
|
$types[] = $this->parseAtomic($tokens);
|
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
return new Ast\Type\UnionTypeNode($types);
|
2021-05-10 00:23:30 +00:00
|
|
|
}
|
2021-07-05 23:06:30 +00:00
|
|
|
/** @phpstan-impure */
|
2022-06-07 08:22:29 +00:00
|
|
|
private function subParseUnion(\PHPStan\PhpDocParser\Parser\TokenIterator $tokens, Ast\Type\TypeNode $type) : Ast\Type\TypeNode
|
2022-03-31 11:04:24 +00:00
|
|
|
{
|
|
|
|
$types = [$type];
|
2022-06-07 08:22:29 +00:00
|
|
|
while ($tokens->tryConsumeTokenType(Lexer::TOKEN_UNION)) {
|
|
|
|
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
2022-03-31 11:04:24 +00:00
|
|
|
$types[] = $this->parseAtomic($tokens);
|
2022-06-07 08:22:29 +00:00
|
|
|
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
2022-03-31 11:04:24 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
return new Ast\Type\UnionTypeNode($types);
|
2022-03-31 11:04:24 +00:00
|
|
|
}
|
|
|
|
/** @phpstan-impure */
|
2022-06-07 08:22:29 +00:00
|
|
|
private function parseIntersection(\PHPStan\PhpDocParser\Parser\TokenIterator $tokens, Ast\Type\TypeNode $type) : Ast\Type\TypeNode
|
2021-05-10 00:23:30 +00:00
|
|
|
{
|
|
|
|
$types = [$type];
|
2022-06-07 08:22:29 +00:00
|
|
|
while ($tokens->tryConsumeTokenType(Lexer::TOKEN_INTERSECTION)) {
|
2021-05-10 00:23:30 +00:00
|
|
|
$types[] = $this->parseAtomic($tokens);
|
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
return new Ast\Type\IntersectionTypeNode($types);
|
2021-05-10 00:23:30 +00:00
|
|
|
}
|
2021-07-05 23:06:30 +00:00
|
|
|
/** @phpstan-impure */
|
2022-06-07 08:22:29 +00:00
|
|
|
private function subParseIntersection(\PHPStan\PhpDocParser\Parser\TokenIterator $tokens, Ast\Type\TypeNode $type) : Ast\Type\TypeNode
|
2022-03-31 11:04:24 +00:00
|
|
|
{
|
|
|
|
$types = [$type];
|
2022-06-07 08:22:29 +00:00
|
|
|
while ($tokens->tryConsumeTokenType(Lexer::TOKEN_INTERSECTION)) {
|
|
|
|
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
2022-03-31 11:04:24 +00:00
|
|
|
$types[] = $this->parseAtomic($tokens);
|
2022-06-07 08:22:29 +00:00
|
|
|
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
2022-03-31 11:04:24 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
return new Ast\Type\IntersectionTypeNode($types);
|
2022-03-31 11:04:24 +00:00
|
|
|
}
|
|
|
|
/** @phpstan-impure */
|
2022-06-07 08:22:29 +00:00
|
|
|
private function parseConditional(\PHPStan\PhpDocParser\Parser\TokenIterator $tokens, Ast\Type\TypeNode $subjectType) : Ast\Type\TypeNode
|
2022-03-28 22:48:45 +00:00
|
|
|
{
|
2022-06-07 08:22:29 +00:00
|
|
|
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
|
2022-03-28 22:48:45 +00:00
|
|
|
$negated = \false;
|
|
|
|
if ($tokens->isCurrentTokenValue('not')) {
|
|
|
|
$negated = \true;
|
2022-06-07 08:22:29 +00:00
|
|
|
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
|
2022-03-28 22:48:45 +00:00
|
|
|
}
|
2022-03-31 11:04:24 +00:00
|
|
|
$targetType = $this->parse($tokens);
|
2022-06-07 08:22:29 +00:00
|
|
|
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
|
|
|
$tokens->consumeTokenType(Lexer::TOKEN_NULLABLE);
|
|
|
|
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
2022-03-31 11:04:24 +00:00
|
|
|
$ifType = $this->parse($tokens);
|
2022-06-07 08:22:29 +00:00
|
|
|
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
|
|
|
$tokens->consumeTokenType(Lexer::TOKEN_COLON);
|
|
|
|
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
2022-10-21 10:12:40 +00:00
|
|
|
$elseType = $this->subParse($tokens);
|
2022-06-07 08:22:29 +00:00
|
|
|
return new Ast\Type\ConditionalTypeNode($subjectType, $targetType, $ifType, $elseType, $negated);
|
2022-03-28 22:48:45 +00:00
|
|
|
}
|
|
|
|
/** @phpstan-impure */
|
2022-06-07 08:22:29 +00:00
|
|
|
private function parseConditionalForParameter(\PHPStan\PhpDocParser\Parser\TokenIterator $tokens, string $parameterName) : Ast\Type\TypeNode
|
2022-03-28 22:48:45 +00:00
|
|
|
{
|
2022-06-07 08:22:29 +00:00
|
|
|
$tokens->consumeTokenType(Lexer::TOKEN_VARIABLE);
|
|
|
|
$tokens->consumeTokenValue(Lexer::TOKEN_IDENTIFIER, 'is');
|
2022-03-28 22:48:45 +00:00
|
|
|
$negated = \false;
|
|
|
|
if ($tokens->isCurrentTokenValue('not')) {
|
|
|
|
$negated = \true;
|
2022-06-07 08:22:29 +00:00
|
|
|
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
|
2022-03-28 22:48:45 +00:00
|
|
|
}
|
2022-04-22 14:28:27 +00:00
|
|
|
$targetType = $this->parse($tokens);
|
2022-06-07 08:22:29 +00:00
|
|
|
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
|
|
|
$tokens->consumeTokenType(Lexer::TOKEN_NULLABLE);
|
|
|
|
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
2022-04-22 14:28:27 +00:00
|
|
|
$ifType = $this->parse($tokens);
|
2022-06-07 08:22:29 +00:00
|
|
|
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
|
|
|
$tokens->consumeTokenType(Lexer::TOKEN_COLON);
|
|
|
|
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
2022-10-21 10:12:40 +00:00
|
|
|
$elseType = $this->subParse($tokens);
|
2022-06-07 08:22:29 +00:00
|
|
|
return new Ast\Type\ConditionalTypeForParameterNode($parameterName, $targetType, $ifType, $elseType, $negated);
|
2022-03-28 22:48:45 +00:00
|
|
|
}
|
|
|
|
/** @phpstan-impure */
|
2022-06-07 08:22:29 +00:00
|
|
|
private function parseNullable(\PHPStan\PhpDocParser\Parser\TokenIterator $tokens) : Ast\Type\TypeNode
|
2021-05-10 00:23:30 +00:00
|
|
|
{
|
2022-06-07 08:22:29 +00:00
|
|
|
$tokens->consumeTokenType(Lexer::TOKEN_NULLABLE);
|
2022-06-09 12:53:21 +00:00
|
|
|
$type = $this->parseAtomic($tokens);
|
2022-06-07 08:22:29 +00:00
|
|
|
return new Ast\Type\NullableTypeNode($type);
|
2021-05-10 00:23:30 +00:00
|
|
|
}
|
2021-12-10 10:22:23 +00:00
|
|
|
/** @phpstan-impure */
|
2022-06-06 17:12:56 +00:00
|
|
|
public function isHtml(\PHPStan\PhpDocParser\Parser\TokenIterator $tokens) : bool
|
2021-05-10 00:23:30 +00:00
|
|
|
{
|
2022-06-07 08:22:29 +00:00
|
|
|
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET);
|
|
|
|
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_IDENTIFIER)) {
|
2021-05-10 00:23:30 +00:00
|
|
|
return \false;
|
|
|
|
}
|
|
|
|
$htmlTagName = $tokens->currentTokenValue();
|
|
|
|
$tokens->next();
|
2022-06-07 08:22:29 +00:00
|
|
|
if (!$tokens->tryConsumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET)) {
|
2021-05-10 00:23:30 +00:00
|
|
|
return \false;
|
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
while (!$tokens->isCurrentTokenType(Lexer::TOKEN_END)) {
|
|
|
|
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET) && strpos($tokens->currentTokenValue(), '/' . $htmlTagName . '>') !== \false) {
|
2021-05-10 00:23:30 +00:00
|
|
|
return \true;
|
|
|
|
}
|
|
|
|
$tokens->next();
|
|
|
|
}
|
|
|
|
return \false;
|
|
|
|
}
|
2021-12-10 10:22:23 +00:00
|
|
|
/** @phpstan-impure */
|
2022-06-07 08:22:29 +00:00
|
|
|
public function parseGeneric(\PHPStan\PhpDocParser\Parser\TokenIterator $tokens, Ast\Type\IdentifierTypeNode $baseType) : Ast\Type\GenericTypeNode
|
2021-05-10 00:23:30 +00:00
|
|
|
{
|
2022-06-07 08:22:29 +00:00
|
|
|
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET);
|
|
|
|
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
2022-12-13 15:54:47 +00:00
|
|
|
$genericTypes = [];
|
|
|
|
$variances = [];
|
|
|
|
[$genericTypes[], $variances[]] = $this->parseGenericTypeArgument($tokens);
|
2022-06-07 08:22:29 +00:00
|
|
|
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
|
|
|
while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) {
|
|
|
|
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
|
|
|
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET)) {
|
2021-05-10 00:23:30 +00:00
|
|
|
// trailing comma case
|
2022-12-13 15:54:47 +00:00
|
|
|
return new Ast\Type\GenericTypeNode($baseType, $genericTypes, $variances);
|
2021-05-10 00:23:30 +00:00
|
|
|
}
|
2022-12-13 15:54:47 +00:00
|
|
|
[$genericTypes[], $variances[]] = $this->parseGenericTypeArgument($tokens);
|
2022-06-07 08:22:29 +00:00
|
|
|
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
2021-05-10 00:23:30 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
|
|
|
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET);
|
2022-12-13 15:54:47 +00:00
|
|
|
return new Ast\Type\GenericTypeNode($baseType, $genericTypes, $variances);
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* @phpstan-impure
|
|
|
|
* @return array{Ast\Type\TypeNode, Ast\Type\GenericTypeNode::VARIANCE_*}
|
|
|
|
*/
|
|
|
|
public function parseGenericTypeArgument(\PHPStan\PhpDocParser\Parser\TokenIterator $tokens) : array
|
|
|
|
{
|
|
|
|
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_WILDCARD)) {
|
|
|
|
return [new Ast\Type\IdentifierTypeNode('mixed'), Ast\Type\GenericTypeNode::VARIANCE_BIVARIANT];
|
|
|
|
}
|
|
|
|
if ($tokens->tryConsumeTokenValue('contravariant')) {
|
|
|
|
$variance = Ast\Type\GenericTypeNode::VARIANCE_CONTRAVARIANT;
|
|
|
|
} elseif ($tokens->tryConsumeTokenValue('covariant')) {
|
|
|
|
$variance = Ast\Type\GenericTypeNode::VARIANCE_COVARIANT;
|
|
|
|
} else {
|
|
|
|
$variance = Ast\Type\GenericTypeNode::VARIANCE_INVARIANT;
|
|
|
|
}
|
|
|
|
$type = $this->parse($tokens);
|
|
|
|
return [$type, $variance];
|
2021-05-10 00:23:30 +00:00
|
|
|
}
|
2021-07-05 23:06:30 +00:00
|
|
|
/** @phpstan-impure */
|
2022-06-07 08:22:29 +00:00
|
|
|
private function parseCallable(\PHPStan\PhpDocParser\Parser\TokenIterator $tokens, Ast\Type\IdentifierTypeNode $identifier) : Ast\Type\TypeNode
|
2021-05-10 00:23:30 +00:00
|
|
|
{
|
2022-06-07 08:22:29 +00:00
|
|
|
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES);
|
|
|
|
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
2021-05-10 00:23:30 +00:00
|
|
|
$parameters = [];
|
2022-06-07 08:22:29 +00:00
|
|
|
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PARENTHESES)) {
|
2021-05-10 00:23:30 +00:00
|
|
|
$parameters[] = $this->parseCallableParameter($tokens);
|
2022-06-07 08:22:29 +00:00
|
|
|
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
|
|
|
while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) {
|
|
|
|
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
|
|
|
if ($tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PARENTHESES)) {
|
2022-03-28 09:17:08 +00:00
|
|
|
break;
|
|
|
|
}
|
2021-05-10 00:23:30 +00:00
|
|
|
$parameters[] = $this->parseCallableParameter($tokens);
|
2022-06-07 08:22:29 +00:00
|
|
|
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
2021-05-10 00:23:30 +00:00
|
|
|
}
|
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES);
|
|
|
|
$tokens->consumeTokenType(Lexer::TOKEN_COLON);
|
2021-05-10 00:23:30 +00:00
|
|
|
$returnType = $this->parseCallableReturnType($tokens);
|
2022-06-07 08:22:29 +00:00
|
|
|
return new Ast\Type\CallableTypeNode($identifier, $parameters, $returnType);
|
2021-05-10 00:23:30 +00:00
|
|
|
}
|
2021-07-05 23:06:30 +00:00
|
|
|
/** @phpstan-impure */
|
2022-06-07 08:22:29 +00:00
|
|
|
private function parseCallableParameter(\PHPStan\PhpDocParser\Parser\TokenIterator $tokens) : Ast\Type\CallableTypeParameterNode
|
2021-05-10 00:23:30 +00:00
|
|
|
{
|
|
|
|
$type = $this->parse($tokens);
|
2022-06-07 08:22:29 +00:00
|
|
|
$isReference = $tokens->tryConsumeTokenType(Lexer::TOKEN_REFERENCE);
|
|
|
|
$isVariadic = $tokens->tryConsumeTokenType(Lexer::TOKEN_VARIADIC);
|
|
|
|
if ($tokens->isCurrentTokenType(Lexer::TOKEN_VARIABLE)) {
|
2021-05-10 00:23:30 +00:00
|
|
|
$parameterName = $tokens->currentTokenValue();
|
2022-06-07 08:22:29 +00:00
|
|
|
$tokens->consumeTokenType(Lexer::TOKEN_VARIABLE);
|
2021-05-10 00:23:30 +00:00
|
|
|
} else {
|
|
|
|
$parameterName = '';
|
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
$isOptional = $tokens->tryConsumeTokenType(Lexer::TOKEN_EQUAL);
|
|
|
|
return new Ast\Type\CallableTypeParameterNode($type, $isReference, $isVariadic, $parameterName, $isOptional);
|
2021-05-10 00:23:30 +00:00
|
|
|
}
|
2021-07-05 23:06:30 +00:00
|
|
|
/** @phpstan-impure */
|
2022-06-07 08:22:29 +00:00
|
|
|
private function parseCallableReturnType(\PHPStan\PhpDocParser\Parser\TokenIterator $tokens) : Ast\Type\TypeNode
|
2021-05-10 00:23:30 +00:00
|
|
|
{
|
2022-06-07 08:22:29 +00:00
|
|
|
if ($tokens->isCurrentTokenType(Lexer::TOKEN_NULLABLE)) {
|
2021-05-10 00:23:30 +00:00
|
|
|
$type = $this->parseNullable($tokens);
|
2022-06-07 08:22:29 +00:00
|
|
|
} elseif ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) {
|
2021-05-10 00:23:30 +00:00
|
|
|
$type = $this->parse($tokens);
|
2022-06-07 08:22:29 +00:00
|
|
|
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES);
|
2021-05-10 00:23:30 +00:00
|
|
|
} else {
|
2022-06-07 08:22:29 +00:00
|
|
|
$type = new Ast\Type\IdentifierTypeNode($tokens->currentTokenValue());
|
|
|
|
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
|
|
|
|
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)) {
|
2021-05-10 00:23:30 +00:00
|
|
|
$type = $this->parseGeneric($tokens, $type);
|
2022-06-07 08:22:29 +00:00
|
|
|
} elseif ($type->name === 'array' && $tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET) && !$tokens->isPrecededByHorizontalWhitespace()) {
|
2021-05-10 00:23:30 +00:00
|
|
|
$type = $this->parseArrayShape($tokens, $type);
|
|
|
|
}
|
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
|
2022-05-05 13:58:33 +00:00
|
|
|
$type = $this->tryParseArrayOrOffsetAccess($tokens, $type);
|
2021-05-10 00:23:30 +00:00
|
|
|
}
|
|
|
|
return $type;
|
|
|
|
}
|
2021-07-05 23:06:30 +00:00
|
|
|
/** @phpstan-impure */
|
2022-06-07 08:22:29 +00:00
|
|
|
private function tryParseCallable(\PHPStan\PhpDocParser\Parser\TokenIterator $tokens, Ast\Type\IdentifierTypeNode $identifier) : Ast\Type\TypeNode
|
2021-05-10 00:23:30 +00:00
|
|
|
{
|
|
|
|
try {
|
|
|
|
$tokens->pushSavePoint();
|
|
|
|
$type = $this->parseCallable($tokens, $identifier);
|
|
|
|
$tokens->dropSavePoint();
|
2022-06-06 17:12:56 +00:00
|
|
|
} catch (\PHPStan\PhpDocParser\Parser\ParserException $e) {
|
2021-05-10 00:23:30 +00:00
|
|
|
$tokens->rollback();
|
|
|
|
$type = $identifier;
|
|
|
|
}
|
|
|
|
return $type;
|
|
|
|
}
|
2021-07-05 23:06:30 +00:00
|
|
|
/** @phpstan-impure */
|
2022-06-07 08:22:29 +00:00
|
|
|
private function tryParseArrayOrOffsetAccess(\PHPStan\PhpDocParser\Parser\TokenIterator $tokens, Ast\Type\TypeNode $type) : Ast\Type\TypeNode
|
2021-05-10 00:23:30 +00:00
|
|
|
{
|
|
|
|
try {
|
2022-06-07 08:22:29 +00:00
|
|
|
while ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
|
2021-05-10 00:23:30 +00:00
|
|
|
$tokens->pushSavePoint();
|
2022-05-05 13:58:33 +00:00
|
|
|
$canBeOffsetAccessType = !$tokens->isPrecededByHorizontalWhitespace();
|
2022-06-07 08:22:29 +00:00
|
|
|
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET);
|
|
|
|
if ($canBeOffsetAccessType && !$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_SQUARE_BRACKET)) {
|
2022-05-05 13:58:33 +00:00
|
|
|
$offset = $this->parse($tokens);
|
2022-06-07 08:22:29 +00:00
|
|
|
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_SQUARE_BRACKET);
|
2022-05-05 13:58:33 +00:00
|
|
|
$tokens->dropSavePoint();
|
2022-06-07 08:22:29 +00:00
|
|
|
$type = new Ast\Type\OffsetAccessTypeNode($type, $offset);
|
2022-05-05 13:58:33 +00:00
|
|
|
} else {
|
2022-06-07 08:22:29 +00:00
|
|
|
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_SQUARE_BRACKET);
|
2022-05-05 13:58:33 +00:00
|
|
|
$tokens->dropSavePoint();
|
2022-06-07 08:22:29 +00:00
|
|
|
$type = new Ast\Type\ArrayTypeNode($type);
|
2022-05-05 13:58:33 +00:00
|
|
|
}
|
2021-05-10 00:23:30 +00:00
|
|
|
}
|
2022-06-06 17:12:56 +00:00
|
|
|
} catch (\PHPStan\PhpDocParser\Parser\ParserException $e) {
|
2021-05-10 00:23:30 +00:00
|
|
|
$tokens->rollback();
|
|
|
|
}
|
|
|
|
return $type;
|
|
|
|
}
|
2021-07-05 23:06:30 +00:00
|
|
|
/** @phpstan-impure */
|
2022-06-07 08:22:29 +00:00
|
|
|
private function parseArrayShape(\PHPStan\PhpDocParser\Parser\TokenIterator $tokens, Ast\Type\TypeNode $type) : Ast\Type\ArrayShapeNode
|
2021-05-10 00:23:30 +00:00
|
|
|
{
|
2022-06-07 08:22:29 +00:00
|
|
|
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET);
|
|
|
|
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET)) {
|
|
|
|
return new Ast\Type\ArrayShapeNode([]);
|
2021-06-11 17:53:41 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
2021-05-10 00:23:30 +00:00
|
|
|
$items = [$this->parseArrayShapeItem($tokens)];
|
2022-06-07 08:22:29 +00:00
|
|
|
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
|
|
|
while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) {
|
|
|
|
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
|
|
|
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET)) {
|
2021-05-10 00:23:30 +00:00
|
|
|
// trailing comma case
|
2022-06-07 08:22:29 +00:00
|
|
|
return new Ast\Type\ArrayShapeNode($items);
|
2021-05-10 00:23:30 +00:00
|
|
|
}
|
|
|
|
$items[] = $this->parseArrayShapeItem($tokens);
|
2022-06-07 08:22:29 +00:00
|
|
|
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
2021-05-10 00:23:30 +00:00
|
|
|
}
|
2022-06-07 08:22:29 +00:00
|
|
|
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
|
|
|
|
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET);
|
|
|
|
return new Ast\Type\ArrayShapeNode($items);
|
2021-05-10 00:23:30 +00:00
|
|
|
}
|
2021-07-05 23:06:30 +00:00
|
|
|
/** @phpstan-impure */
|
2022-06-07 08:22:29 +00:00
|
|
|
private function parseArrayShapeItem(\PHPStan\PhpDocParser\Parser\TokenIterator $tokens) : Ast\Type\ArrayShapeItemNode
|
2021-05-10 00:23:30 +00:00
|
|
|
{
|
|
|
|
try {
|
|
|
|
$tokens->pushSavePoint();
|
|
|
|
$key = $this->parseArrayShapeKey($tokens);
|
2022-06-07 08:22:29 +00:00
|
|
|
$optional = $tokens->tryConsumeTokenType(Lexer::TOKEN_NULLABLE);
|
|
|
|
$tokens->consumeTokenType(Lexer::TOKEN_COLON);
|
2021-05-10 00:23:30 +00:00
|
|
|
$value = $this->parse($tokens);
|
|
|
|
$tokens->dropSavePoint();
|
2022-06-07 08:22:29 +00:00
|
|
|
return new Ast\Type\ArrayShapeItemNode($key, $optional, $value);
|
2022-06-06 17:12:56 +00:00
|
|
|
} catch (\PHPStan\PhpDocParser\Parser\ParserException $e) {
|
2021-05-10 00:23:30 +00:00
|
|
|
$tokens->rollback();
|
|
|
|
$value = $this->parse($tokens);
|
2022-06-07 08:22:29 +00:00
|
|
|
return new Ast\Type\ArrayShapeItemNode(null, \false, $value);
|
2021-05-10 00:23:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* @phpstan-impure
|
|
|
|
* @return Ast\ConstExpr\ConstExprIntegerNode|Ast\ConstExpr\ConstExprStringNode|Ast\Type\IdentifierTypeNode
|
|
|
|
*/
|
2022-06-06 17:12:56 +00:00
|
|
|
private function parseArrayShapeKey(\PHPStan\PhpDocParser\Parser\TokenIterator $tokens)
|
2021-05-10 00:23:30 +00:00
|
|
|
{
|
2022-06-07 08:22:29 +00:00
|
|
|
if ($tokens->isCurrentTokenType(Lexer::TOKEN_INTEGER)) {
|
|
|
|
$key = new Ast\ConstExpr\ConstExprIntegerNode($tokens->currentTokenValue());
|
2021-05-10 00:23:30 +00:00
|
|
|
$tokens->next();
|
2022-06-07 08:22:29 +00:00
|
|
|
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_SINGLE_QUOTED_STRING)) {
|
|
|
|
$key = new Ast\ConstExpr\ConstExprStringNode(trim($tokens->currentTokenValue(), "'"));
|
2021-05-10 00:23:30 +00:00
|
|
|
$tokens->next();
|
2022-06-07 08:22:29 +00:00
|
|
|
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_QUOTED_STRING)) {
|
|
|
|
$key = new Ast\ConstExpr\ConstExprStringNode(trim($tokens->currentTokenValue(), '"'));
|
2021-05-10 00:23:30 +00:00
|
|
|
$tokens->next();
|
|
|
|
} else {
|
2022-06-07 08:22:29 +00:00
|
|
|
$key = new Ast\Type\IdentifierTypeNode($tokens->currentTokenValue());
|
|
|
|
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
|
2021-05-10 00:23:30 +00:00
|
|
|
}
|
|
|
|
return $key;
|
|
|
|
}
|
|
|
|
}
|