https://github.com/tcottin created https://github.com/llvm/llvm-project/pull/174723
The begin source location for function templates is determined by the source location of the template keyword. Pure abbreviated function templates do not have the template keyword. This results in an invalid begin source location for abbreviated function templates. Without a valid begin source location, comments cannot be attached to the function template which leads to the bug described in clangd/clangd#2565. This patch overrides the `getSourceRange` method for `FunctionTemplateDecl` to use the begin source location of the `FunctionDecl` for abbreviated function templates. >From 6ea9e712b6948106c219ff44778cc51902b783ea Mon Sep 17 00:00:00 2001 From: Tim Cottin <[email protected]> Date: Wed, 7 Jan 2026 09:10:53 +0000 Subject: [PATCH] [clang] Add a valid begin source location for abbreviated function templates --- .../clangd/unittests/HoverTests.cpp | 61 ++++ clang/include/clang/AST/DeclTemplate.h | 13 + .../ast-dump-record-definition-data-json.cpp | 18 +- clang/test/AST/ast-dump-templates.cpp | 295 ++++++++++++++++++ 4 files changed, 384 insertions(+), 3 deletions(-) diff --git a/clang-tools-extra/clangd/unittests/HoverTests.cpp b/clang-tools-extra/clangd/unittests/HoverTests.cpp index eb858ff616e90..1d1da620857fe 100644 --- a/clang-tools-extra/clangd/unittests/HoverTests.cpp +++ b/clang-tools-extra/clangd/unittests/HoverTests.cpp @@ -57,6 +57,67 @@ TEST(Hover, Structured) { HI.Type = "void ()"; HI.Parameters.emplace(); }}, + {R"cpp( + // Best foo ever. + void [[fo^o]](auto x) {} + )cpp", + [](HoverInfo &HI) { + HI.NamespaceScope = ""; + HI.Name = "foo"; + HI.Kind = index::SymbolKind::Function; + HI.Documentation = "Best foo ever."; + HI.Definition = "void foo(auto x)"; + HI.ReturnType = "void"; + HI.Type = "void (auto)"; + HI.TemplateParameters = { + {{"class"}, std::string("x:auto"), std::nullopt}, + }; + HI.Parameters = { + {{"auto"}, std::string("x"), std::nullopt}, + }; + }}, + {R"cpp( + // Best foo ever. + template <class T> + void [[fo^o]](T x) {} + )cpp", + [](HoverInfo &HI) { + HI.NamespaceScope = ""; + HI.Name = "foo"; + HI.Kind = index::SymbolKind::Function; + HI.Documentation = "Best foo ever."; + HI.Definition = "template <class T> void foo(T x)"; + HI.ReturnType = "void"; + HI.Type = "void (T)"; + HI.TemplateParameters = { + {{"class"}, std::string("T"), std::nullopt}, + }; + HI.Parameters = { + {{"T"}, std::string("x"), std::nullopt}, + }; + }}, + {R"cpp( + // Best foo ever. + template <class T> + void [[fo^o]](T x, auto y) {} + )cpp", + [](HoverInfo &HI) { + HI.NamespaceScope = ""; + HI.Name = "foo"; + HI.Kind = index::SymbolKind::Function; + HI.Documentation = "Best foo ever."; + HI.Definition = "template <class T> void foo(T x, auto y)"; + HI.ReturnType = "void"; + HI.Type = "void (T, auto)"; + HI.TemplateParameters = { + {{"class"}, std::string("T"), std::nullopt}, + {{"class"}, std::string("y:auto"), std::nullopt}, + }; + HI.Parameters = { + {{"T"}, std::string("x"), std::nullopt}, + {{"auto"}, std::string("y"), std::nullopt}, + }; + }}, // Inside namespace {R"cpp( namespace ns1 { namespace ns2 { diff --git a/clang/include/clang/AST/DeclTemplate.h b/clang/include/clang/AST/DeclTemplate.h index a4a1bb9c13c79..6a29265cfb642 100644 --- a/clang/include/clang/AST/DeclTemplate.h +++ b/clang/include/clang/AST/DeclTemplate.h @@ -1104,6 +1104,19 @@ class FunctionTemplateDecl : public RedeclarableTemplateDecl { static FunctionTemplateDecl *CreateDeserialized(ASTContext &C, GlobalDeclID ID); + SourceRange getSourceRange() const override LLVM_READONLY { + SourceLocation BeginLoc = getTemplateParameters()->getTemplateLoc(); + if (BeginLoc.isInvalid() && isAbbreviated()) { + // The BeginLoc of FunctionTemplateDecls is derived from the template keyword. + // But "pure" abbreviated templates do not use the template keyword. + // Hence the BeginLoc is invalid. + // Therefore just use the beginning of the templated declaration instead. + BeginLoc = getTemplatedDecl()->getBeginLoc(); + } + + return SourceRange(BeginLoc, TemplatedDecl->getSourceRange().getEnd()); + } + // Implement isa/cast/dyncast support static bool classof(const Decl *D) { return classofKind(D->getKind()); } static bool classofKind(Kind K) { return K == FunctionTemplate; } diff --git a/clang/test/AST/ast-dump-record-definition-data-json.cpp b/clang/test/AST/ast-dump-record-definition-data-json.cpp index e35bec78c6847..62acf84e6757c 100644 --- a/clang/test/AST/ast-dump-record-definition-data-json.cpp +++ b/clang/test/AST/ast-dump-record-definition-data-json.cpp @@ -408,7 +408,11 @@ struct DoesNotAllowConstDefaultInit { // CHECK-NEXT: "tokLen": 1 // CHECK-NEXT: }, // CHECK-NEXT: "range": { -// CHECK-NEXT: "begin": {}, +// CHECK-NEXT: "begin": { +// CHECK-NEXT: "offset": 197, +// CHECK-NEXT: "col": 33, +// CHECK-NEXT: "tokLen": 1 +// CHECK-NEXT: }, // CHECK-NEXT: "end": { // CHECK-NEXT: "offset": 199, // CHECK-NEXT: "col": 35, @@ -523,7 +527,11 @@ struct DoesNotAllowConstDefaultInit { // CHECK-NEXT: "tokLen": 1 // CHECK-NEXT: }, // CHECK-NEXT: "range": { -// CHECK-NEXT: "begin": {}, +// CHECK-NEXT: "begin": { +// CHECK-NEXT: "offset": 190, +// CHECK-NEXT: "col": 26, +// CHECK-NEXT: "tokLen": 1 +// CHECK-NEXT: }, // CHECK-NEXT: "end": { // CHECK-NEXT: "offset": 199, // CHECK-NEXT: "col": 35, @@ -598,7 +606,11 @@ struct DoesNotAllowConstDefaultInit { // CHECK-NEXT: "tokLen": 1 // CHECK-NEXT: }, // CHECK-NEXT: "range": { -// CHECK-NEXT: "begin": {}, +// CHECK-NEXT: "begin": { +// CHECK-NEXT: "offset": 190, +// CHECK-NEXT: "col": 26, +// CHECK-NEXT: "tokLen": 1 +// CHECK-NEXT: }, // CHECK-NEXT: "end": { // CHECK-NEXT: "offset": 199, // CHECK-NEXT: "col": 35, diff --git a/clang/test/AST/ast-dump-templates.cpp b/clang/test/AST/ast-dump-templates.cpp index f0357c5a8aa32..8cf9b6a29e332 100644 --- a/clang/test/AST/ast-dump-templates.cpp +++ b/clang/test/AST/ast-dump-templates.cpp @@ -266,6 +266,17 @@ namespace AliasDependentTemplateSpecializationType { // DUMP-NEXT: `-BuiltinType {{.*}} 'int' } // namespace +namespace TestAbbreviatedTemplateDecls { + // DUMP-LABEL: NamespaceDecl {{.*}} TestAbbreviatedTemplateDecls{{$}} + void abbreviated(auto); + template<class T> + void mixed(T, auto); + +// DUMP: FunctionTemplateDecl {{.*}} <line:[[@LINE-4]]:3, col:24> col:8 abbreviated +// DUMP: FunctionTemplateDecl {{.*}} <line:[[@LINE-4]]:3, line:[[@LINE-3]]:21> col:8 mixed + +} // namespace TestAbbreviatedTemplateDecls + // NOTE: CHECK lines have been autogenerated by gen_ast_dump_json_test.py @@ -9256,6 +9267,290 @@ namespace AliasDependentTemplateSpecializationType { // JSON-NEXT: ] // JSON-NEXT: } // JSON-NEXT: ] +// JSON-NEXT: }, +// JSON-NEXT: { +// JSON-NEXT: "id": "0x{{.*}}", +// JSON-NEXT: "kind": "NamespaceDecl", +// JSON-NEXT: "loc": { +// JSON-NEXT: "offset": 11557, +// JSON-NEXT: "line": 269, +// JSON-NEXT: "col": 11, +// JSON-NEXT: "tokLen": 28 +// JSON-NEXT: }, +// JSON-NEXT: "range": { +// JSON-NEXT: "begin": { +// JSON-NEXT: "offset": 11547, +// JSON-NEXT: "col": 1, +// JSON-NEXT: "tokLen": 9 +// JSON-NEXT: }, +// JSON-NEXT: "end": { +// JSON-NEXT: "offset": 11906, +// JSON-NEXT: "line": 278, +// JSON-NEXT: "col": 1, +// JSON-NEXT: "tokLen": 1 +// JSON-NEXT: } +// JSON-NEXT: }, +// JSON-NEXT: "name": "TestAbbreviatedTemplateDecls", +// JSON-NEXT: "inner": [ +// JSON-NEXT: { +// JSON-NEXT: "id": "0x{{.*}}", +// JSON-NEXT: "kind": "FunctionTemplateDecl", +// JSON-NEXT: "loc": { +// JSON-NEXT: "offset": 11667, +// JSON-NEXT: "line": 271, +// JSON-NEXT: "col": 8, +// JSON-NEXT: "tokLen": 11 +// JSON-NEXT: }, +// JSON-NEXT: "range": { +// JSON-NEXT: "begin": { +// JSON-NEXT: "offset": 11662, +// JSON-NEXT: "col": 3, +// JSON-NEXT: "tokLen": 4 +// JSON-NEXT: }, +// JSON-NEXT: "end": { +// JSON-NEXT: "offset": 11683, +// JSON-NEXT: "col": 24, +// JSON-NEXT: "tokLen": 1 +// JSON-NEXT: } +// JSON-NEXT: }, +// JSON-NEXT: "name": "abbreviated", +// JSON-NEXT: "inner": [ +// JSON-NEXT: { +// JSON-NEXT: "id": "0x{{.*}}", +// JSON-NEXT: "kind": "TemplateTypeParmDecl", +// JSON-NEXT: "loc": { +// JSON-NEXT: "offset": 11683, +// JSON-NEXT: "col": 24, +// JSON-NEXT: "tokLen": 1 +// JSON-NEXT: }, +// JSON-NEXT: "range": { +// JSON-NEXT: "begin": { +// JSON-NEXT: "offset": 11679, +// JSON-NEXT: "col": 20, +// JSON-NEXT: "tokLen": 4 +// JSON-NEXT: }, +// JSON-NEXT: "end": { +// JSON-NEXT: "offset": 11683, +// JSON-NEXT: "col": 24, +// JSON-NEXT: "tokLen": 1 +// JSON-NEXT: } +// JSON-NEXT: }, +// JSON-NEXT: "isImplicit": true, +// JSON-NEXT: "name": "auto:1", +// JSON-NEXT: "tagUsed": "class", +// JSON-NEXT: "depth": 0, +// JSON-NEXT: "index": 0 +// JSON-NEXT: }, +// JSON-NEXT: { +// JSON-NEXT: "id": "0x{{.*}}", +// JSON-NEXT: "kind": "FunctionDecl", +// JSON-NEXT: "loc": { +// JSON-NEXT: "offset": 11667, +// JSON-NEXT: "col": 8, +// JSON-NEXT: "tokLen": 11 +// JSON-NEXT: }, +// JSON-NEXT: "range": { +// JSON-NEXT: "begin": { +// JSON-NEXT: "offset": 11662, +// JSON-NEXT: "col": 3, +// JSON-NEXT: "tokLen": 4 +// JSON-NEXT: }, +// JSON-NEXT: "end": { +// JSON-NEXT: "offset": 11683, +// JSON-NEXT: "col": 24, +// JSON-NEXT: "tokLen": 1 +// JSON-NEXT: } +// JSON-NEXT: }, +// JSON-NEXT: "name": "abbreviated", +// JSON-NEXT: "type": { +// JSON-NEXT: "qualType": "void (auto)" +// JSON-NEXT: }, +// JSON-NEXT: "inner": [ +// JSON-NEXT: { +// JSON-NEXT: "id": "0x{{.*}}", +// JSON-NEXT: "kind": "ParmVarDecl", +// JSON-NEXT: "loc": { +// JSON-NEXT: "offset": 11683, +// JSON-NEXT: "col": 24, +// JSON-NEXT: "tokLen": 1 +// JSON-NEXT: }, +// JSON-NEXT: "range": { +// JSON-NEXT: "begin": { +// JSON-NEXT: "offset": 11679, +// JSON-NEXT: "col": 20, +// JSON-NEXT: "tokLen": 4 +// JSON-NEXT: }, +// JSON-NEXT: "end": { +// JSON-NEXT: "offset": 11679, +// JSON-NEXT: "col": 20, +// JSON-NEXT: "tokLen": 4 +// JSON-NEXT: } +// JSON-NEXT: }, +// JSON-NEXT: "type": { +// JSON-NEXT: "qualType": "auto" +// JSON-NEXT: } +// JSON-NEXT: } +// JSON-NEXT: ] +// JSON-NEXT: } +// JSON-NEXT: ] +// JSON-NEXT: }, +// JSON-NEXT: { +// JSON-NEXT: "id": "0x{{.*}}", +// JSON-NEXT: "kind": "FunctionTemplateDecl", +// JSON-NEXT: "loc": { +// JSON-NEXT: "offset": 11713, +// JSON-NEXT: "line": 273, +// JSON-NEXT: "col": 8, +// JSON-NEXT: "tokLen": 5 +// JSON-NEXT: }, +// JSON-NEXT: "range": { +// JSON-NEXT: "begin": { +// JSON-NEXT: "offset": 11688, +// JSON-NEXT: "line": 272, +// JSON-NEXT: "col": 3, +// JSON-NEXT: "tokLen": 8 +// JSON-NEXT: }, +// JSON-NEXT: "end": { +// JSON-NEXT: "offset": 11726, +// JSON-NEXT: "line": 273, +// JSON-NEXT: "col": 21, +// JSON-NEXT: "tokLen": 1 +// JSON-NEXT: } +// JSON-NEXT: }, +// JSON-NEXT: "name": "mixed", +// JSON-NEXT: "inner": [ +// JSON-NEXT: { +// JSON-NEXT: "id": "0x{{.*}}", +// JSON-NEXT: "kind": "TemplateTypeParmDecl", +// JSON-NEXT: "loc": { +// JSON-NEXT: "offset": 11703, +// JSON-NEXT: "line": 272, +// JSON-NEXT: "col": 18, +// JSON-NEXT: "tokLen": 1 +// JSON-NEXT: }, +// JSON-NEXT: "range": { +// JSON-NEXT: "begin": { +// JSON-NEXT: "offset": 11697, +// JSON-NEXT: "col": 12, +// JSON-NEXT: "tokLen": 5 +// JSON-NEXT: }, +// JSON-NEXT: "end": { +// JSON-NEXT: "offset": 11703, +// JSON-NEXT: "col": 18, +// JSON-NEXT: "tokLen": 1 +// JSON-NEXT: } +// JSON-NEXT: }, +// JSON-NEXT: "isReferenced": true, +// JSON-NEXT: "name": "T", +// JSON-NEXT: "tagUsed": "class", +// JSON-NEXT: "depth": 0, +// JSON-NEXT: "index": 0 +// JSON-NEXT: }, +// JSON-NEXT: { +// JSON-NEXT: "id": "0x{{.*}}", +// JSON-NEXT: "kind": "TemplateTypeParmDecl", +// JSON-NEXT: "loc": { +// JSON-NEXT: "offset": 11726, +// JSON-NEXT: "line": 273, +// JSON-NEXT: "col": 21, +// JSON-NEXT: "tokLen": 1 +// JSON-NEXT: }, +// JSON-NEXT: "range": { +// JSON-NEXT: "begin": { +// JSON-NEXT: "offset": 11722, +// JSON-NEXT: "col": 17, +// JSON-NEXT: "tokLen": 4 +// JSON-NEXT: }, +// JSON-NEXT: "end": { +// JSON-NEXT: "offset": 11726, +// JSON-NEXT: "col": 21, +// JSON-NEXT: "tokLen": 1 +// JSON-NEXT: } +// JSON-NEXT: }, +// JSON-NEXT: "isImplicit": true, +// JSON-NEXT: "name": "auto:2", +// JSON-NEXT: "tagUsed": "class", +// JSON-NEXT: "depth": 0, +// JSON-NEXT: "index": 1 +// JSON-NEXT: }, +// JSON-NEXT: { +// JSON-NEXT: "id": "0x{{.*}}", +// JSON-NEXT: "kind": "FunctionDecl", +// JSON-NEXT: "loc": { +// JSON-NEXT: "offset": 11713, +// JSON-NEXT: "col": 8, +// JSON-NEXT: "tokLen": 5 +// JSON-NEXT: }, +// JSON-NEXT: "range": { +// JSON-NEXT: "begin": { +// JSON-NEXT: "offset": 11708, +// JSON-NEXT: "col": 3, +// JSON-NEXT: "tokLen": 4 +// JSON-NEXT: }, +// JSON-NEXT: "end": { +// JSON-NEXT: "offset": 11726, +// JSON-NEXT: "col": 21, +// JSON-NEXT: "tokLen": 1 +// JSON-NEXT: } +// JSON-NEXT: }, +// JSON-NEXT: "name": "mixed", +// JSON-NEXT: "type": { +// JSON-NEXT: "qualType": "void (T, auto)" +// JSON-NEXT: }, +// JSON-NEXT: "inner": [ +// JSON-NEXT: { +// JSON-NEXT: "id": "0x{{.*}}", +// JSON-NEXT: "kind": "ParmVarDecl", +// JSON-NEXT: "loc": { +// JSON-NEXT: "offset": 11720, +// JSON-NEXT: "col": 15, +// JSON-NEXT: "tokLen": 1 +// JSON-NEXT: }, +// JSON-NEXT: "range": { +// JSON-NEXT: "begin": { +// JSON-NEXT: "offset": 11719, +// JSON-NEXT: "col": 14, +// JSON-NEXT: "tokLen": 1 +// JSON-NEXT: }, +// JSON-NEXT: "end": { +// JSON-NEXT: "offset": 11719, +// JSON-NEXT: "col": 14, +// JSON-NEXT: "tokLen": 1 +// JSON-NEXT: } +// JSON-NEXT: }, +// JSON-NEXT: "type": { +// JSON-NEXT: "qualType": "T" +// JSON-NEXT: } +// JSON-NEXT: }, +// JSON-NEXT: { +// JSON-NEXT: "id": "0x{{.*}}", +// JSON-NEXT: "kind": "ParmVarDecl", +// JSON-NEXT: "loc": { +// JSON-NEXT: "offset": 11726, +// JSON-NEXT: "col": 21, +// JSON-NEXT: "tokLen": 1 +// JSON-NEXT: }, +// JSON-NEXT: "range": { +// JSON-NEXT: "begin": { +// JSON-NEXT: "offset": 11722, +// JSON-NEXT: "col": 17, +// JSON-NEXT: "tokLen": 4 +// JSON-NEXT: }, +// JSON-NEXT: "end": { +// JSON-NEXT: "offset": 11722, +// JSON-NEXT: "col": 17, +// JSON-NEXT: "tokLen": 4 +// JSON-NEXT: } +// JSON-NEXT: }, +// JSON-NEXT: "type": { +// JSON-NEXT: "qualType": "auto" +// JSON-NEXT: } +// JSON-NEXT: } +// JSON-NEXT: ] +// JSON-NEXT: } +// JSON-NEXT: ] +// JSON-NEXT: } +// JSON-NEXT: ] // JSON-NEXT: } // JSON-NEXT: ] // JSON-NEXT: } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
