https://github.com/timothyanderson096-ocdealcheck updated 
https://github.com/llvm/llvm-project/pull/220204

>From 169a123e00909eb020eec7f2e3ab3db954f53b9b Mon Sep 17 00:00:00 2001
From: TIM ANDERSON <[email protected]>
Date: Tue, 1 Sep 2026 19:32:28 +1000
Subject: [PATCH 1/2] [clang] Diagnose missing members in incomplete CRTP
 classes

Restrict lookup suppression for being-defined classes to recursive lookups 
reached through a strict descendant class instantiation that suspended SFINAE. 
This preserves GH179118 while diagnosing GH220031 before malformed declarations 
reach CodeGen.

Fixes #220031
---
 clang/include/clang/Sema/Sema.h            |  45 +++++++++
 clang/lib/Sema/SemaExpr.cpp                |   6 +-
 clang/lib/Sema/SemaTemplateInstantiate.cpp |   2 +-
 clang/test/SemaTemplate/GH220031.cpp       | 106 +++++++++++++++++++++
 4 files changed, 157 insertions(+), 2 deletions(-)
 create mode 100644 clang/test/SemaTemplate/GH220031.cpp

diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 4650bd53775f7..b273902a3a1f1 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -12528,6 +12528,7 @@ class Sema final : public SemaBase {
 
   protected:
     Sema &S;
+    bool hadPreviousSFINAEContext() const { return Prev != nullptr; }
     ~SFINAEContextBase() { S.CurrentSFINAEContext = Prev; }
     SFINAEContextBase(const SFINAEContextBase &) = delete;
     SFINAEContextBase &operator=(const SFINAEContextBase &) = delete;
@@ -12536,8 +12537,31 @@ class Sema final : public SemaBase {
     SFINAETrap *Prev;
   };
 
+  struct ClassInstantiationSFINAEContext {
+    const CXXRecordDecl *Record;
+    bool HadActiveSFINAEContext;
+  };
+
   struct NonSFINAEContext : SFINAEContextBase {
     NonSFINAEContext(Sema &S) : SFINAEContextBase(S, nullptr) {}
+
+    NonSFINAEContext(Sema &S, const CXXRecordDecl *Record)
+        : SFINAEContextBase(S, nullptr),
+          Record(Record->getCanonicalDecl()) {
+      S.ClassInstantiationSFINAEContexts.push_back(
+          {this->Record, hadPreviousSFINAEContext()});
+    }
+
+    ~NonSFINAEContext() {
+      if (!Record)
+        return;
+      assert(!S.ClassInstantiationSFINAEContexts.empty());
+      assert(S.ClassInstantiationSFINAEContexts.back().Record == Record);
+      S.ClassInstantiationSFINAEContexts.pop_back();
+    }
+
+  private:
+    const CXXRecordDecl *Record = nullptr;
   };
 
   /// RAII class used to determine whether SFINAE has
@@ -13725,6 +13749,11 @@ class Sema final : public SemaBase {
 
   SFINAETrap *CurrentSFINAEContext = nullptr;
 
+  /// Active class instantiations and whether entering each one suspended a
+  /// SFINAE context.
+  SmallVector<ClassInstantiationSFINAEContext, 4>
+      ClassInstantiationSFINAEContexts;
+
   /// The number of \p CodeSynthesisContexts that are not template
   /// instantiations and, therefore, should not be counted as part of the
   /// instantiation depth.
@@ -13796,6 +13825,22 @@ class Sema final : public SemaBase {
     return CurrentSFINAEContext != nullptr;
   }
 
+  /// Whether \p Record is being instantiated and a strict descendant class
+  /// instantiation suspended a SFINAE context.
+  [[nodiscard]] bool hasSFINAEContextInDescendantClassInstantiation(
+      const CXXRecordDecl *Record) const {
+    Record = Record->getCanonicalDecl();
+    bool HasSFINAEContext = false;
+    for (auto I = ClassInstantiationSFINAEContexts.rbegin(),
+              E = ClassInstantiationSFINAEContexts.rend();
+         I != E; ++I) {
+      if (I->Record == Record)
+        return HasSFINAEContext;
+      HasSFINAEContext |= I->HadActiveSFINAEContext;
+    }
+    return false;
+  }
+
   /// Perform substitution on the type T with a given set of template
   /// arguments.
   ///
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 34f6ccdbc2fe6..93f9e1f4a7590 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -3067,8 +3067,12 @@ ExprResult Sema::BuildQualifiedDeclarationNameExpr(
     // is invalid because it's derived from an invalid base class, then missing
     // members were likely supposed to be inherited.
     DeclContext *DC = computeDeclContext(SS);
+    // A nested class instantiation may have disabled SFINAE while recursively
+    // looking back into a still-being-defined outer class.
     if (const auto *CD = dyn_cast<CXXRecordDecl>(DC))
-      if (CD->isInvalidDecl() || CD->isBeingDefined())
+      if (CD->isInvalidDecl() ||
+          (CD->isBeingDefined() && !isSFINAEContext() &&
+           hasSFINAEContextInDescendantClassInstantiation(CD)))
         return ExprError();
     Diag(NameInfo.getLoc(), diag::err_no_member)
       << NameInfo.getName() << DC << SS.getRange();
diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp 
b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index ffbe8bb0506bc..64ab8200f84a4 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -3607,7 +3607,7 @@ bool Sema::InstantiateClassImpl(
     Spec->setPointOfInstantiation(PointOfInstantiation);
   }
 
-  NonSFINAEContext _(*this);
+  NonSFINAEContext _(*this, Instantiation);
   InstantiatingTemplate Inst(*this, PointOfInstantiation, Instantiation);
   if (Inst.isInvalid())
     return true;
diff --git a/clang/test/SemaTemplate/GH220031.cpp 
b/clang/test/SemaTemplate/GH220031.cpp
new file mode 100644
index 0000000000000..ee56e80ac110b
--- /dev/null
+++ b/clang/test/SemaTemplate/GH220031.cpp
@@ -0,0 +1,106 @@
+// RUN: %clang_cc1 -std=c++11 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify %s
+
+template <bool B>
+struct EnableIf {};
+
+template <>
+struct EnableIf<true> {
+  using type = int;
+};
+
+template <class T, T V>
+struct IntegralConstant {
+  static constexpr T value = V;
+};
+
+namespace NonTemplate {
+
+template <class T>
+// expected-error@+1 {{no member named 'flag'}}
+struct Trait : IntegralConstant<bool, T::flag> {};
+
+template <class D>
+struct CRTP {
+  // expected-note@+1 {{in instantiation of template class}}
+  template <typename EnableIf<Trait<D>::value>::type = 0>
+  void fn() const {}
+};
+
+// expected-note@+1 {{in instantiation of template class}}
+struct Derived : CRTP<Derived> {
+  static constexpr bool flag = true;
+};
+
+} // namespace NonTemplate
+
+namespace Templated {
+
+template <class T>
+// expected-error@+1 {{no member named 'flag'}}
+struct Trait : IntegralConstant<bool, T::flag> {};
+
+template <class D>
+struct CRTP {
+  // expected-note@+1 {{in instantiation of template class}}
+  template <typename EnableIf<Trait<D>::value>::type = 0>
+  void fn() const {}
+};
+
+template <class T>
+// expected-note@+1 {{in instantiation of template class}}
+struct Derived : CRTP<Derived<T>> {};
+
+// expected-note@+1 {{in instantiation of template class}}
+Derived<void> derived;
+
+} // namespace Templated
+
+// Errors in a class body instantiated as a side effect of deduction are not in
+// the immediate context and must not be treated as substitution failures.
+namespace TargetOwnsSFINAE {
+
+template <bool>
+struct Holder {
+  using type = int;
+};
+
+template <class T>
+// expected-error@+1 {{no member named 'flag'}}
+struct Trait : Holder<T::flag> {};
+
+template <class T>
+// expected-note@+1 {{in instantiation of template class}}
+struct Bad : Trait<Bad<T>> {};
+
+template <class T>
+// expected-note@+1 {{in instantiation of template class}}
+typename Bad<T>::type probe(int);
+
+template <class>
+char probe(...);
+
+// expected-note@+1 {{while substituting explicitly-specified template}}
+int x = sizeof(probe<int>(0));
+
+} // namespace TargetOwnsSFINAE
+
+// A lookup performed directly in a live SFINAE context must still select the
+// fallback when the member is declared later in the class.
+namespace LiveSFINAE {
+
+using Yes = char[1];
+using No = char[2];
+
+template <class T, int = T::value>
+No &probe(int);
+
+template <class>
+Yes &probe(...);
+
+struct A {
+  static_assert(sizeof(probe<A>(0)) == sizeof(Yes), "");
+  static constexpr int value = 1;
+};
+
+} // namespace LiveSFINAE

>From 6492d0e5fa919135ba4f0ccbc0b2ef9def0b2819 Mon Sep 17 00:00:00 2001
From: TIM ANDERSON <[email protected]>
Date: Tue, 1 Sep 2026 20:53:17 +1000
Subject: [PATCH 2/2] [clang] Apply clang-format to Sema context constructor

---
 clang/include/clang/Sema/Sema.h | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index b273902a3a1f1..0c2c483e9c3cb 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -12546,8 +12546,7 @@ class Sema final : public SemaBase {
     NonSFINAEContext(Sema &S) : SFINAEContextBase(S, nullptr) {}
 
     NonSFINAEContext(Sema &S, const CXXRecordDecl *Record)
-        : SFINAEContextBase(S, nullptr),
-          Record(Record->getCanonicalDecl()) {
+        : SFINAEContextBase(S, nullptr), Record(Record->getCanonicalDecl()) {
       S.ClassInstantiationSFINAEContexts.push_back(
           {this->Record, hadPreviousSFINAEContext()});
     }

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

Reply via email to