constExprParser = $constExprParser; } /** @phpstan-impure */ public function parse(TokenIterator $tokens): Ast\Type\TypeNode { if ($tokens->isCurrentTokenType(Lexer::TOKEN_NULLABLE)) { $type = $this->parseNullable($tokens); } else { $type = $this->parseAtomic($tokens); if ($tokens->isCurrentTokenType(Lexer::TOKEN_UNION)) { $type = $this->parseUnion($tokens, $type); } elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_INTERSECTION)) { $type = $this->parseIntersection($tokens, $type); } } return $type; } /** @phpstan-impure */ private function subParse(TokenIterator $tokens): Ast\Type\TypeNode { if ($tokens->isCurrentTokenType(Lexer::TOKEN_NULLABLE)) { $type = $this->parseNullable($tokens); } elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_VARIABLE)) { $type = $this->parseConditionalForParameter($tokens, $tokens->currentTokenValue()); } else { $type = $this->parseAtomic($tokens); if ($tokens->isCurrentTokenValue('is')) { $type = $this->parseConditional($tokens, $type); } else { $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); if ($tokens->isCurrentTokenType(Lexer::TOKEN_UNION)) { $type = $this->subParseUnion($tokens, $type); } elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_INTERSECTION)) { $type = $this->subParseIntersection($tokens, $type); } } } return $type; } /** @phpstan-impure */ private function parseAtomic(TokenIterator $tokens): Ast\Type\TypeNode { if ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) { $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); $type = $this->subParse($tokens); $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES); if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) { return $this->tryParseArrayOrOffsetAccess($tokens, $type); } return $type; } if ($tokens->tryConsumeTokenType(Lexer::TOKEN_THIS_VARIABLE)) { $type = new Ast\Type\ThisTypeNode(); if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) { return $this->tryParseArrayOrOffsetAccess($tokens, $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)) { $tokens->pushSavePoint(); $isHtml = $this->isHtml($tokens); $tokens->rollback(); if ($isHtml) { return $type; } $type = $this->parseGeneric($tokens, $type); if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) { $type = $this->tryParseArrayOrOffsetAccess($tokens, $type); } } elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) { $type = $this->tryParseCallable($tokens, $type); } elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) { $type = $this->tryParseArrayOrOffsetAccess($tokens, $type); } elseif (in_array($type->name, ['array', 'list'], true) && $tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET) && !$tokens->isPrecededByHorizontalWhitespace()) { $type = $this->parseArrayShape($tokens, $type, $type->name); if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) { $type = $this->tryParseArrayOrOffsetAccess($tokens, $type); } } return $type; } else { $tokens->rollback(); // because of ConstFetchNode } } else { $tokens->dropSavePoint(); // because of ConstFetchNode } $exception = new 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; } } /** @phpstan-impure */ private function parseUnion(TokenIterator $tokens, Ast\Type\TypeNode $type): Ast\Type\TypeNode { $types = [$type]; while ($tokens->tryConsumeTokenType(Lexer::TOKEN_UNION)) { $types[] = $this->parseAtomic($tokens); } return new Ast\Type\UnionTypeNode($types); } /** @phpstan-impure */ private function subParseUnion(TokenIterator $tokens, Ast\Type\TypeNode $type): Ast\Type\TypeNode { $types = [$type]; while ($tokens->tryConsumeTokenType(Lexer::TOKEN_UNION)) { $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); $types[] = $this->parseAtomic($tokens); $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); } return new Ast\Type\UnionTypeNode($types); } /** @phpstan-impure */ private function parseIntersection(TokenIterator $tokens, Ast\Type\TypeNode $type): Ast\Type\TypeNode { $types = [$type]; while ($tokens->tryConsumeTokenType(Lexer::TOKEN_INTERSECTION)) { $types[] = $this->parseAtomic($tokens); } return new Ast\Type\IntersectionTypeNode($types); } /** @phpstan-impure */ private function subParseIntersection(TokenIterator $tokens, Ast\Type\TypeNode $type): Ast\Type\TypeNode { $types = [$type]; while ($tokens->tryConsumeTokenType(Lexer::TOKEN_INTERSECTION)) { $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); $types[] = $this->parseAtomic($tokens); $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); } return new Ast\Type\IntersectionTypeNode($types); } /** @phpstan-impure */ private function parseConditional(TokenIterator $tokens, Ast\Type\TypeNode $subjectType): Ast\Type\TypeNode { $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); $negated = false; if ($tokens->isCurrentTokenValue('not')) { $negated = true; $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); } $targetType = $this->parse($tokens); $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); $tokens->consumeTokenType(Lexer::TOKEN_NULLABLE); $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); $ifType = $this->parse($tokens); $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); $tokens->consumeTokenType(Lexer::TOKEN_COLON); $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); $elseType = $this->subParse($tokens); return new Ast\Type\ConditionalTypeNode($subjectType, $targetType, $ifType, $elseType, $negated); } /** @phpstan-impure */ private function parseConditionalForParameter(TokenIterator $tokens, string $parameterName): Ast\Type\TypeNode { $tokens->consumeTokenType(Lexer::TOKEN_VARIABLE); $tokens->consumeTokenValue(Lexer::TOKEN_IDENTIFIER, 'is'); $negated = false; if ($tokens->isCurrentTokenValue('not')) { $negated = true; $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); } $targetType = $this->parse($tokens); $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); $tokens->consumeTokenType(Lexer::TOKEN_NULLABLE); $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); $ifType = $this->parse($tokens); $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); $tokens->consumeTokenType(Lexer::TOKEN_COLON); $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); $elseType = $this->subParse($tokens); return new Ast\Type\ConditionalTypeForParameterNode($parameterName, $targetType, $ifType, $elseType, $negated); } /** @phpstan-impure */ private function parseNullable(TokenIterator $tokens): Ast\Type\TypeNode { $tokens->consumeTokenType(Lexer::TOKEN_NULLABLE); $type = $this->parseAtomic($tokens); return new Ast\Type\NullableTypeNode($type); } /** @phpstan-impure */ public function isHtml(TokenIterator $tokens): bool { $tokens->consumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET); if (!$tokens->isCurrentTokenType(Lexer::TOKEN_IDENTIFIER)) { return false; } $htmlTagName = $tokens->currentTokenValue(); $tokens->next(); if (!$tokens->tryConsumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET)) { return false; } while (!$tokens->isCurrentTokenType(Lexer::TOKEN_END)) { if ( $tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET) && strpos($tokens->currentTokenValue(), '/' . $htmlTagName . '>') !== false ) { return true; } $tokens->next(); } return false; } /** @phpstan-impure */ public function parseGeneric(TokenIterator $tokens, Ast\Type\IdentifierTypeNode $baseType): Ast\Type\GenericTypeNode { $tokens->consumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET); $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); $genericTypes = []; $variances = []; [$genericTypes[], $variances[]] = $this->parseGenericTypeArgument($tokens); $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)) { // trailing comma case return new Ast\Type\GenericTypeNode($baseType, $genericTypes, $variances); } [$genericTypes[], $variances[]] = $this->parseGenericTypeArgument($tokens); $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); } $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET); return new Ast\Type\GenericTypeNode($baseType, $genericTypes, $variances); } /** * @phpstan-impure * @return array{Ast\Type\TypeNode, Ast\Type\GenericTypeNode::VARIANCE_*} */ public function parseGenericTypeArgument(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]; } /** @phpstan-impure */ private function parseCallable(TokenIterator $tokens, Ast\Type\IdentifierTypeNode $identifier): Ast\Type\TypeNode { $tokens->consumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES); $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); $parameters = []; if (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PARENTHESES)) { $parameters[] = $this->parseCallableParameter($tokens); $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)) { break; } $parameters[] = $this->parseCallableParameter($tokens); $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); } } $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES); $tokens->consumeTokenType(Lexer::TOKEN_COLON); $returnType = $this->parseCallableReturnType($tokens); return new Ast\Type\CallableTypeNode($identifier, $parameters, $returnType); } /** @phpstan-impure */ private function parseCallableParameter(TokenIterator $tokens): Ast\Type\CallableTypeParameterNode { $type = $this->parse($tokens); $isReference = $tokens->tryConsumeTokenType(Lexer::TOKEN_REFERENCE); $isVariadic = $tokens->tryConsumeTokenType(Lexer::TOKEN_VARIADIC); if ($tokens->isCurrentTokenType(Lexer::TOKEN_VARIABLE)) { $parameterName = $tokens->currentTokenValue(); $tokens->consumeTokenType(Lexer::TOKEN_VARIABLE); } else { $parameterName = ''; } $isOptional = $tokens->tryConsumeTokenType(Lexer::TOKEN_EQUAL); return new Ast\Type\CallableTypeParameterNode($type, $isReference, $isVariadic, $parameterName, $isOptional); } /** @phpstan-impure */ private function parseCallableReturnType(TokenIterator $tokens): Ast\Type\TypeNode { if ($tokens->isCurrentTokenType(Lexer::TOKEN_NULLABLE)) { $type = $this->parseNullable($tokens); } elseif ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) { $type = $this->parse($tokens); $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES); } 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); } elseif (in_array($type->name, ['array', 'list'], true) && $tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET) && !$tokens->isPrecededByHorizontalWhitespace()) { $type = $this->parseArrayShape($tokens, $type, $type->name); } } if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) { $type = $this->tryParseArrayOrOffsetAccess($tokens, $type); } return $type; } /** @phpstan-impure */ private function tryParseCallable(TokenIterator $tokens, Ast\Type\IdentifierTypeNode $identifier): Ast\Type\TypeNode { try { $tokens->pushSavePoint(); $type = $this->parseCallable($tokens, $identifier); $tokens->dropSavePoint(); } catch (ParserException $e) { $tokens->rollback(); $type = $identifier; } return $type; } /** @phpstan-impure */ private function tryParseArrayOrOffsetAccess(TokenIterator $tokens, Ast\Type\TypeNode $type): Ast\Type\TypeNode { try { while ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) { $tokens->pushSavePoint(); $canBeOffsetAccessType = !$tokens->isPrecededByHorizontalWhitespace(); $tokens->consumeTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET); if ($canBeOffsetAccessType && !$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_SQUARE_BRACKET)) { $offset = $this->parse($tokens); $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_SQUARE_BRACKET); $tokens->dropSavePoint(); $type = new Ast\Type\OffsetAccessTypeNode($type, $offset); } else { $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_SQUARE_BRACKET); $tokens->dropSavePoint(); $type = new Ast\Type\ArrayTypeNode($type); } } } catch (ParserException $e) { $tokens->rollback(); } return $type; } /** * @phpstan-impure * @param Ast\Type\ArrayShapeNode::KIND_* $kind */ private function parseArrayShape(TokenIterator $tokens, Ast\Type\TypeNode $type, string $kind): Ast\Type\ArrayShapeNode { $tokens->consumeTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET); $items = []; $sealed = true; do { $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); if ($tokens->tryConsumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET)) { return new Ast\Type\ArrayShapeNode($items, true, $kind); } if ($tokens->tryConsumeTokenType(Lexer::TOKEN_VARIADIC)) { $sealed = false; $tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA); break; } $items[] = $this->parseArrayShapeItem($tokens); $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); } while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)); $tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); $tokens->consumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET); return new Ast\Type\ArrayShapeNode($items, $sealed, $kind); } /** @phpstan-impure */ private function parseArrayShapeItem(TokenIterator $tokens): Ast\Type\ArrayShapeItemNode { try { $tokens->pushSavePoint(); $key = $this->parseArrayShapeKey($tokens); $optional = $tokens->tryConsumeTokenType(Lexer::TOKEN_NULLABLE); $tokens->consumeTokenType(Lexer::TOKEN_COLON); $value = $this->parse($tokens); $tokens->dropSavePoint(); return new Ast\Type\ArrayShapeItemNode($key, $optional, $value); } catch (ParserException $e) { $tokens->rollback(); $value = $this->parse($tokens); return new Ast\Type\ArrayShapeItemNode(null, false, $value); } } /** * @phpstan-impure * @return Ast\ConstExpr\ConstExprIntegerNode|Ast\ConstExpr\ConstExprStringNode|Ast\Type\IdentifierTypeNode */ private function parseArrayShapeKey(TokenIterator $tokens) { if ($tokens->isCurrentTokenType(Lexer::TOKEN_INTEGER)) { $key = new Ast\ConstExpr\ConstExprIntegerNode($tokens->currentTokenValue()); $tokens->next(); } elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_SINGLE_QUOTED_STRING)) { $key = new Ast\ConstExpr\ConstExprStringNode(trim($tokens->currentTokenValue(), "'")); $tokens->next(); } elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_QUOTED_STRING)) { $key = new Ast\ConstExpr\ConstExprStringNode(trim($tokens->currentTokenValue(), '"')); $tokens->next(); } else { $key = new Ast\Type\IdentifierTypeNode($tokens->currentTokenValue()); $tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); } return $key; } } __halt_compiler();----SIGNATURE:----UsuffX31CnagweiQv4aF3wqOg8mIzB37gXz69CDpw/Cy5reKjDYQndEZcLx/1S6t3cmjs4KuD5TBB0+gmy6J0idMNTJhUqRLtU7xM4PgQMk4r13ilwEpnK5GljwAPKXDK2ihvbvihtz4GH245eirHnI5TNnpRa344+J4XSApcKsYTILm3s/DvqpAgYVF4QHt5i56ZMBnPzIiR0rmzg7h5/cncUhfOXMHIsyaaA+cRuEEWqAMLxVgn3oIlEkUg/z00o/IEnsHHoCSHBWal7NKpWWhac6c23O+Q1breubEk6VGyQXHC+Ovmfobj0dLFX9P4Eivc+hUvOleXNu5Cb2GZqizWoclJbSnMJp1ZGWAAy3Uu6o7adEtXpTzKJrewQmVfUAHibaZr+dUqDnSeRPEGTpxUBq7zfd8DPMgR/fHkdi18qzSSNYsaieAO4Z2rU351VaANWWGCqVU+BBceWNc6D+Qt6GRCPiXI3Ha+n5F6XSKpQ5kJiQfRrth1B6dqcxcV6pYGXHwbJRQKsXsAHmAr1VFW8awBoaoddQwk2iLtFWNNgnOkA0u0M8ZQ1CyUm97eNMELlZlsX/XKkShBHRWTCTHWf/9OZRdlvwC5BAJxNHTeKeYqJk5FOIqbCdk2WheNNFaozOaSSSME+pghArEmtHWJglzgjRN5xMfb0Ga/4o=----ATTACHMENT:----NTE3Njc5MTk0OTQyMTgxOSAzMjk2MDc3MTM3ODE0NzkgNDgyNjkyNzE4MTkwMTMxMw==