-
Notifications
You must be signed in to change notification settings - Fork 64
Add callable template support #199
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
<?php declare(strict_types = 1); | ||
|
||
namespace PHPStan\PhpDocParser\Ast\Type; | ||
|
||
use PHPStan\PhpDocParser\Ast\Node; | ||
use PHPStan\PhpDocParser\Ast\NodeAttributes; | ||
|
||
class CallableTypeTemplateNode implements Node | ||
{ | ||
|
||
use NodeAttributes; | ||
|
||
/** @var IdentifierTypeNode */ | ||
public $identifier; | ||
|
||
/** @var TypeNode|null */ | ||
public $bound; | ||
|
||
public function __construct(IdentifierTypeNode $identifier, ?TypeNode $bound) | ||
{ | ||
$this->identifier = $identifier; | ||
$this->bound = $bound; | ||
} | ||
|
||
public function __toString(): string | ||
{ | ||
$res = (string) $this->identifier; | ||
if ($this->bound !== null) { | ||
$res .= ' of ' . $this->bound; | ||
} | ||
|
||
return $res; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -162,13 +162,17 @@ private function parseAtomic(TokenIterator $tokens): Ast\Type\TypeNode | |
return $type; | ||
} | ||
|
||
$type = $this->parseGeneric($tokens, $type); | ||
$origType = $type; | ||
$type = $this->tryParseCallable($tokens, $type, true); | ||
if ($type === $origType) { | ||
$type = $this->parseGeneric($tokens, $type); | ||
|
||
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) { | ||
$type = $this->tryParseArrayOrOffsetAccess($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); | ||
$type = $this->tryParseCallable($tokens, $type, false); | ||
|
||
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) { | ||
$type = $this->tryParseArrayOrOffsetAccess($tokens, $type); | ||
|
@@ -458,8 +462,12 @@ public function parseGenericTypeArgument(TokenIterator $tokens): array | |
|
||
|
||
/** @phpstan-impure */ | ||
private function parseCallable(TokenIterator $tokens, Ast\Type\IdentifierTypeNode $identifier): Ast\Type\TypeNode | ||
private function parseCallable(TokenIterator $tokens, Ast\Type\IdentifierTypeNode $identifier, bool $hasTemplate): Ast\Type\TypeNode | ||
{ | ||
$templates = $hasTemplate | ||
? $this->parseCallableTemplates($tokens) | ||
: []; | ||
|
||
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES); | ||
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); | ||
|
||
|
@@ -484,7 +492,65 @@ private function parseCallable(TokenIterator $tokens, Ast\Type\IdentifierTypeNod | |
$startIndex = $tokens->currentTokenIndex(); | ||
$returnType = $this->enrichWithAttributes($tokens, $this->parseCallableReturnType($tokens), $startLine, $startIndex); | ||
|
||
return new Ast\Type\CallableTypeNode($identifier, $parameters, $returnType); | ||
return new Ast\Type\CallableTypeNode($identifier, $parameters, $returnType, $templates); | ||
} | ||
|
||
|
||
/** | ||
* @return Ast\Type\CallableTypeTemplateNode[] | ||
* | ||
* @phpstan-impure | ||
*/ | ||
private function parseCallableTemplates(TokenIterator $tokens): array | ||
{ | ||
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET); | ||
|
||
$templates = []; | ||
|
||
$isFirst = true; | ||
while ($isFirst || $tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) { | ||
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); | ||
|
||
// trailing comma case | ||
if (!$isFirst && $tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand why is there so much repeated code. We're already parsing template types in similar position for Everything you should need is already in parseTemplateTagValue. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you please guide me how and what to reuse? The code in #160 (Parser/PhpDocParser in general) is for phpdoc instead of for type (Parser/TypeParser). If the newly introduced Ast/Type/CallableTypeTemplateNode class is wanted (and it probably is - callable template should not consist of anything like description), then the code cannot be simplified much. As the grammar is not completely the same, basically the only parsing part to deduplicate would be If you want, feel free to push any changes to my branch. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think #160 implements the very same use case as this PR: it parses inline template tag definitions, disallowing descriptions, but accounting for everything else (bound types, defaults, ...). Perhaps it could suffice to extract the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that logic should be extracted so that it's usable by both TypeParser and PhpDocParser. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mvorisek are you going to look into this feedback or should i have a go at it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mad-briller Please have a go at it :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mad-briller would you like to join the forces? Feel free to either reuse this PR as a separate one or submit a PR againts https://github.com/mvorisek/phpdoc-parser/tree/parse_generic_callable - this PR should be basically done, but the code should be deduplicated per #199 (comment) request. |
||
break; | ||
} | ||
$isFirst = false; | ||
|
||
$templates[] = $this->parseCallableTemplateArgument($tokens); | ||
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL); | ||
} | ||
|
||
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET); | ||
|
||
return $templates; | ||
} | ||
|
||
|
||
private function parseCallableTemplateArgument(TokenIterator $tokens): Ast\Type\CallableTypeTemplateNode | ||
{ | ||
$startLine = $tokens->currentTokenLine(); | ||
$startIndex = $tokens->currentTokenIndex(); | ||
|
||
$identifier = $this->enrichWithAttributes( | ||
$tokens, | ||
new Ast\Type\IdentifierTypeNode($tokens->currentTokenValue()), | ||
$startLine, | ||
$startIndex | ||
); | ||
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER); | ||
|
||
$bound = null; | ||
if ($tokens->tryConsumeTokenValue('of')) { | ||
mvorisek marked this conversation as resolved.
Show resolved
Hide resolved
|
||
$bound = $this->parse($tokens); | ||
} | ||
|
||
return $this->enrichWithAttributes( | ||
$tokens, | ||
new Ast\Type\CallableTypeTemplateNode($identifier, $bound), | ||
$startLine, | ||
$startIndex | ||
); | ||
} | ||
|
||
|
||
|
@@ -662,11 +728,11 @@ private function parseCallableReturnType(TokenIterator $tokens): Ast\Type\TypeNo | |
|
||
|
||
/** @phpstan-impure */ | ||
private function tryParseCallable(TokenIterator $tokens, Ast\Type\IdentifierTypeNode $identifier): Ast\Type\TypeNode | ||
private function tryParseCallable(TokenIterator $tokens, Ast\Type\IdentifierTypeNode $identifier, bool $hasTemplate): Ast\Type\TypeNode | ||
{ | ||
try { | ||
$tokens->pushSavePoint(); | ||
$type = $this->parseCallable($tokens, $identifier); | ||
$type = $this->parseCallable($tokens, $identifier, $hasTemplate); | ||
$tokens->dropSavePoint(); | ||
|
||
} catch (ParserException $e) { | ||
|
Uh oh!
There was an error while loading. Please reload this page.