diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php b/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php index 7b6cd0f4a7e..ece6018a441 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php @@ -143,6 +143,7 @@ public function __construct( /** * @return false|null + * @psalm-suppress ComplexMethod */ public function start(PhpParser\Node\Stmt\ClassLike $node): ?bool { @@ -420,10 +421,17 @@ public function start(PhpParser\Node\Stmt\ClassLike $node): ?bool if ($template_map[1] !== null && $template_map[2] !== null) { if (trim($template_map[2])) { + $type_string = $template_map[2]; + try { + $type_string = CommentAnalyzer::splitDocLine($type_string)[0]; + } catch (DocblockParseException $e) { + throw new DocblockParseException($type_string . ' is not a valid type: '.$e->getMessage()); + } + $type_string = CommentAnalyzer::sanitizeDocblockType($type_string); try { $template_type = TypeParser::parseTokens( TypeTokenizer::getFullyQualifiedTokens( - $template_map[2], + $type_string, $this->aliases, $storage->template_types, $this->type_aliases, diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockScanner.php b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockScanner.php index b85ace5e279..9f9a477cb4d 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockScanner.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockScanner.php @@ -9,8 +9,10 @@ use Psalm\CodeLocation\DocblockTypeLocation; use Psalm\Codebase; use Psalm\Config; +use Psalm\Exception\DocblockParseException; use Psalm\Exception\InvalidMethodOverrideException; use Psalm\Exception\TypeParseTreeException; +use Psalm\Internal\Analyzer\CommentAnalyzer; use Psalm\Internal\Analyzer\NamespaceAnalyzer; use Psalm\Internal\Scanner\FileScanner; use Psalm\Internal\Scanner\FunctionDocblockComment; @@ -1440,10 +1442,17 @@ private static function handleTemplates( if ($template_map[1] !== null && $template_map[2] !== null) { if (trim($template_map[2])) { + $type_string = $template_map[2]; + try { + $type_string = CommentAnalyzer::splitDocLine($type_string)[0]; + } catch (DocblockParseException $e) { + throw new DocblockParseException($type_string . ' is not a valid type: '.$e->getMessage()); + } + $type_string = CommentAnalyzer::sanitizeDocblockType($type_string); try { $template_type = TypeParser::parseTokens( TypeTokenizer::getFullyQualifiedTokens( - $template_map[2], + $type_string, $aliases, $storage->template_types + ($template_types ?: []), $type_aliases, diff --git a/tests/Template/ClassTemplateTest.php b/tests/Template/ClassTemplateTest.php index 4105b6dfb12..912b565f282 100644 --- a/tests/Template/ClassTemplateTest.php +++ b/tests/Template/ClassTemplateTest.php @@ -4051,6 +4051,64 @@ public function __construct( 'ignored_issues' => [], 'php_version' => '8.0', ], + 'template of simple type with additional comment without dot' => [ + 'code' => 't = $t; + } + + /** + * @psalm-return T + */ + public function t(): string { + return $this->t; + } + } + $t = (new Foo(\'\'))->t(); + ', + 'assertions' => [ + '$t===' => '\'\'', + ], + ], + 'template of simple type with additional comment with dot' => [ + 'code' => 't = $t; + } + + /** + * @psalm-return T + */ + public function t(): string { + return $this->t; + } + } + $t = (new Foo(\'\'))->t(); + ', + 'assertions' => [ + '$t===' => '\'\'', + ], + ], ]; } diff --git a/tests/Template/FunctionTemplateTest.php b/tests/Template/FunctionTemplateTest.php index 1a99b80e0cf..ba72c2604d2 100644 --- a/tests/Template/FunctionTemplateTest.php +++ b/tests/Template/FunctionTemplateTest.php @@ -1659,6 +1659,20 @@ function normalizeField(mixed $value, Norm $n): void 'ignored_issues' => [], 'php_version' => '8.0', ], + 'templateWithCommentAfterSimpleType' => [ + 'code' => '