diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index 3e7f5a1935f..f1d3af64aa0 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -206,6 +206,8 @@ public function describe(VerbosityLevel $level): string foreach ($types as $type) { if ($type instanceof ClosureType || $type instanceof CallableType || $type instanceof TemplateUnionType) { $typeNames[] = sprintf('(%s)', $type->describe($level)); + } elseif ($type instanceof TemplateType && ($level->isTypeOnly() || $level->isValue())) { + $typeNames[] = sprintf('(%s)', $type->describe($level)); } elseif ($type instanceof IntersectionType) { $intersectionDescription = $type->describe($level); if (strpos($intersectionDescription, '&') !== false) { diff --git a/src/Type/VerbosityLevel.php b/src/Type/VerbosityLevel.php index fe3bfc06106..304f8c1de31 100644 --- a/src/Type/VerbosityLevel.php +++ b/src/Type/VerbosityLevel.php @@ -60,6 +60,11 @@ public function isTypeOnly(): bool return $this->value === self::TYPE_ONLY; } + public function isValue(): bool + { + return $this->value === self::VALUE; + } + /** @api */ public static function getRecommendedLevelByType(Type $acceptingType, ?Type $acceptedType = null): self { diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 0b4b314dbf2..171a6416dd0 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -591,7 +591,7 @@ public function testBug6842(): void { $errors = $this->runAnalyse(__DIR__ . '/data/bug-6842.php'); $this->assertCount(1, $errors); - $this->assertSame('Generator expects value type T of DateTimeInterface, DateTime|DateTimeImmutable|T of DateTimeInterface given.', $errors[0]->getMessage()); + $this->assertSame('Generator expects value type T of DateTimeInterface, DateTime|DateTimeImmutable|(T of DateTimeInterface given).', $errors[0]->getMessage()); $this->assertSame(28, $errors[0]->getLine()); } @@ -859,6 +859,14 @@ public function testBug7275(): void $this->assertNoErrors($errors); } + public function testBug7484(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-7484.php'); + $this->assertCount(1, $errors); + $this->assertSame('Generator expects key type K of int|string, (K of int)|string given.', $errors[0]->getMessage()); + $this->assertSame(21, $errors[0]->getLine()); + } + /** * @param string[]|null $allAnalysedFiles * @return Error[] diff --git a/tests/PHPStan/Analyser/data/bug-7484.php b/tests/PHPStan/Analyser/data/bug-7484.php new file mode 100644 index 00000000000..f4d20cb93bc --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-7484.php @@ -0,0 +1,23 @@ + $iterable + * @return iterable + */ +function changeKeyCase( + iterable $iterable, + int $case = CASE_LOWER, +): iterable { + $callable = $case === CASE_LOWER ? 'strtolower' : 'strtoupper'; + foreach ($iterable as $key => $value) { + if (is_string($key)) { + $key = $callable($key); + } + + yield $key => $value; + } +}