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

Reply via email to