From 3291bf3a463f89889a444909460bfebbefa31ef0 Mon Sep 17 00:00:00 2001 From: Mathias Arlaud Date: Wed, 12 Mar 2025 16:17:05 +0100 Subject: [PATCH] [TypeInfo] Fix promoted property with `@var` tag --- .../Tests/Fixtures/DummyWithPhpDoc.php | 9 ++++ .../PhpDocAwareReflectionTypeResolverTest.php | 2 + .../PhpDocAwareReflectionTypeResolver.php | 48 ++++++++++--------- 3 files changed, 36 insertions(+), 23 deletions(-) diff --git a/src/Symfony/Component/TypeInfo/Tests/Fixtures/DummyWithPhpDoc.php b/src/Symfony/Component/TypeInfo/Tests/Fixtures/DummyWithPhpDoc.php index 30141f95c3d0f..035457d12a95f 100644 --- a/src/Symfony/Component/TypeInfo/Tests/Fixtures/DummyWithPhpDoc.php +++ b/src/Symfony/Component/TypeInfo/Tests/Fixtures/DummyWithPhpDoc.php @@ -11,9 +11,18 @@ final class DummyWithPhpDoc /** * @param bool $promoted + * @param bool $promotedVarAndParam */ public function __construct( public mixed $promoted, + /** + * @var string + */ + public mixed $promotedVar, + /** + * @var string + */ + public mixed $promotedVarAndParam, ) { } diff --git a/src/Symfony/Component/TypeInfo/Tests/TypeResolver/PhpDocAwareReflectionTypeResolverTest.php b/src/Symfony/Component/TypeInfo/Tests/TypeResolver/PhpDocAwareReflectionTypeResolverTest.php index 7e92638a9ce38..996b04ff11238 100644 --- a/src/Symfony/Component/TypeInfo/Tests/TypeResolver/PhpDocAwareReflectionTypeResolverTest.php +++ b/src/Symfony/Component/TypeInfo/Tests/TypeResolver/PhpDocAwareReflectionTypeResolverTest.php @@ -29,6 +29,8 @@ public function testReadPhpDoc() $this->assertEquals(Type::array(Type::object(Dummy::class)), $resolver->resolve($reflection->getProperty('arrayOfDummies'))); $this->assertEquals(Type::bool(), $resolver->resolve($reflection->getProperty('promoted'))); + $this->assertEquals(Type::string(), $resolver->resolve($reflection->getProperty('promotedVar'))); + $this->assertEquals(Type::string(), $resolver->resolve($reflection->getProperty('promotedVarAndParam'))); $this->assertEquals(Type::object(Dummy::class), $resolver->resolve($reflection->getMethod('getNextDummy'))); $this->assertEquals(Type::object(Dummy::class), $resolver->resolve($reflection->getMethod('getNextDummy')->getParameters()[0])); } diff --git a/src/Symfony/Component/TypeInfo/TypeResolver/PhpDocAwareReflectionTypeResolver.php b/src/Symfony/Component/TypeInfo/TypeResolver/PhpDocAwareReflectionTypeResolver.php index 9f71ee4bc2ed8..24aa20c7d7d72 100644 --- a/src/Symfony/Component/TypeInfo/TypeResolver/PhpDocAwareReflectionTypeResolver.php +++ b/src/Symfony/Component/TypeInfo/TypeResolver/PhpDocAwareReflectionTypeResolver.php @@ -64,36 +64,38 @@ public function resolve(mixed $subject, ?TypeContext $typeContext = null): Type throw new UnsupportedException(\sprintf('Expected subject to be a "ReflectionProperty", a "ReflectionParameter" or a "ReflectionFunctionAbstract", "%s" given.', get_debug_type($subject)), $subject); } - $docComment = match (true) { - $subject instanceof \ReflectionProperty => $subject->isPromoted() ? $subject->getDeclaringClass()?->getConstructor()?->getDocComment() : $subject->getDocComment(), - $subject instanceof \ReflectionParameter => $subject->getDeclaringFunction()->getDocComment(), - $subject instanceof \ReflectionFunctionAbstract => $subject->getDocComment(), + $typeContext ??= $this->typeContextFactory->createFromReflection($subject); + + $docComments = match (true) { + $subject instanceof \ReflectionProperty => $subject->isPromoted() + ? ['@var' => $subject->getDocComment(), '@param' => $subject->getDeclaringClass()?->getConstructor()?->getDocComment()] + : ['@var' => $subject->getDocComment()], + $subject instanceof \ReflectionParameter => ['@param' => $subject->getDeclaringFunction()->getDocComment()], + $subject instanceof \ReflectionFunctionAbstract => ['@return' => $subject->getDocComment()], }; - if (!$docComment) { - return $this->reflectionTypeResolver->resolve($subject); - } + foreach ($docComments as $tagName => $docComment) { + if (!$docComment) { + continue; + } - $typeContext ??= $this->typeContextFactory->createFromReflection($subject); + $tokens = new TokenIterator($this->lexer->tokenize($docComment)); + $docNode = $this->phpDocParser->parse($tokens); - $tagName = match (true) { - $subject instanceof \ReflectionProperty => $subject->isPromoted() ? '@param' : '@var', - $subject instanceof \ReflectionParameter => '@param', - $subject instanceof \ReflectionFunctionAbstract => '@return', - }; + foreach ($docNode->getTagsByName($tagName) as $tag) { + $tagValue = $tag->value; - $tokens = new TokenIterator($this->lexer->tokenize($docComment)); - $docNode = $this->phpDocParser->parse($tokens); + if ('@var' === $tagName && $tagValue instanceof VarTagValueNode) { + return $this->stringTypeResolver->resolve((string) $tagValue, $typeContext); + } - foreach ($docNode->getTagsByName($tagName) as $tag) { - $tagValue = $tag->value; + if ('@param' === $tagName && $tagValue instanceof ParamTagValueNode && '$'.$subject->getName() === $tagValue->parameterName) { + return $this->stringTypeResolver->resolve((string) $tagValue, $typeContext); + } - if ( - $tagValue instanceof VarTagValueNode - || $tagValue instanceof ParamTagValueNode && $tagName && '$'.$subject->getName() === $tagValue->parameterName - || $tagValue instanceof ReturnTagValueNode - ) { - return $this->stringTypeResolver->resolve((string) $tagValue, $typeContext); + if ('@return' === $tagName && $tagValue instanceof ReturnTagValueNode) { + return $this->stringTypeResolver->resolve((string) $tagValue, $typeContext); + } } }