diff --git a/.github/workflows/backward-compatibility.yml b/.github/workflows/backward-compatibility.yml
index a7abd897..1cd62723 100644
--- a/.github/workflows/backward-compatibility.yml
+++ b/.github/workflows/backward-compatibility.yml
@@ -6,7 +6,7 @@ on:
   pull_request:
   push:
     branches:
-      - "1.9.x"
+      - "1.21.x"
 
 jobs:
   backward-compatibility:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index b6ed9990..19401af3 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -6,7 +6,7 @@ on:
   pull_request:
   push:
     branches:
-      - "1.9.x"
+      - "1.21.x"
 
 jobs:
   lint:
diff --git a/.github/workflows/test-slevomat-coding-standard.yml b/.github/workflows/test-slevomat-coding-standard.yml
index cc9e1b0b..ab90c303 100644
--- a/.github/workflows/test-slevomat-coding-standard.yml
+++ b/.github/workflows/test-slevomat-coding-standard.yml
@@ -6,7 +6,7 @@ on:
   pull_request:
   push:
     branches:
-      - "1.9.x"
+      - "1.21.x"
 
 jobs:
   tests:
diff --git a/build-cs/composer.lock b/build-cs/composer.lock
index 0cea3f9e..c59ffc5f 100644
--- a/build-cs/composer.lock
+++ b/build-cs/composer.lock
@@ -154,16 +154,16 @@
         },
         {
             "name": "phpstan/phpdoc-parser",
-            "version": "1.20.2",
+            "version": "1.20.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phpstan/phpdoc-parser.git",
-                "reference": "90490bd8fd8530a272043c4950c180b6d0cf5f81"
+                "reference": "6c04009f6cae6eda2f040745b6b846080ef069c2"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/90490bd8fd8530a272043c4950c180b6d0cf5f81",
-                "reference": "90490bd8fd8530a272043c4950c180b6d0cf5f81",
+                "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6c04009f6cae6eda2f040745b6b846080ef069c2",
+                "reference": "6c04009f6cae6eda2f040745b6b846080ef069c2",
                 "shasum": ""
             },
             "require": {
@@ -193,22 +193,22 @@
             "description": "PHPDoc parser with support for nullable, intersection and generic types",
             "support": {
                 "issues": "https://github.com/phpstan/phpdoc-parser/issues",
-                "source": "https://github.com/phpstan/phpdoc-parser/tree/1.20.2"
+                "source": "https://github.com/phpstan/phpdoc-parser/tree/1.20.3"
             },
-            "time": "2023-04-22T12:59:35+00:00"
+            "time": "2023-04-25T09:01:03+00:00"
         },
         {
             "name": "slevomat/coding-standard",
-            "version": "8.11.0",
+            "version": "8.11.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/slevomat/coding-standard.git",
-                "reference": "91428d5bcf7db93a842bcf97f465edf62527f3ea"
+                "reference": "af87461316b257e46e15bb041dca6fca3796d822"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/91428d5bcf7db93a842bcf97f465edf62527f3ea",
-                "reference": "91428d5bcf7db93a842bcf97f465edf62527f3ea",
+                "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/af87461316b257e46e15bb041dca6fca3796d822",
+                "reference": "af87461316b257e46e15bb041dca6fca3796d822",
                 "shasum": ""
             },
             "require": {
@@ -248,7 +248,7 @@
             ],
             "support": {
                 "issues": "https://github.com/slevomat/coding-standard/issues",
-                "source": "https://github.com/slevomat/coding-standard/tree/8.11.0"
+                "source": "https://github.com/slevomat/coding-standard/tree/8.11.1"
             },
             "funding": [
                 {
@@ -260,7 +260,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2023-04-21T15:51:44+00:00"
+            "time": "2023-04-24T08:19:01+00:00"
         },
         {
             "name": "squizlabs/php_codesniffer",
diff --git a/composer.json b/composer.json
index 30b879b7..aab3969f 100644
--- a/composer.json
+++ b/composer.json
@@ -12,8 +12,8 @@
 		"phpstan/phpstan": "^1.5",
 		"phpstan/phpstan-phpunit": "^1.1",
 		"phpstan/phpstan-strict-rules": "^1.0",
-		"phpunit/phpunit": "^9.5",
-		"symfony/process": "^5.2"
+		"phpunit/phpunit": "^10.0",
+		"symfony/process": "^6.0"
 	},
 	"config": {
 		"platform": {
diff --git a/src/Parser/PhpDocParser.php b/src/Parser/PhpDocParser.php
index 7f0e4a44..d3eed465 100644
--- a/src/Parser/PhpDocParser.php
+++ b/src/Parser/PhpDocParser.php
@@ -333,9 +333,7 @@ public function parseTagValue(TokenIterator $tokens, string $tag): Ast\PhpDoc\Ph
 	private function parseParamTagValue(TokenIterator $tokens): Ast\PhpDoc\PhpDocTagValueNode
 	{
 		if (
-			$tokens->isCurrentTokenType(Lexer::TOKEN_REFERENCE)
-			|| $tokens->isCurrentTokenType(Lexer::TOKEN_VARIADIC)
-			|| $tokens->isCurrentTokenType(Lexer::TOKEN_VARIABLE)
+			$tokens->isCurrentTokenType(Lexer::TOKEN_REFERENCE, Lexer::TOKEN_VARIADIC, Lexer::TOKEN_VARIABLE)
 		) {
 			$type = null;
 		} else {
diff --git a/src/Parser/TypeParser.php b/src/Parser/TypeParser.php
index ff62fca3..4b429809 100644
--- a/src/Parser/TypeParser.php
+++ b/src/Parser/TypeParser.php
@@ -522,47 +522,126 @@ private function parseCallableReturnType(TokenIterator $tokens): Ast\Type\TypeNo
 		$startLine = $tokens->currentTokenLine();
 		$startIndex = $tokens->currentTokenIndex();
 		if ($tokens->isCurrentTokenType(Lexer::TOKEN_NULLABLE)) {
-			$type = $this->parseNullable($tokens);
+			return $this->parseNullable($tokens);
 
 		} elseif ($tokens->tryConsumeTokenType(Lexer::TOKEN_OPEN_PARENTHESES)) {
 			$type = $this->parse($tokens);
 			$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_PARENTHESES);
+			if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
+				$type = $this->tryParseArrayOrOffsetAccess($tokens, $type);
+			}
 
-		} 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,
-					$this->enrichWithAttributes(
-						$tokens,
-						$type,
-						$startLine,
-						$startIndex
-					)
-				);
-
-			} elseif (in_array($type->name, ['array', 'list'], true) && $tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET) && !$tokens->isPrecededByHorizontalWhitespace()) {
-				$type = $this->parseArrayShape($tokens, $this->enrichWithAttributes(
+			return $type;
+		} elseif ($tokens->tryConsumeTokenType(Lexer::TOKEN_THIS_VARIABLE)) {
+			$type = new Ast\Type\ThisTypeNode();
+			if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
+				$type = $this->tryParseArrayOrOffsetAccess($tokens, $this->enrichWithAttributes(
 					$tokens,
 					$type,
 					$startLine,
 					$startIndex
-				), $type->name);
+				));
+			}
+
+			return $type;
+		} else {
+			$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)) {
+					if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)) {
+						$type = $this->parseGeneric(
+							$tokens,
+							$this->enrichWithAttributes(
+								$tokens,
+								$type,
+								$startLine,
+								$startIndex
+							)
+						);
+						if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
+							$type = $this->tryParseArrayOrOffsetAccess($tokens, $this->enrichWithAttributes(
+								$tokens,
+								$type,
+								$startLine,
+								$startIndex
+							));
+						}
+
+					} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
+						$type = $this->tryParseArrayOrOffsetAccess($tokens, $this->enrichWithAttributes(
+							$tokens,
+							$type,
+							$startLine,
+							$startIndex
+						));
+
+					} elseif (in_array($type->name, ['array', 'list', 'object'], true) && $tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET) && !$tokens->isPrecededByHorizontalWhitespace()) {
+						if ($type->name === 'object') {
+							$type = $this->parseObjectShape($tokens);
+						} else {
+							$type = $this->parseArrayShape($tokens, $this->enrichWithAttributes(
+								$tokens,
+								$type,
+								$startLine,
+								$startIndex
+							), $type->name);
+						}
+
+						if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
+							$type = $this->tryParseArrayOrOffsetAccess($tokens, $this->enrichWithAttributes(
+								$tokens,
+								$type,
+								$startLine,
+								$startIndex
+							));
+						}
+					}
+
+					return $type;
+				} else {
+					$tokens->rollback(); // because of ConstFetchNode
+				}
+			} else {
+				$tokens->dropSavePoint(); // because of ConstFetchNode
 			}
 		}
 
-		if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
-			$type = $this->tryParseArrayOrOffsetAccess($tokens, $this->enrichWithAttributes(
-				$tokens,
-				$type,
-				$startLine,
-				$startIndex
-			));
+		$exception = new ParserException(
+			$tokens->currentTokenValue(),
+			$tokens->currentTokenType(),
+			$tokens->currentTokenOffset(),
+			Lexer::TOKEN_IDENTIFIER,
+			null,
+			$tokens->currentTokenLine()
+		);
+
+		if ($this->constExprParser === null) {
+			throw $exception;
 		}
 
-		return $type;
+		try {
+			$constExpr = $this->constExprParser->parse($tokens, true);
+			if ($constExpr instanceof Ast\ConstExpr\ConstExprArrayNode) {
+				throw $exception;
+			}
+
+			$type = new Ast\Type\ConstTypeNode($constExpr);
+			if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
+				$type = $this->tryParseArrayOrOffsetAccess($tokens, $this->enrichWithAttributes(
+					$tokens,
+					$type,
+					$startLine,
+					$startIndex
+				));
+			}
+
+			return $type;
+		} catch (LogicException $e) {
+			throw $exception;
+		}
 	}
 
 
diff --git a/tests/PHPStan/Parser/TypeParserTest.php b/tests/PHPStan/Parser/TypeParserTest.php
index 2af4e1d4..74a27be4 100644
--- a/tests/PHPStan/Parser/TypeParserTest.php
+++ b/tests/PHPStan/Parser/TypeParserTest.php
@@ -770,6 +770,22 @@ public function provideParseData(): array
 					)
 				),
 			],
+			[
+				'callable(): Foo<Bar>[]',
+				new CallableTypeNode(
+					new IdentifierTypeNode('callable'),
+					[],
+					new ArrayTypeNode(new GenericTypeNode(
+						new IdentifierTypeNode('Foo'),
+						[
+							new IdentifierTypeNode('Bar'),
+						],
+						[
+							GenericTypeNode::VARIANCE_INVARIANT,
+						]
+					))
+				),
+			],
 			[
 				'callable(): Foo|Bar',
 				new UnionTypeNode([
@@ -1956,6 +1972,77 @@ public function provideParseData(): array
 				'callable(): ?int',
 				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new NullableTypeNode(new IdentifierTypeNode('int'))),
 			],
+			[
+				'callable(): object{foo: int}',
+				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ObjectShapeNode([
+					new ObjectShapeItemNode(new IdentifierTypeNode('foo'), false, new IdentifierTypeNode('int')),
+				])),
+			],
+			[
+				'callable(): object{foo: int}[]',
+				new CallableTypeNode(
+					new IdentifierTypeNode('callable'),
+					[],
+					new ArrayTypeNode(
+						new ObjectShapeNode([
+							new ObjectShapeItemNode(new IdentifierTypeNode('foo'), false, new IdentifierTypeNode('int')),
+						])
+					)
+				),
+			],
+			[
+				'callable(): $this',
+				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ThisTypeNode()),
+			],
+			[
+				'callable(): $this[]',
+				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ArrayTypeNode(new ThisTypeNode())),
+			],
+			[
+				'2.5|3',
+				new UnionTypeNode([
+					new ConstTypeNode(new ConstExprFloatNode('2.5')),
+					new ConstTypeNode(new ConstExprIntegerNode('3')),
+				]),
+			],
+			[
+				'callable(): 3.5',
+				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ConstTypeNode(new ConstExprFloatNode('3.5'))),
+			],
+			[
+				'callable(): 3.5[]',
+				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ArrayTypeNode(
+					new ConstTypeNode(new ConstExprFloatNode('3.5'))
+				)),
+			],
+			[
+				'callable(): Foo',
+				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new IdentifierTypeNode('Foo')),
+			],
+			[
+				'callable(): (Foo)[]',
+				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ArrayTypeNode(new IdentifierTypeNode('Foo'))),
+			],
+			[
+				'callable(): Foo::BAR',
+				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ConstTypeNode(new ConstFetchNode('Foo', 'BAR'))),
+			],
+			[
+				'callable(): Foo::*',
+				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new ConstTypeNode(new ConstFetchNode('Foo', '*'))),
+			],
+			[
+				'?Foo[]',
+				new NullableTypeNode(new ArrayTypeNode(new IdentifierTypeNode('Foo'))),
+			],
+			[
+				'callable(): ?Foo',
+				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new NullableTypeNode(new IdentifierTypeNode('Foo'))),
+			],
+			[
+				'callable(): ?Foo[]',
+				new CallableTypeNode(new IdentifierTypeNode('callable'), [], new NullableTypeNode(new ArrayTypeNode(new IdentifierTypeNode('Foo')))),
+			],
 		];
 	}
 
diff --git a/tests/PHPStan/Printer/PrinterTest.php b/tests/PHPStan/Printer/PrinterTest.php
index b689b440..ea236b75 100644
--- a/tests/PHPStan/Printer/PrinterTest.php
+++ b/tests/PHPStan/Printer/PrinterTest.php
@@ -26,6 +26,7 @@
 use PHPStan\PhpDocParser\Ast\Type\ObjectShapeItemNode;
 use PHPStan\PhpDocParser\Ast\Type\ObjectShapeNode;
 use PHPStan\PhpDocParser\Ast\Type\OffsetAccessTypeNode;
+use PHPStan\PhpDocParser\Ast\Type\TypeNode;
 use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
 use PHPStan\PhpDocParser\Lexer\Lexer;
 use PHPStan\PhpDocParser\Parser\ConstExprParser;
@@ -42,6 +43,26 @@
 class PrinterTest extends TestCase
 {
 
+	/** @var TypeParser */
+	private $typeParser;
+
+	/** @var PhpDocParser */
+	private $phpDocParser;
+
+	protected function setUp(): void
+	{
+		$usedAttributes = ['lines' => true, 'indexes' => true];
+		$constExprParser = new ConstExprParser(true, true, $usedAttributes);
+		$this->typeParser = new TypeParser($constExprParser, true, $usedAttributes);
+		$this->phpDocParser = new PhpDocParser(
+			$this->typeParser,
+			$constExprParser,
+			true,
+			true,
+			$usedAttributes
+		);
+	}
+
 	/**
 	 * @return iterable<array{string, string, NodeVisitor}>
 	 */
@@ -1153,18 +1174,9 @@ public function enterNode(Node $node)
 	 */
 	public function testPrintFormatPreserving(string $phpDoc, string $expectedResult, NodeVisitor $visitor): void
 	{
-		$usedAttributes = ['lines' => true, 'indexes' => true];
-		$constExprParser = new ConstExprParser(true, true, $usedAttributes);
-		$phpDocParser = new PhpDocParser(
-			new TypeParser($constExprParser, true, $usedAttributes),
-			$constExprParser,
-			true,
-			true,
-			$usedAttributes
-		);
 		$lexer = new Lexer();
 		$tokens = new TokenIterator($lexer->tokenize($phpDoc));
-		$phpDocNode = $phpDocParser->parse($tokens);
+		$phpDocNode = $this->phpDocParser->parse($tokens);
 		$cloningTraverser = new NodeTraverser([new NodeVisitor\CloningVisitor()]);
 		$newNodes = $cloningTraverser->traverse([$phpDocNode]);
 
@@ -1177,14 +1189,13 @@ public function testPrintFormatPreserving(string $phpDoc, string $expectedResult
 		$newPhpDoc = $printer->printFormatPreserving($newNode, $phpDocNode, $tokens);
 		$this->assertSame($expectedResult, $newPhpDoc);
 
-		$newTokens = new TokenIterator($lexer->tokenize($newPhpDoc));
 		$this->assertEquals(
 			$this->unsetAttributes($newNode),
-			$this->unsetAttributes($phpDocParser->parse($newTokens))
+			$this->unsetAttributes($this->phpDocParser->parse(new TokenIterator($lexer->tokenize($newPhpDoc))))
 		);
 	}
 
-	private function unsetAttributes(PhpDocNode $node): PhpDocNode
+	private function unsetAttributes(Node $node): Node
 	{
 		$visitor = new class extends AbstractNodeVisitor {
 
@@ -1207,4 +1218,67 @@ public function enterNode(Node $node)
 		return $traverser->traverse([$node])[0];
 	}
 
+	/**
+	 * @return iterable<list{TypeNode, string}>
+	 */
+	public function dataPrintType(): iterable
+	{
+		yield [
+			new IdentifierTypeNode('int'),
+			'int',
+		];
+	}
+
+	/**
+	 * @dataProvider dataPrintType
+	 */
+	public function testPrintType(TypeNode $node, string $expectedResult): void
+	{
+		$printer = new Printer();
+		$phpDoc = $printer->print($node);
+		$this->assertSame($expectedResult, $phpDoc);
+
+		$lexer = new Lexer();
+		$this->assertEquals(
+			$this->unsetAttributes($node),
+			$this->unsetAttributes($this->typeParser->parse(new TokenIterator($lexer->tokenize($phpDoc))))
+		);
+	}
+
+	/**
+	 * @return iterable<list{PhpDocNode, string}>
+	 */
+	public function dataPrintPhpDocNode(): iterable
+	{
+		yield [
+			new PhpDocNode([
+				new PhpDocTagNode('@param', new ParamTagValueNode(
+					new IdentifierTypeNode('int'),
+					false,
+					'$a',
+					''
+				)),
+			]),
+			'/**
+ * @param int $a
+ */',
+		];
+	}
+
+	/**
+	 * @dataProvider dataPrintPhpDocNode
+	 */
+	public function testPrintPhpDocNode(PhpDocNode $node, string $expectedResult): void
+	{
+		$printer = new Printer();
+		$phpDoc = $printer->print($node);
+		$this->assertSame($expectedResult, $phpDoc);
+
+		$lexer = new Lexer();
+		$this->assertEquals(
+			$this->unsetAttributes($node),
+			$this->unsetAttributes($this->phpDocParser->parse(new TokenIterator($lexer->tokenize($phpDoc))))
+		);
+	}
+
 }