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
