diff --git a/packages/PHPStanStaticTypeMapper/TypeMapper/ConditionalTypeMapper.php b/packages/PHPStanStaticTypeMapper/TypeMapper/ConditionalTypeMapper.php new file mode 100644 index 00000000000..3a9c9d547ec --- /dev/null +++ b/packages/PHPStanStaticTypeMapper/TypeMapper/ConditionalTypeMapper.php @@ -0,0 +1,68 @@ + + */ +final class ConditionalTypeMapper implements TypeMapperInterface +{ + private PHPStanStaticTypeMapper $phpStanStaticTypeMapper; + + #[Required] + public function autowire(PHPStanStaticTypeMapper $phpStanStaticTypeMapper): void + { + $this->phpStanStaticTypeMapper = $phpStanStaticTypeMapper; + } + + /** + * @return class-string + */ + public function getNodeClass(): string + { + return ConditionalType::class; + } + + /** + * @param ConditionalType $type + */ + public function mapToPHPStanPhpDocTypeNode(Type $type): TypeNode + { + $type = TypeTraverser::map($type, static function (Type $type, callable $traverse): Type { + if ($type instanceof ObjectType && !$type->getClassReflection() instanceof ClassReflection) { + $newClassName = (string) Strings::after($type->getClassName(), '\\', -1); + return $traverse(new ObjectType($newClassName)); + } + + return $traverse($type); + }); + + return $type->toPhpDocNode(); + } + + /** + * @param ConditionalType $type + * @param TypeKind::* $typeKind + */ + public function mapToPhpParserNode(Type $type, string $typeKind): ?Node + { + $type = TypeCombinator::union($type->getIf(), $type->getElse()); + return $this->phpStanStaticTypeMapper->mapToPhpParserNode($type, $typeKind); + } +} diff --git a/tests/Issues/IssueConditionalType/Fixture/fixture.php.inc b/tests/Issues/IssueConditionalType/Fixture/fixture.php.inc new file mode 100644 index 00000000000..664077f8aa2 --- /dev/null +++ b/tests/Issues/IssueConditionalType/Fixture/fixture.php.inc @@ -0,0 +1,35 @@ +|scalar + * + * @return (TValue is scalar ? array|scalar : array) + */ + public function resolveValue(): mixed + { + } +} + +?> +----- +|scalar + * + * @return (TValue is (bool | float | int | string) ? (array | bool | float | int | string) : array<(array | bool | float | int | string)>) + */ + public function resolveValue() + { + } +} + +?> diff --git a/tests/Issues/IssueConditionalType/IssueConditionalTypeTest.php b/tests/Issues/IssueConditionalType/IssueConditionalTypeTest.php new file mode 100644 index 00000000000..1a1d09fe9f9 --- /dev/null +++ b/tests/Issues/IssueConditionalType/IssueConditionalTypeTest.php @@ -0,0 +1,28 @@ +doTestFile($filePath); + } + + public static function provideData(): Iterator + { + return self::yieldFilesFromDirectory(__DIR__ . '/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__ . '/config/configured_rule.php'; + } +} diff --git a/tests/Issues/IssueConditionalType/config/configured_rule.php b/tests/Issues/IssueConditionalType/config/configured_rule.php new file mode 100644 index 00000000000..dc84ae67c1c --- /dev/null +++ b/tests/Issues/IssueConditionalType/config/configured_rule.php @@ -0,0 +1,10 @@ +rule(DowngradeMixedTypeDeclarationRector::class); +};