From 93e908833161b57fa3d652b659f8263d3406325f Mon Sep 17 00:00:00 2001
From: Tomohito YABU <tyabu1212@gmail.com>
Date: Wed, 2 Nov 2022 00:46:13 +0900
Subject: [PATCH 1/7] Add generic support to `@method` definitions

---
 src/Ast/PhpDoc/MethodTagValueGenericNode.php |  33 ++++
 src/Ast/PhpDoc/MethodTagValueNode.php        |   9 +-
 src/Parser/PhpDocParser.php                  |  23 ++-
 tests/PHPStan/Parser/PhpDocParserTest.php    | 181 +++++++++++++++++++
 4 files changed, 243 insertions(+), 3 deletions(-)
 create mode 100644 src/Ast/PhpDoc/MethodTagValueGenericNode.php

diff --git a/src/Ast/PhpDoc/MethodTagValueGenericNode.php b/src/Ast/PhpDoc/MethodTagValueGenericNode.php
new file mode 100644
index 00000000..23e84019
--- /dev/null
+++ b/src/Ast/PhpDoc/MethodTagValueGenericNode.php
@@ -0,0 +1,33 @@
+<?php declare(strict_types = 1);
+
+namespace PHPStan\PhpDocParser\Ast\PhpDoc;
+
+use PHPStan\PhpDocParser\Ast\Node;
+use PHPStan\PhpDocParser\Ast\NodeAttributes;
+use PHPStan\PhpDocParser\Ast\Type\TypeNode;
+
+class MethodTagValueGenericNode implements Node
+{
+
+	use NodeAttributes;
+
+	/** @var string */
+	public $name;
+
+	/** @var TypeNode|null */
+	public $bound;
+
+	public function __construct(string $name, ?TypeNode $bound)
+	{
+		$this->name = $name;
+		$this->bound = $bound;
+	}
+
+
+	public function __toString(): string
+	{
+		$bound = $this->bound !== null ? " of {$this->bound}" : '';
+		return trim("{$this->name}{$bound}");
+	}
+
+}
diff --git a/src/Ast/PhpDoc/MethodTagValueNode.php b/src/Ast/PhpDoc/MethodTagValueNode.php
index 155897bb..b2b47bdc 100644
--- a/src/Ast/PhpDoc/MethodTagValueNode.php
+++ b/src/Ast/PhpDoc/MethodTagValueNode.php
@@ -20,17 +20,21 @@ class MethodTagValueNode implements PhpDocTagValueNode
 	/** @var string */
 	public $methodName;
 
+	/** @var MethodTagValueGenericNode[] */
+	public $generics;
+
 	/** @var MethodTagValueParameterNode[] */
 	public $parameters;
 
 	/** @var string (may be empty) */
 	public $description;
 
-	public function __construct(bool $isStatic, ?TypeNode $returnType, string $methodName, array $parameters, string $description)
+	public function __construct(bool $isStatic, ?TypeNode $returnType, string $methodName, array $generics, array $parameters, string $description)
 	{
 		$this->isStatic = $isStatic;
 		$this->returnType = $returnType;
 		$this->methodName = $methodName;
+		$this->generics = $generics;
 		$this->parameters = $parameters;
 		$this->description = $description;
 	}
@@ -40,9 +44,10 @@ public function __toString(): string
 	{
 		$static = $this->isStatic ? 'static ' : '';
 		$returnType = $this->returnType !== null ? "{$this->returnType} " : '';
+		$generics = count($this->generics) > 0 ? '<' . implode(', ', $this->generics) . '>' : '';
 		$parameters = implode(', ', $this->parameters);
 		$description = $this->description !== '' ? " {$this->description}" : '';
-		return "{$static}{$returnType}{$this->methodName}({$parameters}){$description}";
+		return "{$static}{$returnType}{$this->methodName}{$generics}({$parameters}){$description}";
 	}
 
 }
diff --git a/src/Parser/PhpDocParser.php b/src/Parser/PhpDocParser.php
index 9badbe61..cc718572 100644
--- a/src/Parser/PhpDocParser.php
+++ b/src/Parser/PhpDocParser.php
@@ -346,6 +346,14 @@ private function parseMethodTagValue(TokenIterator $tokens): Ast\PhpDoc\MethodTa
 			exit;
 		}
 
+		$generics = [];
+		if ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)) {
+			do {
+				$generics[] = $this->parseMethodTagValueGeneric($tokens);
+			} while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA));
+			$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET);
+		}
+
 		$parameters = [];
 		$tokens->consumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES);
 		if (!$tokens->isCurrentTokenType(Lexer::TOKEN_CLOSE_PARENTHESES)) {
@@ -357,9 +365,22 @@ private function parseMethodTagValue(TokenIterator $tokens): Ast\PhpDoc\MethodTa
 		$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES);
 
 		$description = $this->parseOptionalDescription($tokens);
-		return new Ast\PhpDoc\MethodTagValueNode($isStatic, $returnType, $methodName, $parameters, $description);
+		return new Ast\PhpDoc\MethodTagValueNode($isStatic, $returnType, $methodName, $generics, $parameters, $description);
 	}
 
+	private function parseMethodTagValueGeneric(TokenIterator $tokens): Ast\PhpDoc\MethodTagValueGenericNode
+	{
+		$name = $tokens->currentTokenValue();
+		$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
+
+		if ($tokens->tryConsumeTokenValue('of') || $tokens->tryConsumeTokenValue('as')) {
+			$bound = $this->typeParser->parse($tokens);
+		} else {
+			$bound = null;
+		}
+
+		return new Ast\PhpDoc\MethodTagValueGenericNode($name, $bound);
+	}
 
 	private function parseMethodTagValueParameter(TokenIterator $tokens): Ast\PhpDoc\MethodTagValueParameterNode
 	{
diff --git a/tests/PHPStan/Parser/PhpDocParserTest.php b/tests/PHPStan/Parser/PhpDocParserTest.php
index c81a3a4c..c023761f 100644
--- a/tests/PHPStan/Parser/PhpDocParserTest.php
+++ b/tests/PHPStan/Parser/PhpDocParserTest.php
@@ -3,6 +3,7 @@
 namespace PHPStan\PhpDocParser\Parser;
 
 use Iterator;
+use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprArrayItemNode;
 use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprArrayNode;
 use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode;
 use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode;
@@ -16,6 +17,7 @@
 use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode;
 use PHPStan\PhpDocParser\Ast\PhpDoc\ImplementsTagValueNode;
 use PHPStan\PhpDocParser\Ast\PhpDoc\InvalidTagValueNode;
+use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueGenericNode;
 use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode;
 use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueParameterNode;
 use PHPStan\PhpDocParser\Ast\PhpDoc\MixinTagValueNode;
@@ -44,6 +46,7 @@
 use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode;
 use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
 use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
+use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode;
 use PHPStan\PhpDocParser\Ast\Type\OffsetAccessTypeNode;
 use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
 use PHPStan\PhpDocParser\Lexer\Lexer;
@@ -1834,6 +1837,7 @@ public function provideMethodTagsData(): Iterator
 						null,
 						'foo',
 						[],
+						[],
 						''
 					)
 				),
@@ -1851,6 +1855,7 @@ public function provideMethodTagsData(): Iterator
 						new IdentifierTypeNode('Foo'),
 						'foo',
 						[],
+						[],
 						''
 					)
 				),
@@ -1868,6 +1873,7 @@ public function provideMethodTagsData(): Iterator
 						new IdentifierTypeNode('static'),
 						'foo',
 						[],
+						[],
 						''
 					)
 				),
@@ -1885,6 +1891,7 @@ public function provideMethodTagsData(): Iterator
 						new IdentifierTypeNode('Foo'),
 						'foo',
 						[],
+						[],
 						''
 					)
 				),
@@ -1902,6 +1909,7 @@ public function provideMethodTagsData(): Iterator
 						new IdentifierTypeNode('static'),
 						'foo',
 						[],
+						[],
 						''
 					)
 				),
@@ -1919,6 +1927,7 @@ public function provideMethodTagsData(): Iterator
 						new IdentifierTypeNode('Foo'),
 						'foo',
 						[],
+						[],
 						'optional description'
 					)
 				),
@@ -1935,6 +1944,7 @@ public function provideMethodTagsData(): Iterator
 						false,
 						new IdentifierTypeNode('Foo'),
 						'foo',
+						[],
 						[
 							new MethodTagValueParameterNode(
 								null,
@@ -1960,6 +1970,7 @@ public function provideMethodTagsData(): Iterator
 						false,
 						new IdentifierTypeNode('Foo'),
 						'foo',
+						[],
 						[
 							new MethodTagValueParameterNode(
 								new IdentifierTypeNode('A'),
@@ -1985,6 +1996,7 @@ public function provideMethodTagsData(): Iterator
 						false,
 						new IdentifierTypeNode('Foo'),
 						'foo',
+						[],
 						[
 							new MethodTagValueParameterNode(
 								new IdentifierTypeNode('A'),
@@ -2010,6 +2022,7 @@ public function provideMethodTagsData(): Iterator
 						false,
 						new IdentifierTypeNode('Foo'),
 						'foo',
+						[],
 						[
 							new MethodTagValueParameterNode(
 								new IdentifierTypeNode('A'),
@@ -2035,6 +2048,7 @@ public function provideMethodTagsData(): Iterator
 						false,
 						new IdentifierTypeNode('Foo'),
 						'foo',
+						[],
 						[
 							new MethodTagValueParameterNode(
 								new IdentifierTypeNode('A'),
@@ -2060,6 +2074,7 @@ public function provideMethodTagsData(): Iterator
 						false,
 						new IdentifierTypeNode('Foo'),
 						'foo',
+						[],
 						[
 							new MethodTagValueParameterNode(
 								null,
@@ -2085,6 +2100,7 @@ public function provideMethodTagsData(): Iterator
 						false,
 						new IdentifierTypeNode('Foo'),
 						'foo',
+						[],
 						[
 							new MethodTagValueParameterNode(
 								new IdentifierTypeNode('A'),
@@ -2110,6 +2126,7 @@ public function provideMethodTagsData(): Iterator
 						false,
 						new IdentifierTypeNode('Foo'),
 						'foo',
+						[],
 						[
 							new MethodTagValueParameterNode(
 								null,
@@ -2195,6 +2212,93 @@ public function provideMethodTagsData(): Iterator
 				),
 			]),
 		];
+
+		yield [
+			'OK non-static, with return type and parameter with generic type',
+			'/** @method ?T randomElement<T>(array<array-key, T> $array = [\'a\', \'b\']) */',
+			new PhpDocNode([
+				new PhpDocTagNode(
+					'@method',
+					new MethodTagValueNode(
+						false,
+						new NullableTypeNode(new IdentifierTypeNode('T')),
+						'randomElement',
+						[
+							new MethodTagValueGenericNode('T', null),
+						],
+						[
+							new MethodTagValueParameterNode(
+								new GenericTypeNode(
+									new IdentifierTypeNode('array'),
+									[
+										new IdentifierTypeNode('array-key'),
+										new IdentifierTypeNode('T'),
+									]
+								),
+								false,
+								false,
+								'$array',
+								new ConstExprArrayNode([
+									new ConstExprArrayItemNode(
+										null,
+										new ConstExprStringNode('\'a\'')
+									),
+									new ConstExprArrayItemNode(
+										null,
+										new ConstExprStringNode('\'b\'')
+									),
+								]),
+							),
+						],
+						''
+					)
+				)
+			])
+		];
+
+		yield [
+			'OK static, with return type and multiple parameters with generic type',
+			'/** @method static bool compare<T1, T2 of Bar, T3 as Baz>(T1 $t1, T2 $t2, T3 $t3) */',
+			new PhpDocNode([
+				new PhpDocTagNode(
+					'@method',
+					new MethodTagValueNode(
+						true,
+						new IdentifierTypeNode('bool'),
+						'compare',
+						[
+							new MethodTagValueGenericNode('T1', null),
+							new MethodTagValueGenericNode('T2', new IdentifierTypeNode('Bar')),
+							new MethodTagValueGenericNode('T3', new IdentifierTypeNode('Baz')),
+						],
+						[
+							new MethodTagValueParameterNode(
+								new IdentifierTypeNode('T1'),
+								false,
+								false,
+								'$t1',
+								null
+							),
+							new MethodTagValueParameterNode(
+								new IdentifierTypeNode('T2'),
+								false,
+								false,
+								'$t2',
+								null
+							),
+							new MethodTagValueParameterNode(
+								new IdentifierTypeNode('T3'),
+								false,
+								false,
+								'$t3',
+								null
+							),
+						],
+						''
+					)
+				)
+			])
+		];
 	}
 
 
@@ -2533,6 +2637,7 @@ public function provideMultiLinePhpDocData(): array
 							false,
 							new IdentifierTypeNode('int'),
 							'getInteger',
+							[],
 							[
 								new MethodTagValueParameterNode(
 									new IdentifierTypeNode('int'),
@@ -2558,6 +2663,7 @@ public function provideMultiLinePhpDocData(): array
 							false,
 							new IdentifierTypeNode('void'),
 							'doSomething',
+							[],
 							[
 								new MethodTagValueParameterNode(
 									new IdentifierTypeNode('int'),
@@ -2587,6 +2693,7 @@ public function provideMultiLinePhpDocData(): array
 							]),
 							'getFooOrBar',
 							[],
+							[],
 							''
 						)
 					),
@@ -2597,6 +2704,7 @@ public function provideMultiLinePhpDocData(): array
 							null,
 							'methodWithNoReturnType',
 							[],
+							[],
 							''
 						)
 					),
@@ -2606,6 +2714,7 @@ public function provideMultiLinePhpDocData(): array
 							true,
 							new IdentifierTypeNode('int'),
 							'getIntegerStatically',
+							[],
 							[
 								new MethodTagValueParameterNode(
 									new IdentifierTypeNode('int'),
@@ -2631,6 +2740,7 @@ public function provideMultiLinePhpDocData(): array
 							true,
 							new IdentifierTypeNode('void'),
 							'doSomethingStatically',
+							[],
 							[
 								new MethodTagValueParameterNode(
 									new IdentifierTypeNode('int'),
@@ -2660,6 +2770,7 @@ public function provideMultiLinePhpDocData(): array
 							]),
 							'getFooOrBarStatically',
 							[],
+							[],
 							''
 						)
 					),
@@ -2670,6 +2781,7 @@ public function provideMultiLinePhpDocData(): array
 							new IdentifierTypeNode('static'),
 							'methodWithNoReturnTypeStatically',
 							[],
+							[],
 							''
 						)
 					),
@@ -2679,6 +2791,7 @@ public function provideMultiLinePhpDocData(): array
 							false,
 							new IdentifierTypeNode('int'),
 							'getIntegerWithDescription',
+							[],
 							[
 								new MethodTagValueParameterNode(
 									new IdentifierTypeNode('int'),
@@ -2704,6 +2817,7 @@ public function provideMultiLinePhpDocData(): array
 							false,
 							new IdentifierTypeNode('void'),
 							'doSomethingWithDescription',
+							[],
 							[
 								new MethodTagValueParameterNode(
 									new IdentifierTypeNode('int'),
@@ -2733,6 +2847,7 @@ public function provideMultiLinePhpDocData(): array
 							]),
 							'getFooOrBarWithDescription',
 							[],
+							[],
 							'Get a Foo or a Bar with a description.'
 						)
 					),
@@ -2743,6 +2858,7 @@ public function provideMultiLinePhpDocData(): array
 							null,
 							'methodWithNoReturnTypeWithDescription',
 							[],
+							[],
 							'Do something with a description but what, who knows!'
 						)
 					),
@@ -2752,6 +2868,7 @@ public function provideMultiLinePhpDocData(): array
 							true,
 							new IdentifierTypeNode('int'),
 							'getIntegerStaticallyWithDescription',
+							[],
 							[
 								new MethodTagValueParameterNode(
 									new IdentifierTypeNode('int'),
@@ -2777,6 +2894,7 @@ public function provideMultiLinePhpDocData(): array
 							true,
 							new IdentifierTypeNode('void'),
 							'doSomethingStaticallyWithDescription',
+							[],
 							[
 								new MethodTagValueParameterNode(
 									new IdentifierTypeNode('int'),
@@ -2806,6 +2924,7 @@ public function provideMultiLinePhpDocData(): array
 							]),
 							'getFooOrBarStaticallyWithDescription',
 							[],
+							[],
 							'Get a Foo or a Bar with a description statically.'
 						)
 					),
@@ -2816,6 +2935,7 @@ public function provideMultiLinePhpDocData(): array
 							new IdentifierTypeNode('static'),
 							'methodWithNoReturnTypeStaticallyWithDescription',
 							[],
+							[],
 							'Do something with a description statically, but what, who knows!'
 						)
 					),
@@ -2826,6 +2946,7 @@ public function provideMultiLinePhpDocData(): array
 							new IdentifierTypeNode('bool'),
 							'aStaticMethodThatHasAUniqueReturnTypeInThisClass',
 							[],
+							[],
 							''
 						)
 					),
@@ -2836,6 +2957,7 @@ public function provideMultiLinePhpDocData(): array
 							new IdentifierTypeNode('string'),
 							'aStaticMethodThatHasAUniqueReturnTypeInThisClassWithDescription',
 							[],
+							[],
 							'A Description.'
 						)
 					),
@@ -2846,6 +2968,7 @@ public function provideMultiLinePhpDocData(): array
 							new IdentifierTypeNode('int'),
 							'getIntegerNoParams',
 							[],
+							[],
 							''
 						)
 					),
@@ -2856,6 +2979,7 @@ public function provideMultiLinePhpDocData(): array
 							new IdentifierTypeNode('void'),
 							'doSomethingNoParams',
 							[],
+							[],
 							''
 						)
 					),
@@ -2869,6 +2993,7 @@ public function provideMultiLinePhpDocData(): array
 							]),
 							'getFooOrBarNoParams',
 							[],
+							[],
 							''
 						)
 					),
@@ -2879,6 +3004,7 @@ public function provideMultiLinePhpDocData(): array
 							null,
 							'methodWithNoReturnTypeNoParams',
 							[],
+							[],
 							''
 						)
 					),
@@ -2889,6 +3015,7 @@ public function provideMultiLinePhpDocData(): array
 							new IdentifierTypeNode('int'),
 							'getIntegerStaticallyNoParams',
 							[],
+							[],
 							''
 						)
 					),
@@ -2899,6 +3026,7 @@ public function provideMultiLinePhpDocData(): array
 							new IdentifierTypeNode('void'),
 							'doSomethingStaticallyNoParams',
 							[],
+							[],
 							''
 						)
 					),
@@ -2912,6 +3040,7 @@ public function provideMultiLinePhpDocData(): array
 							]),
 							'getFooOrBarStaticallyNoParams',
 							[],
+							[],
 							''
 						)
 					),
@@ -2922,6 +3051,7 @@ public function provideMultiLinePhpDocData(): array
 							new IdentifierTypeNode('static'),
 							'methodWithNoReturnTypeStaticallyNoParams',
 							[],
+							[],
 							''
 						)
 					),
@@ -2932,6 +3062,7 @@ public function provideMultiLinePhpDocData(): array
 							new IdentifierTypeNode('int'),
 							'getIntegerWithDescriptionNoParams',
 							[],
+							[],
 							'Get an integer with a description.'
 						)
 					),
@@ -2942,6 +3073,7 @@ public function provideMultiLinePhpDocData(): array
 							new IdentifierTypeNode('void'),
 							'doSomethingWithDescriptionNoParams',
 							[],
+							[],
 							'Do something with a description.'
 						)
 					),
@@ -2955,6 +3087,7 @@ public function provideMultiLinePhpDocData(): array
 							]),
 							'getFooOrBarWithDescriptionNoParams',
 							[],
+							[],
 							'Get a Foo or a Bar with a description.'
 						)
 					),
@@ -2965,6 +3098,7 @@ public function provideMultiLinePhpDocData(): array
 							new IdentifierTypeNode('int'),
 							'getIntegerStaticallyWithDescriptionNoParams',
 							[],
+							[],
 							'Get an integer with a description statically.'
 						)
 					),
@@ -2975,6 +3109,7 @@ public function provideMultiLinePhpDocData(): array
 							new IdentifierTypeNode('void'),
 							'doSomethingStaticallyWithDescriptionNoParams',
 							[],
+							[],
 							'Do something with a description statically.'
 						)
 					),
@@ -2988,6 +3123,7 @@ public function provideMultiLinePhpDocData(): array
 							]),
 							'getFooOrBarStaticallyWithDescriptionNoParams',
 							[],
+							[],
 							'Get a Foo or a Bar with a description statically.'
 						)
 					),
@@ -3001,6 +3137,7 @@ public function provideMultiLinePhpDocData(): array
 							]),
 							'aStaticMethodThatHasAUniqueReturnTypeInThisClassNoParams',
 							[],
+							[],
 							''
 						)
 					),
@@ -3014,6 +3151,7 @@ public function provideMultiLinePhpDocData(): array
 							]),
 							'aStaticMethodThatHasAUniqueReturnTypeInThisClassWithDescriptionNoParams',
 							[],
+							[],
 							'A Description.'
 						)
 					),
@@ -3023,6 +3161,7 @@ public function provideMultiLinePhpDocData(): array
 							false,
 							new IdentifierTypeNode('\\Aws\\Result'),
 							'publish',
+							[],
 							[
 								new MethodTagValueParameterNode(
 									new IdentifierTypeNode('array'),
@@ -3041,6 +3180,7 @@ public function provideMultiLinePhpDocData(): array
 							false,
 							new IdentifierTypeNode('Image'),
 							'rotate',
+							[],
 							[
 								new MethodTagValueParameterNode(
 									new IdentifierTypeNode('float'),
@@ -3067,11 +3207,52 @@ public function provideMultiLinePhpDocData(): array
 							new IdentifierTypeNode('Foo'),
 							'overridenMethod',
 							[],
+							[],
 							''
 						)
 					),
 				]),
 			],
+			[
+				'OK with template method',
+				'/**
+				  * @template TKey as array-key
+				  * @template TValue
+				  * @method TKey|null find(TValue $v) find index of $v
+				  */',
+				new PhpDocNode([
+					new PhpDocTagNode(
+						'@template',
+						new TemplateTagValueNode('TKey', new IdentifierTypeNode('array-key'), '')
+					),
+					new PhpDocTagNode(
+						'@template',
+						new TemplateTagValueNode('TValue', null, '')
+					),
+					new PhpDocTagNode(
+						'@method',
+						new MethodTagValueNode(
+							false,
+							new UnionTypeNode([
+								new IdentifierTypeNode('TKey'),
+								new IdentifierTypeNode('null'),
+							]),
+							'find',
+							[],
+							[
+								new MethodTagValueParameterNode(
+									new IdentifierTypeNode('TValue'),
+									false,
+									false,
+									'$v',
+									null
+								),
+							],
+							'find index of $v',
+						)
+					),
+				]),
+			],
 			[
 				'OK with multiline conditional return type',
 				'/**

From 6e9c7760d3518e489bc5f80211709fb27e6c6e37 Mon Sep 17 00:00:00 2001
From: Tomohito YABU <tyabu1212@gmail.com>
Date: Wed, 2 Nov 2022 02:12:09 +0900
Subject: [PATCH 2/7] Avoid BC break and reuse TemplateTagValueNode

---
 src/Ast/PhpDoc/MethodTagValueGenericNode.php | 33 --------
 src/Ast/PhpDoc/MethodTagValueNode.php        | 12 +--
 src/Parser/PhpDocParser.php                  | 16 ++--
 tests/PHPStan/Parser/PhpDocParserTest.php    | 80 ++++----------------
 4 files changed, 33 insertions(+), 108 deletions(-)
 delete mode 100644 src/Ast/PhpDoc/MethodTagValueGenericNode.php

diff --git a/src/Ast/PhpDoc/MethodTagValueGenericNode.php b/src/Ast/PhpDoc/MethodTagValueGenericNode.php
deleted file mode 100644
index 23e84019..00000000
--- a/src/Ast/PhpDoc/MethodTagValueGenericNode.php
+++ /dev/null
@@ -1,33 +0,0 @@
-<?php declare(strict_types = 1);
-
-namespace PHPStan\PhpDocParser\Ast\PhpDoc;
-
-use PHPStan\PhpDocParser\Ast\Node;
-use PHPStan\PhpDocParser\Ast\NodeAttributes;
-use PHPStan\PhpDocParser\Ast\Type\TypeNode;
-
-class MethodTagValueGenericNode implements Node
-{
-
-	use NodeAttributes;
-
-	/** @var string */
-	public $name;
-
-	/** @var TypeNode|null */
-	public $bound;
-
-	public function __construct(string $name, ?TypeNode $bound)
-	{
-		$this->name = $name;
-		$this->bound = $bound;
-	}
-
-
-	public function __toString(): string
-	{
-		$bound = $this->bound !== null ? " of {$this->bound}" : '';
-		return trim("{$this->name}{$bound}");
-	}
-
-}
diff --git a/src/Ast/PhpDoc/MethodTagValueNode.php b/src/Ast/PhpDoc/MethodTagValueNode.php
index b2b47bdc..14859cda 100644
--- a/src/Ast/PhpDoc/MethodTagValueNode.php
+++ b/src/Ast/PhpDoc/MethodTagValueNode.php
@@ -20,8 +20,8 @@ class MethodTagValueNode implements PhpDocTagValueNode
 	/** @var string */
 	public $methodName;
 
-	/** @var MethodTagValueGenericNode[] */
-	public $generics;
+	/** @var TemplateTagValueNode[] */
+	public $templateTypes;
 
 	/** @var MethodTagValueParameterNode[] */
 	public $parameters;
@@ -29,14 +29,14 @@ class MethodTagValueNode implements PhpDocTagValueNode
 	/** @var string (may be empty) */
 	public $description;
 
-	public function __construct(bool $isStatic, ?TypeNode $returnType, string $methodName, array $generics, array $parameters, string $description)
+	public function __construct(bool $isStatic, ?TypeNode $returnType, string $methodName, array $parameters, string $description, array $templateTypes = [])
 	{
 		$this->isStatic = $isStatic;
 		$this->returnType = $returnType;
 		$this->methodName = $methodName;
-		$this->generics = $generics;
 		$this->parameters = $parameters;
 		$this->description = $description;
+		$this->templateTypes = $templateTypes;
 	}
 
 
@@ -44,10 +44,10 @@ public function __toString(): string
 	{
 		$static = $this->isStatic ? 'static ' : '';
 		$returnType = $this->returnType !== null ? "{$this->returnType} " : '';
-		$generics = count($this->generics) > 0 ? '<' . implode(', ', $this->generics) . '>' : '';
 		$parameters = implode(', ', $this->parameters);
 		$description = $this->description !== '' ? " {$this->description}" : '';
-		return "{$static}{$returnType}{$this->methodName}{$generics}({$parameters}){$description}";
+		$templateTypes = count($this->templateTypes) > 0 ? '<' . implode(', ', $this->templateTypes) . '>' : '';
+		return "{$static}{$returnType}{$this->methodName}{$templateTypes}({$parameters}){$description}";
 	}
 
 }
diff --git a/src/Parser/PhpDocParser.php b/src/Parser/PhpDocParser.php
index cc718572..965d9509 100644
--- a/src/Parser/PhpDocParser.php
+++ b/src/Parser/PhpDocParser.php
@@ -346,10 +346,10 @@ private function parseMethodTagValue(TokenIterator $tokens): Ast\PhpDoc\MethodTa
 			exit;
 		}
 
-		$generics = [];
+		$templateTypes = [];
 		if ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)) {
 			do {
-				$generics[] = $this->parseMethodTagValueGeneric($tokens);
+				$templateTypes[] = $this->parseMethodTagValueTemplateType($tokens);
 			} while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA));
 			$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET);
 		}
@@ -365,10 +365,10 @@ private function parseMethodTagValue(TokenIterator $tokens): Ast\PhpDoc\MethodTa
 		$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES);
 
 		$description = $this->parseOptionalDescription($tokens);
-		return new Ast\PhpDoc\MethodTagValueNode($isStatic, $returnType, $methodName, $generics, $parameters, $description);
+		return new Ast\PhpDoc\MethodTagValueNode($isStatic, $returnType, $methodName, $parameters, $description, $templateTypes);
 	}
 
-	private function parseMethodTagValueGeneric(TokenIterator $tokens): Ast\PhpDoc\MethodTagValueGenericNode
+	private function parseMethodTagValueTemplateType(TokenIterator $tokens): Ast\PhpDoc\TemplateTagValueNode
 	{
 		$name = $tokens->currentTokenValue();
 		$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
@@ -379,7 +379,13 @@ private function parseMethodTagValueGeneric(TokenIterator $tokens): Ast\PhpDoc\M
 			$bound = null;
 		}
 
-		return new Ast\PhpDoc\MethodTagValueGenericNode($name, $bound);
+		if ($tokens->tryConsumeTokenValue('=')) {
+			$default = $this->typeParser->parse($tokens);
+		} else {
+			$default = null;
+		}
+
+		return new Ast\PhpDoc\TemplateTagValueNode($name, $bound, '', $default);
 	}
 
 	private function parseMethodTagValueParameter(TokenIterator $tokens): Ast\PhpDoc\MethodTagValueParameterNode
diff --git a/tests/PHPStan/Parser/PhpDocParserTest.php b/tests/PHPStan/Parser/PhpDocParserTest.php
index c023761f..5c9fa567 100644
--- a/tests/PHPStan/Parser/PhpDocParserTest.php
+++ b/tests/PHPStan/Parser/PhpDocParserTest.php
@@ -17,7 +17,6 @@
 use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode;
 use PHPStan\PhpDocParser\Ast\PhpDoc\ImplementsTagValueNode;
 use PHPStan\PhpDocParser\Ast\PhpDoc\InvalidTagValueNode;
-use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueGenericNode;
 use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode;
 use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueParameterNode;
 use PHPStan\PhpDocParser\Ast\PhpDoc\MixinTagValueNode;
@@ -1837,7 +1836,6 @@ public function provideMethodTagsData(): Iterator
 						null,
 						'foo',
 						[],
-						[],
 						''
 					)
 				),
@@ -1855,7 +1853,6 @@ public function provideMethodTagsData(): Iterator
 						new IdentifierTypeNode('Foo'),
 						'foo',
 						[],
-						[],
 						''
 					)
 				),
@@ -1873,7 +1870,6 @@ public function provideMethodTagsData(): Iterator
 						new IdentifierTypeNode('static'),
 						'foo',
 						[],
-						[],
 						''
 					)
 				),
@@ -1891,7 +1887,6 @@ public function provideMethodTagsData(): Iterator
 						new IdentifierTypeNode('Foo'),
 						'foo',
 						[],
-						[],
 						''
 					)
 				),
@@ -1909,7 +1904,6 @@ public function provideMethodTagsData(): Iterator
 						new IdentifierTypeNode('static'),
 						'foo',
 						[],
-						[],
 						''
 					)
 				),
@@ -1927,7 +1921,6 @@ public function provideMethodTagsData(): Iterator
 						new IdentifierTypeNode('Foo'),
 						'foo',
 						[],
-						[],
 						'optional description'
 					)
 				),
@@ -1944,7 +1937,6 @@ public function provideMethodTagsData(): Iterator
 						false,
 						new IdentifierTypeNode('Foo'),
 						'foo',
-						[],
 						[
 							new MethodTagValueParameterNode(
 								null,
@@ -1970,7 +1962,6 @@ public function provideMethodTagsData(): Iterator
 						false,
 						new IdentifierTypeNode('Foo'),
 						'foo',
-						[],
 						[
 							new MethodTagValueParameterNode(
 								new IdentifierTypeNode('A'),
@@ -1996,7 +1987,6 @@ public function provideMethodTagsData(): Iterator
 						false,
 						new IdentifierTypeNode('Foo'),
 						'foo',
-						[],
 						[
 							new MethodTagValueParameterNode(
 								new IdentifierTypeNode('A'),
@@ -2022,7 +2012,6 @@ public function provideMethodTagsData(): Iterator
 						false,
 						new IdentifierTypeNode('Foo'),
 						'foo',
-						[],
 						[
 							new MethodTagValueParameterNode(
 								new IdentifierTypeNode('A'),
@@ -2048,7 +2037,6 @@ public function provideMethodTagsData(): Iterator
 						false,
 						new IdentifierTypeNode('Foo'),
 						'foo',
-						[],
 						[
 							new MethodTagValueParameterNode(
 								new IdentifierTypeNode('A'),
@@ -2074,7 +2062,6 @@ public function provideMethodTagsData(): Iterator
 						false,
 						new IdentifierTypeNode('Foo'),
 						'foo',
-						[],
 						[
 							new MethodTagValueParameterNode(
 								null,
@@ -2100,7 +2087,6 @@ public function provideMethodTagsData(): Iterator
 						false,
 						new IdentifierTypeNode('Foo'),
 						'foo',
-						[],
 						[
 							new MethodTagValueParameterNode(
 								new IdentifierTypeNode('A'),
@@ -2126,7 +2112,6 @@ public function provideMethodTagsData(): Iterator
 						false,
 						new IdentifierTypeNode('Foo'),
 						'foo',
-						[],
 						[
 							new MethodTagValueParameterNode(
 								null,
@@ -2215,7 +2200,7 @@ public function provideMethodTagsData(): Iterator
 
 		yield [
 			'OK non-static, with return type and parameter with generic type',
-			'/** @method ?T randomElement<T>(array<array-key, T> $array = [\'a\', \'b\']) */',
+			'/** @method ?T randomElement<T = string>(array<array-key, T> $array = [\'a\', \'b\']) */',
 			new PhpDocNode([
 				new PhpDocTagNode(
 					'@method',
@@ -2223,9 +2208,6 @@ public function provideMethodTagsData(): Iterator
 						false,
 						new NullableTypeNode(new IdentifierTypeNode('T')),
 						'randomElement',
-						[
-							new MethodTagValueGenericNode('T', null),
-						],
 						[
 							new MethodTagValueParameterNode(
 								new GenericTypeNode(
@@ -2250,7 +2232,15 @@ public function provideMethodTagsData(): Iterator
 								]),
 							),
 						],
-						''
+						'',
+						[
+							new TemplateTagValueNode(
+								'T',
+								null,
+								'',
+								new IdentifierTypeNode('string'),
+							),
+						],
 					)
 				)
 			])
@@ -2266,11 +2256,6 @@ public function provideMethodTagsData(): Iterator
 						true,
 						new IdentifierTypeNode('bool'),
 						'compare',
-						[
-							new MethodTagValueGenericNode('T1', null),
-							new MethodTagValueGenericNode('T2', new IdentifierTypeNode('Bar')),
-							new MethodTagValueGenericNode('T3', new IdentifierTypeNode('Baz')),
-						],
 						[
 							new MethodTagValueParameterNode(
 								new IdentifierTypeNode('T1'),
@@ -2294,7 +2279,12 @@ public function provideMethodTagsData(): Iterator
 								null
 							),
 						],
-						''
+						'',
+						[
+							new TemplateTagValueNode('T1', null, ''),
+							new TemplateTagValueNode('T2', new IdentifierTypeNode('Bar'), ''),
+							new TemplateTagValueNode('T3', new IdentifierTypeNode('Baz'), ''),
+						],
 					)
 				)
 			])
@@ -2637,7 +2627,6 @@ public function provideMultiLinePhpDocData(): array
 							false,
 							new IdentifierTypeNode('int'),
 							'getInteger',
-							[],
 							[
 								new MethodTagValueParameterNode(
 									new IdentifierTypeNode('int'),
@@ -2663,7 +2652,6 @@ public function provideMultiLinePhpDocData(): array
 							false,
 							new IdentifierTypeNode('void'),
 							'doSomething',
-							[],
 							[
 								new MethodTagValueParameterNode(
 									new IdentifierTypeNode('int'),
@@ -2693,7 +2681,6 @@ public function provideMultiLinePhpDocData(): array
 							]),
 							'getFooOrBar',
 							[],
-							[],
 							''
 						)
 					),
@@ -2704,7 +2691,6 @@ public function provideMultiLinePhpDocData(): array
 							null,
 							'methodWithNoReturnType',
 							[],
-							[],
 							''
 						)
 					),
@@ -2714,7 +2700,6 @@ public function provideMultiLinePhpDocData(): array
 							true,
 							new IdentifierTypeNode('int'),
 							'getIntegerStatically',
-							[],
 							[
 								new MethodTagValueParameterNode(
 									new IdentifierTypeNode('int'),
@@ -2740,7 +2725,6 @@ public function provideMultiLinePhpDocData(): array
 							true,
 							new IdentifierTypeNode('void'),
 							'doSomethingStatically',
-							[],
 							[
 								new MethodTagValueParameterNode(
 									new IdentifierTypeNode('int'),
@@ -2770,7 +2754,6 @@ public function provideMultiLinePhpDocData(): array
 							]),
 							'getFooOrBarStatically',
 							[],
-							[],
 							''
 						)
 					),
@@ -2781,7 +2764,6 @@ public function provideMultiLinePhpDocData(): array
 							new IdentifierTypeNode('static'),
 							'methodWithNoReturnTypeStatically',
 							[],
-							[],
 							''
 						)
 					),
@@ -2791,7 +2773,6 @@ public function provideMultiLinePhpDocData(): array
 							false,
 							new IdentifierTypeNode('int'),
 							'getIntegerWithDescription',
-							[],
 							[
 								new MethodTagValueParameterNode(
 									new IdentifierTypeNode('int'),
@@ -2817,7 +2798,6 @@ public function provideMultiLinePhpDocData(): array
 							false,
 							new IdentifierTypeNode('void'),
 							'doSomethingWithDescription',
-							[],
 							[
 								new MethodTagValueParameterNode(
 									new IdentifierTypeNode('int'),
@@ -2847,7 +2827,6 @@ public function provideMultiLinePhpDocData(): array
 							]),
 							'getFooOrBarWithDescription',
 							[],
-							[],
 							'Get a Foo or a Bar with a description.'
 						)
 					),
@@ -2858,7 +2837,6 @@ public function provideMultiLinePhpDocData(): array
 							null,
 							'methodWithNoReturnTypeWithDescription',
 							[],
-							[],
 							'Do something with a description but what, who knows!'
 						)
 					),
@@ -2868,7 +2846,6 @@ public function provideMultiLinePhpDocData(): array
 							true,
 							new IdentifierTypeNode('int'),
 							'getIntegerStaticallyWithDescription',
-							[],
 							[
 								new MethodTagValueParameterNode(
 									new IdentifierTypeNode('int'),
@@ -2894,7 +2871,6 @@ public function provideMultiLinePhpDocData(): array
 							true,
 							new IdentifierTypeNode('void'),
 							'doSomethingStaticallyWithDescription',
-							[],
 							[
 								new MethodTagValueParameterNode(
 									new IdentifierTypeNode('int'),
@@ -2924,7 +2900,6 @@ public function provideMultiLinePhpDocData(): array
 							]),
 							'getFooOrBarStaticallyWithDescription',
 							[],
-							[],
 							'Get a Foo or a Bar with a description statically.'
 						)
 					),
@@ -2935,7 +2910,6 @@ public function provideMultiLinePhpDocData(): array
 							new IdentifierTypeNode('static'),
 							'methodWithNoReturnTypeStaticallyWithDescription',
 							[],
-							[],
 							'Do something with a description statically, but what, who knows!'
 						)
 					),
@@ -2946,7 +2920,6 @@ public function provideMultiLinePhpDocData(): array
 							new IdentifierTypeNode('bool'),
 							'aStaticMethodThatHasAUniqueReturnTypeInThisClass',
 							[],
-							[],
 							''
 						)
 					),
@@ -2957,7 +2930,6 @@ public function provideMultiLinePhpDocData(): array
 							new IdentifierTypeNode('string'),
 							'aStaticMethodThatHasAUniqueReturnTypeInThisClassWithDescription',
 							[],
-							[],
 							'A Description.'
 						)
 					),
@@ -2968,7 +2940,6 @@ public function provideMultiLinePhpDocData(): array
 							new IdentifierTypeNode('int'),
 							'getIntegerNoParams',
 							[],
-							[],
 							''
 						)
 					),
@@ -2979,7 +2950,6 @@ public function provideMultiLinePhpDocData(): array
 							new IdentifierTypeNode('void'),
 							'doSomethingNoParams',
 							[],
-							[],
 							''
 						)
 					),
@@ -2993,7 +2963,6 @@ public function provideMultiLinePhpDocData(): array
 							]),
 							'getFooOrBarNoParams',
 							[],
-							[],
 							''
 						)
 					),
@@ -3004,7 +2973,6 @@ public function provideMultiLinePhpDocData(): array
 							null,
 							'methodWithNoReturnTypeNoParams',
 							[],
-							[],
 							''
 						)
 					),
@@ -3015,7 +2983,6 @@ public function provideMultiLinePhpDocData(): array
 							new IdentifierTypeNode('int'),
 							'getIntegerStaticallyNoParams',
 							[],
-							[],
 							''
 						)
 					),
@@ -3026,7 +2993,6 @@ public function provideMultiLinePhpDocData(): array
 							new IdentifierTypeNode('void'),
 							'doSomethingStaticallyNoParams',
 							[],
-							[],
 							''
 						)
 					),
@@ -3040,7 +3006,6 @@ public function provideMultiLinePhpDocData(): array
 							]),
 							'getFooOrBarStaticallyNoParams',
 							[],
-							[],
 							''
 						)
 					),
@@ -3051,7 +3016,6 @@ public function provideMultiLinePhpDocData(): array
 							new IdentifierTypeNode('static'),
 							'methodWithNoReturnTypeStaticallyNoParams',
 							[],
-							[],
 							''
 						)
 					),
@@ -3062,7 +3026,6 @@ public function provideMultiLinePhpDocData(): array
 							new IdentifierTypeNode('int'),
 							'getIntegerWithDescriptionNoParams',
 							[],
-							[],
 							'Get an integer with a description.'
 						)
 					),
@@ -3073,7 +3036,6 @@ public function provideMultiLinePhpDocData(): array
 							new IdentifierTypeNode('void'),
 							'doSomethingWithDescriptionNoParams',
 							[],
-							[],
 							'Do something with a description.'
 						)
 					),
@@ -3087,7 +3049,6 @@ public function provideMultiLinePhpDocData(): array
 							]),
 							'getFooOrBarWithDescriptionNoParams',
 							[],
-							[],
 							'Get a Foo or a Bar with a description.'
 						)
 					),
@@ -3098,7 +3059,6 @@ public function provideMultiLinePhpDocData(): array
 							new IdentifierTypeNode('int'),
 							'getIntegerStaticallyWithDescriptionNoParams',
 							[],
-							[],
 							'Get an integer with a description statically.'
 						)
 					),
@@ -3109,7 +3069,6 @@ public function provideMultiLinePhpDocData(): array
 							new IdentifierTypeNode('void'),
 							'doSomethingStaticallyWithDescriptionNoParams',
 							[],
-							[],
 							'Do something with a description statically.'
 						)
 					),
@@ -3123,7 +3082,6 @@ public function provideMultiLinePhpDocData(): array
 							]),
 							'getFooOrBarStaticallyWithDescriptionNoParams',
 							[],
-							[],
 							'Get a Foo or a Bar with a description statically.'
 						)
 					),
@@ -3137,7 +3095,6 @@ public function provideMultiLinePhpDocData(): array
 							]),
 							'aStaticMethodThatHasAUniqueReturnTypeInThisClassNoParams',
 							[],
-							[],
 							''
 						)
 					),
@@ -3151,7 +3108,6 @@ public function provideMultiLinePhpDocData(): array
 							]),
 							'aStaticMethodThatHasAUniqueReturnTypeInThisClassWithDescriptionNoParams',
 							[],
-							[],
 							'A Description.'
 						)
 					),
@@ -3161,7 +3117,6 @@ public function provideMultiLinePhpDocData(): array
 							false,
 							new IdentifierTypeNode('\\Aws\\Result'),
 							'publish',
-							[],
 							[
 								new MethodTagValueParameterNode(
 									new IdentifierTypeNode('array'),
@@ -3180,7 +3135,6 @@ public function provideMultiLinePhpDocData(): array
 							false,
 							new IdentifierTypeNode('Image'),
 							'rotate',
-							[],
 							[
 								new MethodTagValueParameterNode(
 									new IdentifierTypeNode('float'),
@@ -3207,7 +3161,6 @@ public function provideMultiLinePhpDocData(): array
 							new IdentifierTypeNode('Foo'),
 							'overridenMethod',
 							[],
-							[],
 							''
 						)
 					),
@@ -3238,7 +3191,6 @@ public function provideMultiLinePhpDocData(): array
 								new IdentifierTypeNode('null'),
 							]),
 							'find',
-							[],
 							[
 								new MethodTagValueParameterNode(
 									new IdentifierTypeNode('TValue'),

From e661949ecda58fd0b57e5264c8c940bd5e272cf4 Mon Sep 17 00:00:00 2001
From: Tomohito YABU <tyabu1212@gmail.com>
Date: Wed, 2 Nov 2022 02:22:09 +0900
Subject: [PATCH 3/7] Fix PHP 7.2 lint

---
 tests/PHPStan/Parser/PhpDocParserTest.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/PHPStan/Parser/PhpDocParserTest.php b/tests/PHPStan/Parser/PhpDocParserTest.php
index 5c9fa567..061bf027 100644
--- a/tests/PHPStan/Parser/PhpDocParserTest.php
+++ b/tests/PHPStan/Parser/PhpDocParserTest.php
@@ -2230,7 +2230,7 @@ public function provideMethodTagsData(): Iterator
 										new ConstExprStringNode('\'b\'')
 									),
 								]),
-							),
+							)
 						],
 						'',
 						[

From ba315abeed57c5b681967dc353288250b7beb6a3 Mon Sep 17 00:00:00 2001
From: Tomohito YABU <tyabu1212@gmail.com>
Date: Wed, 2 Nov 2022 02:26:11 +0900
Subject: [PATCH 4/7] Fix PHP 7.2 lint again

---
 tests/PHPStan/Parser/PhpDocParserTest.php | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/tests/PHPStan/Parser/PhpDocParserTest.php b/tests/PHPStan/Parser/PhpDocParserTest.php
index 061bf027..26b1d383 100644
--- a/tests/PHPStan/Parser/PhpDocParserTest.php
+++ b/tests/PHPStan/Parser/PhpDocParserTest.php
@@ -2229,8 +2229,8 @@ public function provideMethodTagsData(): Iterator
 										null,
 										new ConstExprStringNode('\'b\'')
 									),
-								]),
-							)
+								])
+							),
 						],
 						'',
 						[
@@ -2238,7 +2238,7 @@ public function provideMethodTagsData(): Iterator
 								'T',
 								null,
 								'',
-								new IdentifierTypeNode('string'),
+								new IdentifierTypeNode('string')
 							),
 						],
 					)
@@ -2284,7 +2284,7 @@ public function provideMethodTagsData(): Iterator
 							new TemplateTagValueNode('T1', null, ''),
 							new TemplateTagValueNode('T2', new IdentifierTypeNode('Bar'), ''),
 							new TemplateTagValueNode('T3', new IdentifierTypeNode('Baz'), ''),
-						],
+						]
 					)
 				)
 			])

From cbcd76b49bad0fc0e5a30eb2bec6a3333bfdcdd1 Mon Sep 17 00:00:00 2001
From: Tomohito YABU <tyabu1212@gmail.com>
Date: Wed, 2 Nov 2022 02:31:19 +0900
Subject: [PATCH 5/7] Fix PHP 7.2 lint again 2

---
 tests/PHPStan/Parser/PhpDocParserTest.php | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/tests/PHPStan/Parser/PhpDocParserTest.php b/tests/PHPStan/Parser/PhpDocParserTest.php
index 26b1d383..7980c67f 100644
--- a/tests/PHPStan/Parser/PhpDocParserTest.php
+++ b/tests/PHPStan/Parser/PhpDocParserTest.php
@@ -2240,7 +2240,7 @@ public function provideMethodTagsData(): Iterator
 								'',
 								new IdentifierTypeNode('string')
 							),
-						],
+						]
 					)
 				)
 			])
@@ -3200,7 +3200,7 @@ public function provideMultiLinePhpDocData(): array
 									null
 								),
 							],
-							'find index of $v',
+							'find index of $v'
 						)
 					),
 				]),

From 6838eed6c9d2b8107c313163a27b871147cc5d0c Mon Sep 17 00:00:00 2001
From: Tomohito YABU <tyabu1212@gmail.com>
Date: Wed, 2 Nov 2022 02:33:55 +0900
Subject: [PATCH 6/7] Fix coding standard errors

---
 src/Ast/PhpDoc/MethodTagValueNode.php     | 1 +
 tests/PHPStan/Parser/PhpDocParserTest.php | 8 ++++----
 2 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/src/Ast/PhpDoc/MethodTagValueNode.php b/src/Ast/PhpDoc/MethodTagValueNode.php
index 14859cda..075cec04 100644
--- a/src/Ast/PhpDoc/MethodTagValueNode.php
+++ b/src/Ast/PhpDoc/MethodTagValueNode.php
@@ -4,6 +4,7 @@
 
 use PHPStan\PhpDocParser\Ast\NodeAttributes;
 use PHPStan\PhpDocParser\Ast\Type\TypeNode;
+use function count;
 use function implode;
 
 class MethodTagValueNode implements PhpDocTagValueNode
diff --git a/tests/PHPStan/Parser/PhpDocParserTest.php b/tests/PHPStan/Parser/PhpDocParserTest.php
index 7980c67f..6a2657b9 100644
--- a/tests/PHPStan/Parser/PhpDocParserTest.php
+++ b/tests/PHPStan/Parser/PhpDocParserTest.php
@@ -2242,8 +2242,8 @@ public function provideMethodTagsData(): Iterator
 							),
 						]
 					)
-				)
-			])
+				),
+			]),
 		];
 
 		yield [
@@ -2286,8 +2286,8 @@ public function provideMethodTagsData(): Iterator
 							new TemplateTagValueNode('T3', new IdentifierTypeNode('Baz'), ''),
 						]
 					)
-				)
-			])
+				),
+			]),
 		];
 	}
 

From 96d3a2f7440df759bcdef851ccc51109eb1d040f Mon Sep 17 00:00:00 2001
From: Tomohito YABU <tyabu1212@gmail.com>
Date: Wed, 2 Nov 2022 08:11:46 +0900
Subject: [PATCH 7/7] Reuse parseTemplateTagValue

---
 src/Parser/PhpDocParser.php | 32 ++++++++------------------------
 1 file changed, 8 insertions(+), 24 deletions(-)

diff --git a/src/Parser/PhpDocParser.php b/src/Parser/PhpDocParser.php
index 965d9509..d9942b3d 100644
--- a/src/Parser/PhpDocParser.php
+++ b/src/Parser/PhpDocParser.php
@@ -182,7 +182,7 @@ public function parseTagValue(TokenIterator $tokens, string $tag): Ast\PhpDoc\Ph
 				case '@template-contravariant':
 				case '@phpstan-template-contravariant':
 				case '@psalm-template-contravariant':
-					$tagValue = $this->parseTemplateTagValue($tokens);
+					$tagValue = $this->parseTemplateTagValue($tokens, true);
 					break;
 
 				case '@extends':
@@ -349,7 +349,7 @@ private function parseMethodTagValue(TokenIterator $tokens): Ast\PhpDoc\MethodTa
 		$templateTypes = [];
 		if ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)) {
 			do {
-				$templateTypes[] = $this->parseMethodTagValueTemplateType($tokens);
+				$templateTypes[] = $this->parseTemplateTagValue($tokens, false);
 			} while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA));
 			$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET);
 		}
@@ -368,26 +368,6 @@ private function parseMethodTagValue(TokenIterator $tokens): Ast\PhpDoc\MethodTa
 		return new Ast\PhpDoc\MethodTagValueNode($isStatic, $returnType, $methodName, $parameters, $description, $templateTypes);
 	}
 
-	private function parseMethodTagValueTemplateType(TokenIterator $tokens): Ast\PhpDoc\TemplateTagValueNode
-	{
-		$name = $tokens->currentTokenValue();
-		$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
-
-		if ($tokens->tryConsumeTokenValue('of') || $tokens->tryConsumeTokenValue('as')) {
-			$bound = $this->typeParser->parse($tokens);
-		} else {
-			$bound = null;
-		}
-
-		if ($tokens->tryConsumeTokenValue('=')) {
-			$default = $this->typeParser->parse($tokens);
-		} else {
-			$default = null;
-		}
-
-		return new Ast\PhpDoc\TemplateTagValueNode($name, $bound, '', $default);
-	}
-
 	private function parseMethodTagValueParameter(TokenIterator $tokens): Ast\PhpDoc\MethodTagValueParameterNode
 	{
 		switch ($tokens->currentTokenType()) {
@@ -417,7 +397,7 @@ private function parseMethodTagValueParameter(TokenIterator $tokens): Ast\PhpDoc
 		return new Ast\PhpDoc\MethodTagValueParameterNode($parameterType, $isReference, $isVariadic, $parameterName, $defaultValue);
 	}
 
-	private function parseTemplateTagValue(TokenIterator $tokens): Ast\PhpDoc\TemplateTagValueNode
+	private function parseTemplateTagValue(TokenIterator $tokens, bool $parseDescription): Ast\PhpDoc\TemplateTagValueNode
 	{
 		$name = $tokens->currentTokenValue();
 		$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
@@ -435,7 +415,11 @@ private function parseTemplateTagValue(TokenIterator $tokens): Ast\PhpDoc\Templa
 			$default = null;
 		}
 
-		$description = $this->parseOptionalDescription($tokens);
+		if ($parseDescription) {
+			$description = $this->parseOptionalDescription($tokens);
+		} else {
+			$description = '';
+		}
 
 		return new Ast\PhpDoc\TemplateTagValueNode($name, $bound, $description, $default);
 	}