[clang] [clang][AST] Fix source range of class template implicit instantiations. (PR #156011)

Fred Tingaud via cfe-commits cfe-commits at lists.llvm.org
Fri Aug 29 05:07:21 PDT 2025


https://github.com/frederic-tingaud-sonarsource created https://github.com/llvm/llvm-project/pull/156011

When getting the source range of the implicit instantiation of a class template, we get the source range of the latest declaration of the class template, even when it is not its definition. The problem does not occur with function templates and variable templates.
This patch aligns the behavior of class templates with function and variable templates.

>From 247ee6b7982cb7a0fb53bbe52b2bf1693c118b6a Mon Sep 17 00:00:00 2001
From: Fred Tingaud <frederic.tingaud at sonarsource.com>
Date: Mon, 25 Aug 2025 20:07:04 +0200
Subject: [PATCH] [clang][AST] Fix source range of class template implicit
 instantiations.

---
 clang/lib/AST/DeclTemplate.cpp                |  7 +-
 clang/test/AST/ast-dump-decl.cpp              |  6 +-
 ...penmp-begin-declare-variant_template_3.cpp |  4 +-
 .../test/AST/ast-dump-template-decls-json.cpp |  4 +-
 clang/test/AST/ast-dump-template-redecl.cpp   | 88 +++++++++++++++++++
 5 files changed, 101 insertions(+), 8 deletions(-)
 create mode 100644 clang/test/AST/ast-dump-template-redecl.cpp

diff --git a/clang/lib/AST/DeclTemplate.cpp b/clang/lib/AST/DeclTemplate.cpp
index 3162857aac5d0..01cc33b795308 100644
--- a/clang/lib/AST/DeclTemplate.cpp
+++ b/clang/lib/AST/DeclTemplate.cpp
@@ -1050,7 +1050,12 @@ ClassTemplateSpecializationDecl::getSourceRange() const {
     if (const auto *CTPSD =
             dyn_cast<ClassTemplatePartialSpecializationDecl *>(Pattern))
       return CTPSD->getSourceRange();
-    return cast<ClassTemplateDecl *>(Pattern)->getSourceRange();
+
+    const auto *CTD = cast<ClassTemplateDecl *>(Pattern);
+    if (CTD->getTemplatedDecl()->hasDefinition())
+      return CTD->getTemplatedDecl()->getDefinition()->getSourceRange();
+
+    return CTD->getSourceRange();
   }
   case TSK_ExplicitSpecialization: {
     SourceRange Range = CXXRecordDecl::getSourceRange();
diff --git a/clang/test/AST/ast-dump-decl.cpp b/clang/test/AST/ast-dump-decl.cpp
index afb507833d869..8d7aed69520b4 100644
--- a/clang/test/AST/ast-dump-decl.cpp
+++ b/clang/test/AST/ast-dump-decl.cpp
@@ -334,7 +334,7 @@ namespace testClassTemplateDecl {
 // CHECK-NEXT:  | |-CXXDestructorDecl 0x[[#%x,TEMPLATE_DESTRUCTOR_DECL:]] <line:[[@LINE-50]]:5, col:24> col:5 ~TestClassTemplate<T> 'void ()' not_selected{{$}}
 // CHECK-NEXT:  | |-CXXMethodDecl 0x[[#%x,TEMPLATE_METHOD_DECL:]] <line:[[@LINE-50]]:5, col:11> col:9 j 'int ()'{{$}}
 // CHECK-NEXT:  | `-FieldDecl 0x{{.+}} <line:[[@LINE-50]]:5, col:9> col:9 i 'int'{{$}}
-// CHECK-NEXT:  |-ClassTemplateSpecializationDecl 0x{{.+}} <line:[[@LINE-56]]:3, line:[[@LINE-50]]:3> line:[[@LINE-56]]:30 class TestClassTemplate definition implicit_instantiation{{$}}
+// CHECK-NEXT:  |-ClassTemplateSpecializationDecl 0x{{.+}} <line:[[@LINE-56]]:24, line:[[@LINE-50]]:3> line:[[@LINE-56]]:30 class TestClassTemplate definition implicit_instantiation{{$}}
 // CHECK-NEXT:  | |-DefinitionData standard_layout has_user_declared_ctor can_const_default_init{{$}}
 // CHECK-NEXT:  | | |-DefaultConstructor exists non_trivial user_provided{{$}}
 // CHECK-NEXT:  | | |-CopyConstructor simple trivial has_const_param implicit_has_const_param{{$}}
@@ -635,7 +635,7 @@ namespace testCanonicalTemplate {
   // CHECK-NEXT: |   `-ClassTemplateDecl 0x{{.+}} parent 0x{{.+}} <col:5, col:40> col:40 friend_undeclared TestClassTemplate{{$}}
   // CHECK-NEXT: |     |-TemplateTypeParmDecl 0x{{.+}} <col:14, col:23> col:23 typename depth 1 index 0 T2{{$}}
   // CHECK-NEXT: |     `-CXXRecordDecl 0x{{.+}} parent 0x{{.+}} <col:34, col:40> col:40 class TestClassTemplate{{$}}
-  // CHECK-NEXT: `-ClassTemplateSpecializationDecl 0x{{.+}} <line:[[@LINE-19]]:3, line:[[@LINE-17]]:3> line:[[@LINE-19]]:31 class TestClassTemplate definition implicit_instantiation{{$}}
+  // CHECK-NEXT: `-ClassTemplateSpecializationDecl 0x{{.+}} <line:[[@LINE-19]]:25, line:[[@LINE-17]]:3> line:[[@LINE-19]]:31 class TestClassTemplate definition implicit_instantiation{{$}}
   // CHECK-NEXT:   |-DefinitionData pass_in_registers empty aggregate standard_layout trivially_copyable pod trivial literal has_constexpr_non_copy_move_ctor can_const_default_init{{$}}
   // CHECK-NEXT:   | |-DefaultConstructor exists trivial constexpr defaulted_is_constexpr{{$}}
   // CHECK-NEXT:   | |-CopyConstructor simple trivial has_const_param implicit_has_const_param{{$}}
@@ -668,7 +668,7 @@ namespace testCanonicalTemplate {
   // CHECK:      ClassTemplateDecl 0x{{.+}} <{{.+}}:[[@LINE-5]]:3, col:31> col:31 TestClassTemplate2{{$}}
   // CHECK-NEXT: |-TemplateTypeParmDecl 0x{{.+}} <col:12, col:21> col:21 typename depth 0 index 0 T1{{$}}
   // CHECK-NEXT: |-CXXRecordDecl 0x{{.+}} <col:25, col:31> col:31 class TestClassTemplate2{{$}}
-  // CHECK-NEXT: `-ClassTemplateSpecializationDecl 0x{{.+}} <line:[[@LINE-6]]:3, line:[[@LINE-5]]:3> line:[[@LINE-6]]:31 class TestClassTemplate2 definition implicit_instantiation{{$}}
+  // CHECK-NEXT: `-ClassTemplateSpecializationDecl 0x{{.+}} <line:[[@LINE-6]]:25, line:[[@LINE-5]]:3> line:[[@LINE-6]]:31 class TestClassTemplate2 definition implicit_instantiation{{$}}
   // CHECK-NEXT:   |-DefinitionData pass_in_registers empty aggregate standard_layout trivially_copyable pod trivial literal has_constexpr_non_copy_move_ctor can_const_default_init{{$}}
   // CHECK-NEXT:   | |-DefaultConstructor exists trivial constexpr defaulted_is_constexpr{{$}}
   // CHECK-NEXT:   | |-CopyConstructor simple trivial has_const_param implicit_has_const_param{{$}}
diff --git a/clang/test/AST/ast-dump-openmp-begin-declare-variant_template_3.cpp b/clang/test/AST/ast-dump-openmp-begin-declare-variant_template_3.cpp
index 44d1cb462cd58..779dfec42ea8e 100644
--- a/clang/test/AST/ast-dump-openmp-begin-declare-variant_template_3.cpp
+++ b/clang/test/AST/ast-dump-openmp-begin-declare-variant_template_3.cpp
@@ -59,7 +59,7 @@ int test() {
 // CHECK-NEXT: | |   |-ParmVarDecl [[ADDR_5:0x[a-z0-9]*]] <col:5> col:8 'int'
 // CHECK-NEXT: | |   |-ParmVarDecl [[ADDR_6:0x[a-z0-9]*]] <col:10, col:12> col:13 'T *'
 // CHECK-NEXT: | |   `-CompoundStmt [[ADDR_7:0x[a-z0-9]*]] <col:15, col:16>
-// CHECK-NEXT: | |-ClassTemplateSpecializationDecl [[ADDR_8:0x[a-z0-9]*]] <line:5:1, line:7:1> line:5:30 struct S definition
+// CHECK-NEXT: | |-ClassTemplateSpecializationDecl [[ADDR_8:0x[a-z0-9]*]] <line:5:23, line:7:1> line:5:30 struct S definition
 // CHECK-NEXT: | | |-DefinitionData pass_in_registers empty standard_layout trivially_copyable has_user_declared_ctor can_const_default_init
 // CHECK-NEXT: | | | |-DefaultConstructor defaulted_is_constexpr
 // CHECK-NEXT: | | | |-CopyConstructor simple trivial has_const_param implicit_has_const_param
@@ -82,7 +82,7 @@ int test() {
 // CXX17-NEXT: | | |-CXXConstructorDecl [[ADDR_16:0x[a-z0-9]*]] <col:30> col:30 implicit constexpr S 'void (S<int> &&)' inline default trivial noexcept-unevaluated
 // CXX17-NEXT: | | | `-ParmVarDecl [[ADDR_17:0x[a-z0-9]*]] <col:30> col:30 'S<int> &&'
 // CHECK-NEXT: | | `-CXXDestructorDecl [[ADDR_19:0x[a-z0-9]*]] <col:30> col:30 implicit referenced {{(constexpr )?}}~S 'void ({{.*}}) noexcept' inline default trivial
-// CHECK-NEXT: | `-ClassTemplateSpecializationDecl [[ADDR_20:0x[a-z0-9]*]] <col:1, line:7:1> line:5:30 struct S
+// CHECK-NEXT: | `-ClassTemplateSpecializationDecl [[ADDR_20:0x[a-z0-9]*]] <col:23, line:7:1> line:5:30 struct S
 // CHECK-NEXT: |   `-TemplateArgument type 'double'
 // CHECK-NEXT: |     `-BuiltinType [[ADDR_21:0x[a-z0-9]*]] 'double'
 // CHECK-NEXT: |-FunctionTemplateDecl [[ADDR_22:0x[a-z0-9]*]] <line:9:1, line:12:1> line:10:5 also_before
diff --git a/clang/test/AST/ast-dump-template-decls-json.cpp b/clang/test/AST/ast-dump-template-decls-json.cpp
index 70f1d3b55f3ee..240469955f4b6 100644
--- a/clang/test/AST/ast-dump-template-decls-json.cpp
+++ b/clang/test/AST/ast-dump-template-decls-json.cpp
@@ -2872,8 +2872,8 @@ W(int)->W<1>;
 // CHECK-NEXT:      "range": {
 // CHECK-NEXT:       "begin": {
 // CHECK-NEXT:        "offset": {{[0-9]+}},
-// CHECK-NEXT:        "col": 1,
-// CHECK-NEXT:        "tokLen": 8
+// CHECK-NEXT:        "col": 18,
+// CHECK-NEXT:        "tokLen": 5
 // CHECK-NEXT:       },
 // CHECK-NEXT:       "end": {
 // CHECK-NEXT:        "offset": {{[0-9]+}},
diff --git a/clang/test/AST/ast-dump-template-redecl.cpp b/clang/test/AST/ast-dump-template-redecl.cpp
new file mode 100644
index 0000000000000..a883375c1630b
--- /dev/null
+++ b/clang/test/AST/ast-dump-template-redecl.cpp
@@ -0,0 +1,88 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-unknown -fopenmp -verify -ast-dump %s %std_cxx17- | FileCheck %s --check-prefixes=CHECK
+// expected-no-diagnostics
+
+template <class T>
+struct Redeclared {
+  void function() {}
+};
+
+template <class T>
+struct Redeclared;
+
+Redeclared<int> instantiation;
+
+template <typename T>
+void redeclaredFunction(T t) {
+  (void)t;
+}
+
+template <typename T>
+void redeclaredFunction(T t);
+
+void instantiate() {
+  redeclaredFunction(0);
+}
+
+
+// CHECK:      |-ClassTemplateDecl [[ADDR_0:0x[a-z0-9]*]] <{{.*}}:4:1, line:7:1> line:5:8 Redeclared
+// CHECK-NEXT: | |-TemplateTypeParmDecl [[ADDR_1:0x[a-z0-9]*]] <line:4:11, col:17> col:17 class depth 0 index 0 T
+// CHECK-NEXT: | |-CXXRecordDecl [[ADDR_2:0x[a-z0-9]*]] <line:5:1, line:7:1> line:5:8 struct Redeclared definition
+// CHECK-NEXT: | | |-DefinitionData empty aggregate standard_layout trivially_copyable pod trivial literal has_constexpr_non_copy_move_ctor can_const_default_init
+// CHECK-NEXT: | | | |-DefaultConstructor exists trivial constexpr needs_implicit defaulted_is_constexpr
+// CHECK-NEXT: | | | |-CopyConstructor simple trivial has_const_param needs_implicit implicit_has_const_param
+// CHECK-NEXT: | | | |-MoveConstructor exists simple trivial needs_implicit
+// CHECK-NEXT: | | | |-CopyAssignment simple trivial has_const_param needs_implicit implicit_has_const_param
+// CHECK-NEXT: | | | |-MoveAssignment exists simple trivial needs_implicit
+// CHECK-NEXT: | | | `-Destructor simple irrelevant trivial {{(constexpr )?}}needs_implicit
+// CHECK-NEXT: | | |-CXXRecordDecl [[ADDR_3:0x[a-z0-9]*]] <col:1, col:8> col:8 implicit struct Redeclared
+// CHECK-NEXT: | | `-CXXMethodDecl [[ADDR_4:0x[a-z0-9]*]] <line:6:3, col:20> col:8 function 'void ()' implicit-inline
+// CHECK-NEXT: | |   `-CompoundStmt [[ADDR_5:0x[a-z0-9]*]] <col:19, col:20>
+// CHECK-NEXT: | `-ClassTemplateSpecializationDecl [[ADDR_6:0x[a-z0-9]*]] <line:5:1, line:7:1> line:5:8 struct Redeclared definition implicit_instantiation
+// CHECK-NEXT: |   |-DefinitionData pass_in_registers empty aggregate standard_layout trivially_copyable pod trivial literal has_constexpr_non_copy_move_ctor can_const_default_init
+// CHECK-NEXT: |   | |-DefaultConstructor exists trivial constexpr defaulted_is_constexpr
+// CHECK-NEXT: |   | |-CopyConstructor simple trivial has_const_param implicit_has_const_param
+// CHECK-NEXT: |   | |-MoveConstructor exists simple trivial
+// CHECK-NEXT: |   | |-CopyAssignment simple trivial has_const_param needs_implicit implicit_has_const_param
+// CHECK-NEXT: |   | |-MoveAssignment exists simple trivial needs_implicit
+// CHECK-NEXT: |   | `-Destructor simple irrelevant trivial constexpr needs_implicit
+// CHECK-NEXT: |   |-TemplateArgument type 'int'
+// CHECK-NEXT: |   | `-BuiltinType [[ADDR_7:0x[a-z0-9]*]] 'int'
+// CHECK-NEXT: |   |-CXXRecordDecl [[ADDR_8:0x[a-z0-9]*]] <col:1, col:8> col:8 implicit struct Redeclared
+// CHECK-NEXT: |   |-CXXMethodDecl [[ADDR_9:0x[a-z0-9]*]] <line:6:3, col:20> col:8 function 'void ()' implicit_instantiation implicit-inline instantiated_from [[ADDR_4]]
+// CHECK-NEXT: |   |-CXXConstructorDecl [[ADDR_10:0x[a-z0-9]*]] <line:5:8> col:8 implicit used constexpr Redeclared 'void () noexcept' inline default trivial
+// CHECK-NEXT: |   | `-CompoundStmt [[ADDR_11:0x[a-z0-9]*]] <col:8>
+// CHECK-NEXT: |   |-CXXConstructorDecl [[ADDR_12:0x[a-z0-9]*]] <col:8> col:8 implicit constexpr Redeclared 'void (const Redeclared<int> &)' inline default trivial noexcept-unevaluated [[ADDR_12]]
+// CHECK-NEXT: |   | `-ParmVarDecl [[ADDR_13:0x[a-z0-9]*]] <col:8> col:8 'const Redeclared<int> &'
+// CHECK-NEXT: |   `-CXXConstructorDecl [[ADDR_14:0x[a-z0-9]*]] <col:8> col:8 implicit constexpr Redeclared 'void (Redeclared<int> &&)' inline default trivial noexcept-unevaluated [[ADDR_14]]
+// CHECK-NEXT: |     `-ParmVarDecl [[ADDR_15:0x[a-z0-9]*]] <col:8> col:8 'Redeclared<int> &&'
+// CHECK-NEXT: |-ClassTemplateDecl [[ADDR_16:0x[a-z0-9]*]] prev [[ADDR_0]] <line:9:1, line:10:8> col:8 Redeclared
+// CHECK-NEXT: | |-TemplateTypeParmDecl [[ADDR_17:0x[a-z0-9]*]] <line:9:11, col:17> col:17 class depth 0 index 0 T
+// CHECK-NEXT: | |-CXXRecordDecl [[ADDR_18:0x[a-z0-9]*]] prev [[ADDR_2]] <line:10:1, col:8> col:8 struct Redeclared
+// CHECK-NEXT: | `-ClassTemplateSpecialization [[ADDR_19:0x[a-z0-9]*]] 'Redeclared'
+// CHECK-NEXT: |-VarDecl [[ADDR_20:0x[a-z0-9]*]] <line:12:1, col:17> col:17 instantiation 'Redeclared<int>' callinit
+// CHECK-NEXT: | `-CXXConstructExpr [[ADDR_21:0x[a-z0-9]*]] <col:17> 'Redeclared<int>' 'void () noexcept'
+// CHECK-NEXT: |-FunctionTemplateDecl [[ADDR_22:0x[a-z0-9]*]] <line:14:1, line:17:1> line:15:6 redeclaredFunction
+// CHECK-NEXT: | |-TemplateTypeParmDecl [[ADDR_23:0x[a-z0-9]*]] <line:14:11, col:20> col:20 referenced typename depth 0 index 0 T
+// CHECK-NEXT: | |-FunctionDecl [[ADDR_24:0x[a-z0-9]*]] <line:15:1, line:17:1> line:15:6 redeclaredFunction 'void (T)'
+// CHECK-NEXT: | | |-ParmVarDecl [[ADDR_25:0x[a-z0-9]*]] <col:25, col:27> col:27 referenced t 'T'
+// CHECK-NEXT: | | `-CompoundStmt [[ADDR_26:0x[a-z0-9]*]] <col:30, line:17:1>
+// CHECK-NEXT: | |   `-CStyleCastExpr [[ADDR_27:0x[a-z0-9]*]] <line:16:3, col:9> 'void' <ToVoid>
+// CHECK-NEXT: | |     `-DeclRefExpr [[ADDR_28:0x[a-z0-9]*]] <col:9> 'T' lvalue ParmVar [[ADDR_25]] 't' 'T'
+// CHECK-NEXT: | `-FunctionDecl [[ADDR_29:0x[a-z0-9]*]] <line:15:1, line:17:1> line:15:6 used redeclaredFunction 'void (int)' implicit_instantiation
+// CHECK-NEXT: |   |-TemplateArgument type 'int'
+// CHECK-NEXT: |   | `-BuiltinType [[ADDR_30:0x[a-z0-9]*]] 'int'
+// CHECK-NEXT: |   |-ParmVarDecl [[ADDR_31:0x[a-z0-9]*]] <line:20:25, col:27> col:27 used t 'int'
+// CHECK-NEXT: |   `-CompoundStmt [[ADDR_32:0x[a-z0-9]*]] <line:15:30, line:17:1>
+// CHECK-NEXT: |     `-CStyleCastExpr [[ADDR_33:0x[a-z0-9]*]] <line:16:3, col:9> 'void' <ToVoid>
+// CHECK-NEXT: |       `-DeclRefExpr [[ADDR_34:0x[a-z0-9]*]] <col:9> 'int' lvalue ParmVar [[ADDR_31]] 't' 'int'
+// CHECK-NEXT: |-FunctionTemplateDecl [[ADDR_35:0x[a-z0-9]*]] prev [[ADDR_22]] <line:19:1, line:20:28> col:6 redeclaredFunction
+// CHECK-NEXT: | |-TemplateTypeParmDecl [[ADDR_36:0x[a-z0-9]*]] <line:19:11, col:20> col:20 referenced typename depth 0 index 0 T
+// CHECK-NEXT: | |-FunctionDecl [[ADDR_37:0x[a-z0-9]*]] prev [[ADDR_24]] <line:20:1, col:28> col:6 redeclaredFunction 'void (T)'
+// CHECK-NEXT: | | `-ParmVarDecl [[ADDR_38:0x[a-z0-9]*]] <col:25, col:27> col:27 t 'T'
+// CHECK-NEXT: | `-Function [[ADDR_39:0x[a-z0-9]*]] 'redeclaredFunction' 'void (int)'
+// CHECK-NEXT: `-FunctionDecl [[ADDR_40:0x[a-z0-9]*]] <line:22:1, line:24:1> line:22:6 instantiate 'void ()'
+// CHECK-NEXT:   `-CompoundStmt [[ADDR_41:0x[a-z0-9]*]] <col:20, line:24:1>
+// CHECK-NEXT:     `-CallExpr [[ADDR_42:0x[a-z0-9]*]] <line:23:3, col:23> 'void'
+// CHECK-NEXT:       |-ImplicitCastExpr [[ADDR_43:0x[a-z0-9]*]] <col:3> 'void (*)(int)' <FunctionToPointerDecay>
+// CHECK-NEXT:       | `-DeclRefExpr [[ADDR_44:0x[a-z0-9]*]] <col:3> 'void (int)' lvalue Function [[ADDR_29]] 'redeclaredFunction' 'void (int)' (FunctionTemplate [[ADDR_35]] 'redeclaredFunction')
+// CHECK-NEXT:       `-IntegerLiteral [[ADDR_45:0x[a-z0-9]*]] <col:22> 'int' 0



More information about the cfe-commits mailing list