https://github.com/NeKon69 updated 
https://github.com/llvm/llvm-project/pull/203866

>From 434605bd9d9625e6f78f161ae42b2360240408bb Mon Sep 17 00:00:00 2001
From: NeKon69 <[email protected]>
Date: Mon, 15 Jun 2026 12:24:14 +0300
Subject: [PATCH 1/4] [LifetimeSafety] Avoid suggesting lifetimbound attribute
 on primary templates

---
 clang/lib/Analysis/LifetimeSafety/Checker.cpp |  64 ++++++++++-
 .../misplaced-lifetimebound-cross-tu.cpp      |  28 +++++
 .../misplaced-lifetimebound-intra-tu.cpp      | 104 +++++++++++++++++-
 3 files changed, 186 insertions(+), 10 deletions(-)

diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp 
b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index 155c6072a33a5..0940dabbb856d 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -84,6 +84,49 @@ class LifetimeChecker {
     llvm_unreachable("unhandled causing fact in PointerUnion");
   }
 
+  /// For an explicit specialization, returns the source-level specialization
+  /// declaration to target for attribute placement, if one exists. Skips
+  /// implicit specialization redeclarations that are backed by the template
+  /// pattern. In other cases, returns nullptr.
+  const FunctionDecl *
+  getExplicitSpecializationDeclForAttr(const FunctionDecl *FDef) {
+    if (FDef->getTemplateSpecializationKindForInstantiation() !=
+        TSK_ExplicitSpecialization)
+      return nullptr;
+
+    const SourceManager &SM = AST.getSourceManager();
+    auto IsImplicitTemplateSpecialization = [&SM](const FunctionDecl *Redecl,
+                                                  const FunctionDecl *Pattern) 
{
+      return Pattern && Redecl->getTypeSourceInfo() &&
+             Pattern->getTypeSourceInfo() &&
+             SM.getFileLoc(
+                 Redecl->getTypeSourceInfo()->getTypeLoc().getBeginLoc()) ==
+                 SM.getFileLoc(
+                     Pattern->getTypeSourceInfo()->getTypeLoc().getBeginLoc());
+    };
+
+    auto redecls = llvm::to_vector(FDef->redecls());
+    for (const FunctionDecl *Redecl : llvm::reverse(redecls)) {
+      if (Redecl == FDef)
+        continue;
+      if (auto *MSI = Redecl->getMemberSpecializationInfo();
+          MSI && MSI->isExplicitSpecialization()) {
+        const auto *From = dyn_cast<FunctionDecl>(MSI->getInstantiatedFrom());
+        if (IsImplicitTemplateSpecialization(Redecl, From))
+          continue;
+        return Redecl;
+      }
+      if (auto *FTSI = Redecl->getTemplateSpecializationInfo();
+          FTSI && FTSI->isExplicitInstantiationOrSpecialization()) {
+        const FunctionDecl *Pattern = FTSI->getTemplate()->getTemplatedDecl();
+        if (IsImplicitTemplateSpecialization(Redecl, Pattern))
+          continue;
+        return Redecl;
+      }
+    }
+    return nullptr;
+  }
+
 public:
   LifetimeChecker(const LoanPropagationAnalysis &LoanPropagation,
                   const MovedLoansAnalysis &MovedLoans,
@@ -358,11 +401,17 @@ class LifetimeChecker {
     };
 
     const FileID DefFile = GetFile(FDef);
-    const FunctionDecl *CanonicalDecl = FDef->getCanonicalDecl();
+    const FunctionDecl *TargetDecl = FDef->getCanonicalDecl();
+
+    // For explicit specializations, the canonical decl is the primary 
template.
+    // We should target the explicit specialization declaration instead.
+    if (const FunctionDecl *SpecDecl =
+            getExplicitSpecializationDeclForAttr(FDef))
+      TargetDecl = SpecDecl;
+
     llvm::SmallVector<std::pair<const FunctionDecl *, WarningScope>, 2> 
Targets{
-        {CanonicalDecl, GetFile(CanonicalDecl) == DefFile
-                            ? WarningScope::IntraTU
-                            : WarningScope::CrossTU}};
+        {TargetDecl, GetFile(TargetDecl) == DefFile ? WarningScope::IntraTU
+                                                    : WarningScope::CrossTU}};
 
     // Find the earliest redeclaration in each file other than the definition
     // file.
@@ -370,6 +419,13 @@ class LifetimeChecker {
       FileID File = GetFile(FD);
       if (File == DefFile)
         return;
+      // For explicit specializations, skip the primary template in the redecl
+      // chain.
+      if (FDef->getTemplateSpecializationKindForInstantiation() ==
+              TSK_ExplicitSpecialization &&
+          FD->getTemplateSpecializationKindForInstantiation() !=
+              TSK_ExplicitSpecialization)
+        return;
       for (auto [SeenFD, _] : Targets)
         if (GetFile(SeenFD) == File)
           return;
diff --git 
a/clang/test/Sema/LifetimeSafety/misplaced-lifetimebound-cross-tu.cpp 
b/clang/test/Sema/LifetimeSafety/misplaced-lifetimebound-cross-tu.cpp
index 1ccdb5253bf0c..3f910e212a29e 100644
--- a/clang/test/Sema/LifetimeSafety/misplaced-lifetimebound-cross-tu.cpp
+++ b/clang/test/Sema/LifetimeSafety/misplaced-lifetimebound-cross-tu.cpp
@@ -23,6 +23,12 @@ struct HeaderS {
                              // CHECK: 
fix-it:"{{.*}}cross.h":{[[#THIS_WARN_LINE]]:{{[0-9]+}}-[[#THIS_WARN_LINE]]:{{[0-9]+}}}:"
 {{\[\[}}clang::lifetimebound{{\]\]}}"
 };
 
+template <typename T>
+HeaderObj &spec_cross_tu(HeaderObj &obj);
+
+template <>
+HeaderObj &spec_cross_tu<HeaderObj>(HeaderObj &obj); // expected-warning 
{{'lifetimebound' attribute on this definition is not visible to callers in 
other translation units; add it to the declaration instead}}
+
 //--- cross_1.h
 struct HeaderObj;
 HeaderObj &multi_header_param(HeaderObj &  obj  // expected-warning 
{{'lifetimebound' attribute on this definition is not visible to callers in 
other translation units; add it to the declaration instead}}
@@ -30,6 +36,15 @@ HeaderObj &multi_header_param(HeaderObj &  obj  // 
expected-warning {{'lifetimeb
                                                 // CHECK: 
fix-it:"{{.*}}cross_1.h":{[[#PARAM_WARN_LINE]]:{{[0-9]+}}-[[#PARAM_WARN_LINE]]:{{[0-9]+}}}:"
 {{\[\[}}clang::lifetimebound{{\]\]}}"
                               );
 
+template <typename T>
+struct HeaderMemberSpec {
+  T data;
+  T &get();
+};
+
+template <>
+HeaderObj &HeaderMemberSpec<HeaderObj>::get(); // expected-warning 
{{'lifetimebound' attribute on this definition is not visible to callers in 
other translation units; add it to the declaration instead}}
+
 //--- cross_2.h
 struct HeaderObj;
 HeaderObj &multi_header_param(HeaderObj &  obj  // expected-warning 
{{'lifetimebound' attribute on this definition is not visible to callers in 
other translation units; add it to the declaration instead}}
@@ -37,6 +52,9 @@ HeaderObj &multi_header_param(HeaderObj &  obj  // 
expected-warning {{'lifetimeb
                                                 // CHECK: 
fix-it:"{{.*}}cross_2.h":{[[#PARAM_WARN_LINE]]:{{[0-9]+}}-[[#PARAM_WARN_LINE]]:{{[0-9]+}}}:"
 {{\[\[}}clang::lifetimebound{{\]\]}}"
                               );
 
+template <>
+HeaderObj &HeaderMemberSpec<HeaderObj>::get(); // expected-warning 
{{'lifetimebound' attribute on this definition is not visible to callers in 
other translation units; add it to the declaration instead}}
+
 //--- cross.cpp
 #include "cross.h"
 #include "cross_1.h"
@@ -53,3 +71,13 @@ HeaderObj &HeaderS::header_this() [[clang::lifetimebound]] { 
// expected-note {{
 HeaderObj &multi_header_param(HeaderObj &obj [[clang::lifetimebound]]) { // 
expected-note 2 {{'lifetimebound' attribute appears here on the definition}}
   return obj;
 }
+
+template <>
+HeaderObj &spec_cross_tu<HeaderObj>(HeaderObj &obj [[clang::lifetimebound]]) { 
// expected-note {{'lifetimebound' attribute appears here on the definition}}
+  return obj;
+}
+
+template <>
+HeaderObj &HeaderMemberSpec<HeaderObj>::get() [[clang::lifetimebound]] { // 
expected-note 2 {{'lifetimebound' attribute appears here on the definition}}
+  return data;
+}
diff --git 
a/clang/test/Sema/LifetimeSafety/misplaced-lifetimebound-intra-tu.cpp 
b/clang/test/Sema/LifetimeSafety/misplaced-lifetimebound-intra-tu.cpp
index 28dd65211b268..a49bb3fa84ab8 100644
--- a/clang/test/Sema/LifetimeSafety/misplaced-lifetimebound-intra-tu.cpp
+++ b/clang/test/Sema/LifetimeSafety/misplaced-lifetimebound-intra-tu.cpp
@@ -135,18 +135,110 @@ View friend_redecl(MyObj &obj) {
 }
 
 template <typename T>
-// FIXME: Current analysis suggests adding to the primary template 
declaration, which is not ideal, as it will affect all specializations.
-MyObj &spec_func(T & obj // expected-warning {{'lifetimebound' attribute on 
this definition is not visible to callers before the definition; add it to the 
declaration instead}}
-                         // CHECK: 
fix-it:"{{.*}}":{[[@LINE-1]]:{{[0-9]+}}-[[@LINE-1]]:{{[0-9]+}}}:" 
{{\[\[clang::lifetimebound\]\]}}"
-                 );
+MyObj &spec_func(T &obj);
 
 template <>
-// FIXME: Attribute is inhetired, diagnostic's wording is not correct.
-MyObj &spec_func<MyObj>(MyObj &obj [[clang::lifetimebound]]); // expected-note 
{{'lifetimebound' attribute appears here on the definition}}
+MyObj &spec_func<MyObj>(MyObj &obj [[clang::lifetimebound]]);
 
 template <>
 MyObj &spec_func<MyObj>(MyObj &obj) { return obj; }
 
+template <typename T>
+MyObj &spec_misplaced(T &obj);
+
+template <>
+MyObj &spec_misplaced<MyObj>(MyObj &obj); // expected-warning 
{{'lifetimebound' attribute on this definition is not visible to callers before 
the definition; add it to the declaration instead}}
+
+template <>
+MyObj &spec_misplaced<MyObj>(MyObj &obj [[clang::lifetimebound]]) { // 
expected-note {{'lifetimebound' attribute appears here on the definition}}
+  return obj;
+}
+
+template <class T>
+struct MemberSpec {
+  T data;
+  T &get();
+};
+
+template <>
+MyObj &MemberSpec<MyObj>::get(); // expected-warning {{'lifetimebound' 
attribute on this definition is not visible to callers before the definition; 
add it to the declaration instead}}
+
+template <>
+MyObj &MemberSpec<MyObj>::get() [[clang::lifetimebound]] { // expected-note 
{{'lifetimebound' attribute appears here on the definition}}
+  return data;
+}
+
+template <class T>
+struct MemberSpecOk {
+  T data;
+  T &get();
+};
+
+template <>
+MyObj &MemberSpecOk<MyObj>::get() [[clang::lifetimebound]];
+
+template <>
+MyObj &MemberSpecOk<MyObj>::get() {
+  return data;
+}
+
+template <class T>
+struct NestedMemberSpecBoth {
+  template <class U>
+  struct Inner {
+    U data;
+    template <class V>
+    U &both(U &arg, V);
+  };
+};
+
+template <>
+template <>
+template <>
+MyObj &NestedMemberSpecBoth<int>::Inner<MyObj>::both(
+    MyObj &arg, bool); // expected-warning {{'lifetimebound' attribute on this 
definition is not visible to callers before the definition; add it to the 
declaration instead}} \
+                       // expected-warning {{'lifetimebound' attribute on this 
definition is not visible to callers before the definition; add it to the 
declaration instead}}
+
+template <>
+template <>
+template <>
+MyObj &NestedMemberSpecBoth<int>::Inner<MyObj>::both(
+    MyObj &arg [[clang::lifetimebound]], bool use_arg) // expected-note 
{{'lifetimebound' attribute appears here on the definition}}
+    [[clang::lifetimebound]] {                         // expected-note 
{{'lifetimebound' attribute appears here on the definition}}
+  return use_arg ? arg : data;
+}
+
+template <typename T>
+T &multi_spec(T &obj);
+
+template <>
+int &multi_spec<int>(int &obj);
+
+template <>
+MyObj &multi_spec<MyObj>(MyObj &obj); // expected-warning {{'lifetimebound' 
attribute on this definition is not visible to callers before the definition; 
add it to the declaration instead}}
+
+template <>
+MyObj &multi_spec<MyObj>(MyObj &obj [[clang::lifetimebound]]) { // 
expected-note {{'lifetimebound' attribute appears here on the definition}}
+  return obj;
+}
+
+template <>
+int &multi_spec<int>(int &obj) { return obj; }
+
+template <typename T>
+T &multi_redecl_spec(T &obj);
+
+template <>
+MyObj &multi_redecl_spec<MyObj>(MyObj &obj); // expected-warning 
{{'lifetimebound' attribute on this definition is not visible to callers before 
the definition; add it to the declaration instead}}
+
+template <>
+MyObj &multi_redecl_spec<MyObj>(MyObj &obj);
+
+template <>
+MyObj &multi_redecl_spec<MyObj>(MyObj &obj [[clang::lifetimebound]]) { // 
expected-note {{'lifetimebound' attribute appears here on the definition}}
+  return obj;
+}
+
 MyObj get_default_obj();
 const MyObj &default_arg_param(const MyObj& obj // expected-warning 
{{'lifetimebound' attribute on this definition is not visible to callers before 
the definition; add it to the declaration instead}}
                                                 // CHECK: 
fix-it:"{{.*}}":{[[@LINE-1]]:{{[0-9]+}}-[[@LINE-1]]:{{[0-9]+}}}:" 
{{\[\[clang::lifetimebound\]\]}}"

>From 69e6d88230c3628979f3b1476fdf7cb12540ad57 Mon Sep 17 00:00:00 2001
From: NeKon69 <[email protected]>
Date: Mon, 15 Jun 2026 12:40:10 +0300
Subject: [PATCH 2/4] simplify

---
 clang/lib/Analysis/LifetimeSafety/Checker.cpp | 20 ++++++++-----------
 1 file changed, 8 insertions(+), 12 deletions(-)

diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp 
b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index 0940dabbb856d..ffa5da0d0ae92 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -110,19 +110,15 @@ class LifetimeChecker {
       if (Redecl == FDef)
         continue;
       if (auto *MSI = Redecl->getMemberSpecializationInfo();
-          MSI && MSI->isExplicitSpecialization()) {
-        const auto *From = dyn_cast<FunctionDecl>(MSI->getInstantiatedFrom());
-        if (IsImplicitTemplateSpecialization(Redecl, From))
-          continue;
-        return Redecl;
-      }
+          MSI && MSI->isExplicitSpecialization())
+        if (!IsImplicitTemplateSpecialization(
+                Redecl, dyn_cast<FunctionDecl>(MSI->getInstantiatedFrom())))
+          return Redecl;
       if (auto *FTSI = Redecl->getTemplateSpecializationInfo();
-          FTSI && FTSI->isExplicitInstantiationOrSpecialization()) {
-        const FunctionDecl *Pattern = FTSI->getTemplate()->getTemplatedDecl();
-        if (IsImplicitTemplateSpecialization(Redecl, Pattern))
-          continue;
-        return Redecl;
-      }
+          FTSI && FTSI->isExplicitInstantiationOrSpecialization())
+        if (!IsImplicitTemplateSpecialization(
+                Redecl, FTSI->getTemplate()->getTemplatedDecl()))
+          return Redecl;
     }
     return nullptr;
   }

>From 5e25fa7ad63b5f2b5767fe974b5d6b10e8d5bb7f Mon Sep 17 00:00:00 2001
From: NeKon69 <[email protected]>
Date: Mon, 15 Jun 2026 12:49:09 +0300
Subject: [PATCH 3/4] cleanup

---
 clang/lib/Analysis/LifetimeSafety/Checker.cpp | 20 +++++++------------
 1 file changed, 7 insertions(+), 13 deletions(-)

diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp 
b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index ffa5da0d0ae92..8f4266eb7977b 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -88,21 +88,15 @@ class LifetimeChecker {
   /// declaration to target for attribute placement, if one exists. Skips
   /// implicit specialization redeclarations that are backed by the template
   /// pattern. In other cases, returns nullptr.
-  const FunctionDecl *
+  static const FunctionDecl *
   getExplicitSpecializationDeclForAttr(const FunctionDecl *FDef) {
     if (FDef->getTemplateSpecializationKindForInstantiation() !=
         TSK_ExplicitSpecialization)
       return nullptr;
 
-    const SourceManager &SM = AST.getSourceManager();
-    auto IsImplicitTemplateSpecialization = [&SM](const FunctionDecl *Redecl,
-                                                  const FunctionDecl *Pattern) 
{
-      return Pattern && Redecl->getTypeSourceInfo() &&
-             Pattern->getTypeSourceInfo() &&
-             SM.getFileLoc(
-                 Redecl->getTypeSourceInfo()->getTypeLoc().getBeginLoc()) ==
-                 SM.getFileLoc(
-                     Pattern->getTypeSourceInfo()->getTypeLoc().getBeginLoc());
+    auto IsImplicitTemplateSpecialization = [](const FunctionDecl *Redecl,
+                                               const FunctionDecl *Pattern) {
+      return Pattern && Redecl->getBeginLoc() == Pattern->getBeginLoc();
     };
 
     auto redecls = llvm::to_vector(FDef->redecls());
@@ -115,7 +109,7 @@ class LifetimeChecker {
                 Redecl, dyn_cast<FunctionDecl>(MSI->getInstantiatedFrom())))
           return Redecl;
       if (auto *FTSI = Redecl->getTemplateSpecializationInfo();
-          FTSI && FTSI->isExplicitInstantiationOrSpecialization())
+          FTSI && FTSI->isExplicitSpecialization())
         if (!IsImplicitTemplateSpecialization(
                 Redecl, FTSI->getTemplate()->getTemplatedDecl()))
           return Redecl;
@@ -415,8 +409,8 @@ class LifetimeChecker {
       FileID File = GetFile(FD);
       if (File == DefFile)
         return;
-      // For explicit specializations, skip the primary template in the redecl
-      // chain.
+      // For explicit specializations, skip redeclarations that do not belong 
to
+      // the same explicit-specialization instantiation path.
       if (FDef->getTemplateSpecializationKindForInstantiation() ==
               TSK_ExplicitSpecialization &&
           FD->getTemplateSpecializationKindForInstantiation() !=

>From 513f6ae174900cc2d6edb9207a7ae98237806dd6 Mon Sep 17 00:00:00 2001
From: NeKon69 <[email protected]>
Date: Tue, 28 Jul 2026 12:24:17 +0300
Subject: [PATCH 4/4] add comment, rename

---
 clang/lib/Analysis/LifetimeSafety/Checker.cpp | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/clang/lib/Analysis/LifetimeSafety/Checker.cpp 
b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
index 8f4266eb7977b..8d019d3e85f81 100644
--- a/clang/lib/Analysis/LifetimeSafety/Checker.cpp
+++ b/clang/lib/Analysis/LifetimeSafety/Checker.cpp
@@ -94,13 +94,15 @@ class LifetimeChecker {
         TSK_ExplicitSpecialization)
       return nullptr;
 
+    // FIXME: Remove this source-location heuristic once
+    // https://github.com/llvm/llvm-project/issues/206790 is fixed.
     auto IsImplicitTemplateSpecialization = [](const FunctionDecl *Redecl,
                                                const FunctionDecl *Pattern) {
       return Pattern && Redecl->getBeginLoc() == Pattern->getBeginLoc();
     };
 
-    auto redecls = llvm::to_vector(FDef->redecls());
-    for (const FunctionDecl *Redecl : llvm::reverse(redecls)) {
+    auto Redecls = llvm::to_vector(FDef->redecls());
+    for (const FunctionDecl *Redecl : llvm::reverse(Redecls)) {
       if (Redecl == FDef)
         continue;
       if (auto *MSI = Redecl->getMemberSpecializationInfo();

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to