Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 2a4686e

Browse files
authored
Add generics support to @method definitions
1 parent c7c2609 commit 2a4686e

File tree

3 files changed

+157
-7
lines changed

3 files changed

+157
-7
lines changed

‎src/Ast/PhpDoc/MethodTagValueNode.php‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use PHPStan\PhpDocParser\Ast\NodeAttributes;
66
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
7+
use function count;
78
use function implode;
89

910
class MethodTagValueNode implements PhpDocTagValueNode
@@ -20,19 +21,23 @@ class MethodTagValueNode implements PhpDocTagValueNode
2021
/** @var string */
2122
public $methodName;
2223

24+
/** @var TemplateTagValueNode[] */
25+
public $templateTypes;
26+
2327
/** @var MethodTagValueParameterNode[] */
2428
public $parameters;
2529

2630
/** @var string (may be empty) */
2731
public $description;
2832

29-
public function __construct(bool $isStatic, ?TypeNode $returnType, string $methodName, array $parameters, string $description)
33+
public function __construct(bool $isStatic, ?TypeNode $returnType, string $methodName, array $parameters, string $description, array$templateTypes = [])
3034
{
3135
$this->isStatic = $isStatic;
3236
$this->returnType = $returnType;
3337
$this->methodName = $methodName;
3438
$this->parameters = $parameters;
3539
$this->description = $description;
40+
$this->templateTypes = $templateTypes;
3641
}
3742

3843

@@ -42,7 +47,8 @@ public function __toString(): string
4247
$returnType = $this->returnType !== null ? "{$this->returnType}" : '';
4348
$parameters = implode(', ', $this->parameters);
4449
$description = $this->description !== '' ? "{$this->description}" : '';
45-
return "{$static}{$returnType}{$this->methodName}({$parameters}){$description}";
50+
$templateTypes = count($this->templateTypes) > 0 ? '<' . implode(', ', $this->templateTypes) . '>' : '';
51+
return "{$static}{$returnType}{$this->methodName}{$templateTypes}({$parameters}){$description}";
4652
}
4753

4854
}

‎src/Parser/PhpDocParser.php‎

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ public function parseTagValue(TokenIterator $tokens, string $tag): Ast\PhpDoc\Ph
182182
case '@template-contravariant':
183183
case '@phpstan-template-contravariant':
184184
case '@psalm-template-contravariant':
185-
$tagValue = $this->parseTemplateTagValue($tokens);
185+
$tagValue = $this->parseTemplateTagValue($tokens, true);
186186
break;
187187

188188
case '@extends':
@@ -346,6 +346,14 @@ private function parseMethodTagValue(TokenIterator $tokens): Ast\PhpDoc\MethodTa
346346
exit;
347347
}
348348

349+
$templateTypes = [];
350+
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)) {
351+
do {
352+
$templateTypes[] = $this->parseTemplateTagValue($tokens, false);
353+
} while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA));
354+
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET);
355+
}
356+
349357
$parameters = [];
350358
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES);
351359
if (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PARENTHESES)) {
@@ -357,10 +365,9 @@ private function parseMethodTagValue(TokenIterator $tokens): Ast\PhpDoc\MethodTa
357365
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES);
358366

359367
$description = $this->parseOptionalDescription($tokens);
360-
return new Ast\PhpDoc\MethodTagValueNode($isStatic, $returnType, $methodName, $parameters, $description);
368+
return new Ast\PhpDoc\MethodTagValueNode($isStatic, $returnType, $methodName, $parameters, $description, $templateTypes);
361369
}
362370

363-
364371
private function parseMethodTagValueParameter(TokenIterator $tokens): Ast\PhpDoc\MethodTagValueParameterNode
365372
{
366373
switch ($tokens->currentTokenType()) {
@@ -390,7 +397,7 @@ private function parseMethodTagValueParameter(TokenIterator $tokens): Ast\PhpDoc
390397
return new Ast\PhpDoc\MethodTagValueParameterNode($parameterType, $isReference, $isVariadic, $parameterName, $defaultValue);
391398
}
392399

393-
private function parseTemplateTagValue(TokenIterator $tokens): Ast\PhpDoc\TemplateTagValueNode
400+
private function parseTemplateTagValue(TokenIterator $tokens, bool$parseDescription): Ast\PhpDoc\TemplateTagValueNode
394401
{
395402
$name = $tokens->currentTokenValue();
396403
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
@@ -408,7 +415,11 @@ private function parseTemplateTagValue(TokenIterator $tokens): Ast\PhpDoc\Templa
408415
$default = null;
409416
}
410417

411-
$description = $this->parseOptionalDescription($tokens);
418+
if ($parseDescription) {
419+
$description = $this->parseOptionalDescription($tokens);
420+
} else {
421+
$description = '';
422+
}
412423

413424
return new Ast\PhpDoc\TemplateTagValueNode($name, $bound, $description, $default);
414425
}

‎tests/PHPStan/Parser/PhpDocParserTest.php‎

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace PHPStan\PhpDocParser\Parser;
44

55
use Iterator;
6+
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprArrayItemNode;
67
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprArrayNode;
78
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode;
89
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode;
@@ -44,6 +45,7 @@
4445
use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode;
4546
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
4647
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
48+
use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode;
4749
use PHPStan\PhpDocParser\Ast\Type\OffsetAccessTypeNode;
4850
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
4951
use PHPStan\PhpDocParser\Lexer\Lexer;
@@ -2201,6 +2203,98 @@ public function provideMethodTagsData(): Iterator
22012203
),
22022204
]),
22032205
];
2206+
2207+
yield [
2208+
'OK non-static, with return type and parameter with generic type',
2209+
'/** @method ?T randomElement<T = string>(array<array-key, T> $array = [\'a\', \'b\']) */',
2210+
new PhpDocNode([
2211+
new PhpDocTagNode(
2212+
'@method',
2213+
new MethodTagValueNode(
2214+
false,
2215+
new NullableTypeNode(new IdentifierTypeNode('T')),
2216+
'randomElement',
2217+
[
2218+
new MethodTagValueParameterNode(
2219+
new GenericTypeNode(
2220+
new IdentifierTypeNode('array'),
2221+
[
2222+
new IdentifierTypeNode('array-key'),
2223+
new IdentifierTypeNode('T'),
2224+
]
2225+
),
2226+
false,
2227+
false,
2228+
'$array',
2229+
new ConstExprArrayNode([
2230+
new ConstExprArrayItemNode(
2231+
null,
2232+
new ConstExprStringNode('\'a\'')
2233+
),
2234+
new ConstExprArrayItemNode(
2235+
null,
2236+
new ConstExprStringNode('\'b\'')
2237+
),
2238+
])
2239+
),
2240+
],
2241+
'',
2242+
[
2243+
new TemplateTagValueNode(
2244+
'T',
2245+
null,
2246+
'',
2247+
new IdentifierTypeNode('string')
2248+
),
2249+
]
2250+
)
2251+
),
2252+
]),
2253+
];
2254+
2255+
yield [
2256+
'OK static, with return type and multiple parameters with generic type',
2257+
'/** @method static bool compare<T1, T2 of Bar, T3 as Baz>(T1 $t1, T2 $t2, T3 $t3) */',
2258+
new PhpDocNode([
2259+
new PhpDocTagNode(
2260+
'@method',
2261+
new MethodTagValueNode(
2262+
true,
2263+
new IdentifierTypeNode('bool'),
2264+
'compare',
2265+
[
2266+
new MethodTagValueParameterNode(
2267+
new IdentifierTypeNode('T1'),
2268+
false,
2269+
false,
2270+
'$t1',
2271+
null
2272+
),
2273+
new MethodTagValueParameterNode(
2274+
new IdentifierTypeNode('T2'),
2275+
false,
2276+
false,
2277+
'$t2',
2278+
null
2279+
),
2280+
new MethodTagValueParameterNode(
2281+
new IdentifierTypeNode('T3'),
2282+
false,
2283+
false,
2284+
'$t3',
2285+
null
2286+
),
2287+
],
2288+
'',
2289+
[
2290+
new TemplateTagValueNode('T1', null, ''),
2291+
new TemplateTagValueNode('T2', new IdentifierTypeNode('Bar'), ''),
2292+
new TemplateTagValueNode('T3', new IdentifierTypeNode('Baz'), ''),
2293+
]
2294+
)
2295+
),
2296+
]),
2297+
];
22042298
}
22052299

22062300

@@ -3078,6 +3172,45 @@ public function provideMultiLinePhpDocData(): array
30783172
),
30793173
]),
30803174
],
3175+
[
3176+
'OK with template method',
3177+
'/**
3178+
* @template TKey as array-key
3179+
* @template TValue
3180+
* @method TKey|null find(TValue $v) find index of $v
3181+
*/',
3182+
new PhpDocNode([
3183+
new PhpDocTagNode(
3184+
'@template',
3185+
new TemplateTagValueNode('TKey', new IdentifierTypeNode('array-key'), '')
3186+
),
3187+
new PhpDocTagNode(
3188+
'@template',
3189+
new TemplateTagValueNode('TValue', null, '')
3190+
),
3191+
new PhpDocTagNode(
3192+
'@method',
3193+
new MethodTagValueNode(
3194+
false,
3195+
new UnionTypeNode([
3196+
new IdentifierTypeNode('TKey'),
3197+
new IdentifierTypeNode('null'),
3198+
]),
3199+
'find',
3200+
[
3201+
new MethodTagValueParameterNode(
3202+
new IdentifierTypeNode('TValue'),
3203+
false,
3204+
false,
3205+
'$v',
3206+
null
3207+
),
3208+
],
3209+
'find index of $v'
3210+
)
3211+
),
3212+
]),
3213+
],
30813214
[
30823215
'OK with multiline conditional return type',
30833216
'/**

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /