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 cc479c9

Browse files
Merge remote-tracking branch 'origin/1.23.x' into 2.0.x
2 parents b78c136 + 249f15f commit cc479c9

File tree

7 files changed

+109
-20
lines changed

7 files changed

+109
-20
lines changed

‎doc/grammars/type.abnf‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ CallableTemplate
4141
= TokenAngleBracketOpen CallableTemplateArgument *(TokenComma CallableTemplateArgument) TokenAngleBracketClose
4242

4343
CallableTemplateArgument
44-
= TokenIdentifier [1*ByteHorizontalWs TokenOf Type]
44+
= TokenIdentifier [1*ByteHorizontalWs TokenOf Type] [1*ByteHorizontalWsTokenSuperType] ["="Type]
4545

4646
CallableParameters
4747
= CallableParameter *(TokenComma CallableParameter)
@@ -201,6 +201,9 @@ TokenNot
201201
TokenOf
202202
= %s"of" 1*ByteHorizontalWs
203203

204+
TokenSuper
205+
= %s"super" 1*ByteHorizontalWs
206+
204207
TokenContravariant
205208
= %s"contravariant" 1*ByteHorizontalWs
206209

‎src/Ast/PhpDoc/TemplateTagValueNode.php‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,30 +14,34 @@ class TemplateTagValueNode implements PhpDocTagValueNode
1414
/** @var non-empty-string */
1515
public string $name;
1616

17-
public ?TypeNode $bound = null;
17+
public ?TypeNode $bound;
1818

19-
public ?TypeNode $default = null;
19+
public ?TypeNode $default;
20+
21+
public ?TypeNode $lowerBound;
2022

2123
/** @var string (may be empty) */
2224
public string $description;
2325

2426
/**
2527
* @param non-empty-string $name
2628
*/
27-
public function __construct(string $name, ?TypeNode $bound, string $description, ?TypeNode $default = null)
29+
public function __construct(string $name, ?TypeNode $bound, string $description, ?TypeNode $default = null, ?TypeNode$lowerBound = null)
2830
{
2931
$this->name = $name;
3032
$this->bound = $bound;
33+
$this->lowerBound = $lowerBound;
3134
$this->default = $default;
3235
$this->description = $description;
3336
}
3437

3538

3639
public function __toString(): string
3740
{
38-
$bound = $this->bound !== null ? " of {$this->bound}" : '';
41+
$upperBound = $this->bound !== null ? " of {$this->bound}" : '';
42+
$lowerBound = $this->lowerBound !== null ? " super {$this->lowerBound}" : '';
3943
$default = $this->default !== null ? " = {$this->default}" : '';
40-
return trim("{$this->name}{$bound}{$default}{$this->description}");
44+
return trim("{$this->name}{$upperBound}{$lowerBound}{$default}{$this->description}");
4145
}
4246

4347
}

‎src/Parser/TypeParser.php‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -466,11 +466,14 @@ public function parseTemplateTagValue(
466466
$name = $tokens->currentTokenValue();
467467
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
468468

469+
$upperBound = $lowerBound = null;
470+
469471
if ($tokens->tryConsumeTokenValue('of') || $tokens->tryConsumeTokenValue('as')) {
470-
$bound = $this->parse($tokens);
472+
$upperBound = $this->parse($tokens);
473+
}
471474

472-
} else {
473-
$bound = null;
475+
if ($tokens->tryConsumeTokenValue('super')) {
476+
$lowerBound = $this->parse($tokens);
474477
}
475478

476479
if ($tokens->tryConsumeTokenValue('=')) {
@@ -489,7 +492,7 @@ public function parseTemplateTagValue(
489492
throw new LogicException('Template tag name cannot be empty.');
490493
}
491494

492-
return new Ast\PhpDoc\TemplateTagValueNode($name, $bound, $description, $default);
495+
return new Ast\PhpDoc\TemplateTagValueNode($name, $upperBound, $description, $default, $lowerBound);
493496
}
494497

495498

‎src/Printer/Printer.php‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -355,9 +355,10 @@ private function printTagValue(PhpDocTagValueNode $node): string
355355
return trim($type . '' . $node->description);
356356
}
357357
if ($node instanceof TemplateTagValueNode) {
358-
$bound = $node->bound !== null ? ' of ' . $this->printType($node->bound) : '';
358+
$upperBound = $node->bound !== null ? ' of ' . $this->printType($node->bound) : '';
359+
$lowerBound = $node->lowerBound !== null ? ' super ' . $this->printType($node->lowerBound) : '';
359360
$default = $node->default !== null ? ' = ' . $this->printType($node->default) : '';
360-
return trim("{$node->name}{$bound}{$default}{$node->description}");
361+
return trim("{$node->name}{$upperBound}{$lowerBound}{$default}{$node->description}");
361362
}
362363
if ($node instanceof ThrowsTagValueNode) {
363364
$type = $this->printType($node->type);

‎tests/PHPStan/Ast/ToString/PhpDocToStringTest.php‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,12 +155,15 @@ public static function provideOtherCases(): Generator
155155
$baz = new IdentifierTypeNode('Foo\\Baz');
156156

157157
yield from [
158-
['TValue', new TemplateTagValueNode('TValue', null, '', null)],
159-
['TValue of Foo\\Bar', new TemplateTagValueNode('TValue', $bar, '', null)],
158+
['TValue', new TemplateTagValueNode('TValue', null, '')],
159+
['TValue of Foo\\Bar', new TemplateTagValueNode('TValue', $bar, '')],
160+
['TValue super Foo\\Bar', new TemplateTagValueNode('TValue', null, '', null, $bar)],
160161
['TValue = Foo\\Bar', new TemplateTagValueNode('TValue', null, '', $bar)],
161162
['TValue of Foo\\Bar = Foo\\Baz', new TemplateTagValueNode('TValue', $bar, '', $baz)],
162-
['TValue Description.', new TemplateTagValueNode('TValue', null, 'Description.', null)],
163+
['TValue Description.', new TemplateTagValueNode('TValue', null, 'Description.')],
163164
['TValue of Foo\\Bar = Foo\\Baz Description.', new TemplateTagValueNode('TValue', $bar, 'Description.', $baz)],
165+
['TValue super Foo\\Bar = Foo\\Baz Description.', new TemplateTagValueNode('TValue', null, 'Description.', $baz, $bar)],
166+
['TValue of Foo\\Bar super Foo\\Baz Description.', new TemplateTagValueNode('TValue', $bar, 'Description.', null, $baz)],
164167
];
165168
}
166169

‎tests/PHPStan/Parser/PhpDocParserTest.php‎

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4058,7 +4058,7 @@ public function provideTemplateTagsData(): Iterator
40584058
];
40594059

40604060
yield [
4061-
'OK with bound and description',
4061+
'OK with upper bound and description',
40624062
'/** @template T of DateTime the value type */',
40634063
new PhpDocNode([
40644064
new PhpDocTagNode(
@@ -4073,22 +4073,41 @@ public function provideTemplateTagsData(): Iterator
40734073
];
40744074

40754075
yield [
4076-
'OK with bound and description',
4077-
'/** @template T as DateTime the value type */',
4076+
'OK with lower bound and description',
4077+
'/** @template T super DateTimeImmutable the value type */',
40784078
new PhpDocNode([
40794079
new PhpDocTagNode(
40804080
'@template',
40814081
new TemplateTagValueNode(
40824082
'T',
4083-
new IdentifierTypeNode('DateTime'),
4083+
null,
4084+
'the value type',
4085+
null,
4086+
new IdentifierTypeNode('DateTimeImmutable'),
4087+
),
4088+
),
4089+
]),
4090+
];
4091+
4092+
yield [
4093+
'OK with both bounds and description',
4094+
'/** @template T of DateTimeInterface super DateTimeImmutable the value type */',
4095+
new PhpDocNode([
4096+
new PhpDocTagNode(
4097+
'@template',
4098+
new TemplateTagValueNode(
4099+
'T',
4100+
new IdentifierTypeNode('DateTimeInterface'),
40844101
'the value type',
4102+
null,
4103+
new IdentifierTypeNode('DateTimeImmutable'),
40854104
),
40864105
),
40874106
]),
40884107
];
40894108

40904109
yield [
4091-
'invalid without bound and description',
4110+
'invalid without bounds and description',
40924111
'/** @template */',
40934112
new PhpDocNode([
40944113
new PhpDocTagNode(

‎tests/PHPStan/Printer/PrinterTest.php‎

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -943,6 +943,12 @@ public function enterNode(Node $node)
943943
$addTemplateTagBound,
944944
];
945945

946+
yield [
947+
'/** @template T super string */',
948+
'/** @template T of int super string */',
949+
$addTemplateTagBound,
950+
];
951+
946952
$removeTemplateTagBound = new class extends AbstractNodeVisitor {
947953

948954
public function enterNode(Node $node)
@@ -962,6 +968,56 @@ public function enterNode(Node $node)
962968
$removeTemplateTagBound,
963969
];
964970

971+
$addTemplateTagLowerBound = new class extends AbstractNodeVisitor {
972+
973+
public function enterNode(Node $node)
974+
{
975+
if ($node instanceof TemplateTagValueNode) {
976+
$node->lowerBound = new IdentifierTypeNode('int');
977+
}
978+
979+
return $node;
980+
}
981+
982+
};
983+
984+
yield [
985+
'/** @template T */',
986+
'/** @template T super int */',
987+
$addTemplateTagLowerBound,
988+
];
989+
990+
yield [
991+
'/** @template T super string */',
992+
'/** @template T super int */',
993+
$addTemplateTagLowerBound,
994+
];
995+
996+
yield [
997+
'/** @template T of string */',
998+
'/** @template T of string super int */',
999+
$addTemplateTagLowerBound,
1000+
];
1001+
1002+
$removeTemplateTagLowerBound = new class extends AbstractNodeVisitor {
1003+
1004+
public function enterNode(Node $node)
1005+
{
1006+
if ($node instanceof TemplateTagValueNode) {
1007+
$node->lowerBound = null;
1008+
}
1009+
1010+
return $node;
1011+
}
1012+
1013+
};
1014+
1015+
yield [
1016+
'/** @template T super int */',
1017+
'/** @template T */',
1018+
$removeTemplateTagLowerBound,
1019+
];
1020+
9651021
$addKeyNameToArrayShapeItemNode = new class extends AbstractNodeVisitor {
9661022

9671023
public function enterNode(Node $node)

0 commit comments

Comments
(0)

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