[llvm-branch-commits] [clang] [Clang][CWG2369] Implement GCC's heuristic for DR 2369 (PR #124231)

Younan Zhang via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Thu Jan 23 22:04:31 PST 2025


https://github.com/zyn0217 created https://github.com/llvm/llvm-project/pull/124231

None

>From c36dd4fcac367b206072b36ccc9be4106a22ec3b Mon Sep 17 00:00:00 2001
From: Younan Zhang <zyn7109 at gmail.com>
Date: Fri, 24 Jan 2025 13:52:37 +0800
Subject: [PATCH] Implement GCC's CWG 2369 heuristic

---
 clang/include/clang/Sema/Sema.h               |   7 +-
 clang/lib/Sema/SemaOverload.cpp               |  70 +++++++-
 clang/lib/Sema/SemaTemplateDeduction.cpp      |  13 +-
 .../SemaTemplate/concepts-recursive-inst.cpp  | 169 ++++++++++++++++++
 4 files changed, 246 insertions(+), 13 deletions(-)

diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 87d9a335763e31..fd4d1f7e0d8f9c 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -10236,7 +10236,8 @@ class Sema final : public SemaBase {
       FunctionTemplateDecl *FunctionTemplate, ArrayRef<QualType> ParamTypes,
       ArrayRef<Expr *> Args, OverloadCandidateSet &CandidateSet,
       ConversionSequenceList &Conversions, bool SuppressUserConversions,
-      CXXRecordDecl *ActingContext = nullptr, QualType ObjectType = QualType(),
+      bool NonInstOnly, CXXRecordDecl *ActingContext = nullptr,
+      QualType ObjectType = QualType(),
       Expr::Classification ObjectClassification = {},
       OverloadCandidateParamOrder PO = {});
 
@@ -12272,7 +12273,7 @@ class Sema final : public SemaBase {
       sema::TemplateDeductionInfo &Info,
       SmallVectorImpl<OriginalCallArg> const *OriginalCallArgs = nullptr,
       bool PartialOverloading = false,
-      llvm::function_ref<bool()> CheckNonDependent = [] { return false; });
+      llvm::function_ref<bool(bool)> CheckNonDependent = [](bool) { return false; });
 
   /// Perform template argument deduction from a function call
   /// (C++ [temp.deduct.call]).
@@ -12306,7 +12307,7 @@ class Sema final : public SemaBase {
       FunctionDecl *&Specialization, sema::TemplateDeductionInfo &Info,
       bool PartialOverloading, bool AggregateDeductionCandidate,
       QualType ObjectType, Expr::Classification ObjectClassification,
-      llvm::function_ref<bool(ArrayRef<QualType>)> CheckNonDependent);
+      llvm::function_ref<bool(ArrayRef<QualType>, bool)> CheckNonDependent);
 
   /// Deduce template arguments when taking the address of a function
   /// template (C++ [temp.deduct.funcaddr]) or matching a specialization to
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 3be9ade80f1d94..aded8abe5b4f7b 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -7733,10 +7733,10 @@ void Sema::AddMethodTemplateCandidate(
           MethodTmpl, ExplicitTemplateArgs, Args, Specialization, Info,
           PartialOverloading, /*AggregateDeductionCandidate=*/false, ObjectType,
           ObjectClassification,
-          [&](ArrayRef<QualType> ParamTypes) {
+          [&](ArrayRef<QualType> ParamTypes, bool NonInstOnly) {
             return CheckNonDependentConversions(
                 MethodTmpl, ParamTypes, Args, CandidateSet, Conversions,
-                SuppressUserConversions, ActingContext, ObjectType,
+                SuppressUserConversions, NonInstOnly, ActingContext, ObjectType,
                 ObjectClassification, PO);
           });
       Result != TemplateDeductionResult::Success) {
@@ -7818,10 +7818,11 @@ void Sema::AddTemplateOverloadCandidate(
           PartialOverloading, AggregateCandidateDeduction,
           /*ObjectType=*/QualType(),
           /*ObjectClassification=*/Expr::Classification(),
-          [&](ArrayRef<QualType> ParamTypes) {
+          [&](ArrayRef<QualType> ParamTypes, bool NonInstOnly) {
             return CheckNonDependentConversions(
                 FunctionTemplate, ParamTypes, Args, CandidateSet, Conversions,
-                SuppressUserConversions, nullptr, QualType(), {}, PO);
+                SuppressUserConversions, NonInstOnly, nullptr, QualType(), {},
+                PO);
           });
       Result != TemplateDeductionResult::Success) {
     OverloadCandidate &Candidate =
@@ -7863,7 +7864,7 @@ bool Sema::CheckNonDependentConversions(
     FunctionTemplateDecl *FunctionTemplate, ArrayRef<QualType> ParamTypes,
     ArrayRef<Expr *> Args, OverloadCandidateSet &CandidateSet,
     ConversionSequenceList &Conversions, bool SuppressUserConversions,
-    CXXRecordDecl *ActingContext, QualType ObjectType,
+    bool NonInstOnly, CXXRecordDecl *ActingContext, QualType ObjectType,
     Expr::Classification ObjectClassification, OverloadCandidateParamOrder PO) {
   // FIXME: The cases in which we allow explicit conversions for constructor
   // arguments never consider calling a constructor template. It's not clear
@@ -7900,6 +7901,63 @@ bool Sema::CheckNonDependentConversions(
     }
   }
 
+  // https://gcc.gnu.org/git/gitweb.cgi?p=gcc.git;h=2154bcd6d43cfd821ca70e1583880c4ed955355d
+  auto ConversionMightInduceInstantiation = [&](QualType ParmType,
+                                                QualType ArgType) {
+    ParmType = ParmType.getNonReferenceType();
+    ArgType = ArgType.getNonReferenceType();
+    bool PointerConv = ParmType->isPointerType() && ArgType->isPointerType();
+    if (PointerConv) {
+      ParmType = ParmType->getPointeeType();
+      ArgType = ArgType->getPointeeType();
+    }
+
+    // If one of the types is a not-yet-instantiated class template
+    // specialization, then computing the conversion might instantiate it in
+    // order to inspect bases, conversion functions and/or converting
+    // constructors.
+    auto IsInstantiation = [&](QualType T) {
+      if (auto *RT = T->getAs<RecordType>()) {
+        if (auto *RD = dyn_cast<CXXRecordDecl>(RT->getDecl())) {
+          if (auto *ClassTemplateSpec =
+                  dyn_cast<ClassTemplateSpecializationDecl>(RD))
+            return ClassTemplateSpec->getSpecializationKind() == TSK_Undeclared;
+          if (RD->getInstantiatedFromMemberClass())
+            return RD->getMemberSpecializationInfo()
+                       ->getTemplateSpecializationKind() !=
+                   TemplateSpecializationKind::TSK_ExplicitSpecialization;
+        }
+      }
+      return false;
+    };
+    if (IsInstantiation(ParmType) || IsInstantiation(ArgType))
+      return true;
+
+    // Converting from one pointer type to another, or between reference-related
+    // types, always yields a standard conversion.
+    if (PointerConv || CompareReferenceRelationship(SourceLocation(), ParmType,
+                                                    ArgType) == Ref_Related)
+      return false;
+
+    // Converting to a non-aggregate class type will consider its user-declared
+    // constructors, which might induce instantiation.
+    if (auto *RT = ParmType->getAs<RecordType>())
+      if (auto *RD = dyn_cast<CXXRecordDecl>(RT->getDecl());
+          RD && RD->hasDefinition() && !RD->isAggregate())
+        return false;
+
+    // Similarly, converting from a class type will consider its conversion
+    // functions.
+    if (auto *RT = ArgType->getAs<RecordType>())
+      if (auto *RD = dyn_cast<CXXRecordDecl>(RT->getDecl()))
+        return RD->hasDefinition() &&
+               !RD->getVisibleConversionFunctions().empty();
+
+    // Otherwise, computing this conversion definitely won't induce template
+    // instantiation.
+    return false;
+  };
+
   unsigned Offset =
       Method && Method->hasCXXExplicitFunctionObjectParameter() ? 1 : 0;
 
@@ -7920,6 +7978,8 @@ bool Sema::CheckNonDependentConversions(
         // For members, 'this' got ConvIdx = 0 previously.
         ConvIdx = ThisConversions + I;
       }
+      if (NonInstOnly && ConversionMightInduceInstantiation(ParamType, Args[I]->getType()))
+        continue;
       Conversions[ConvIdx]
         = TryCopyInitialization(*this, Args[I], ParamType,
                                 SuppressUserConversions,
diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp
index e87a724f37fdd2..a44ad00d2cccfe 100644
--- a/clang/lib/Sema/SemaTemplateDeduction.cpp
+++ b/clang/lib/Sema/SemaTemplateDeduction.cpp
@@ -3907,7 +3907,7 @@ TemplateDeductionResult Sema::FinishTemplateArgumentDeduction(
     unsigned NumExplicitlySpecified, FunctionDecl *&Specialization,
     TemplateDeductionInfo &Info,
     SmallVectorImpl<OriginalCallArg> const *OriginalCallArgs,
-    bool PartialOverloading, llvm::function_ref<bool()> CheckNonDependent) {
+    bool PartialOverloading, llvm::function_ref<bool(bool)> CheckNonDependent) {
   // Unevaluated SFINAE context.
   EnterExpressionEvaluationContext Unevaluated(
       *this, Sema::ExpressionEvaluationContext::Unevaluated);
@@ -3965,6 +3965,9 @@ TemplateDeductionResult Sema::FinishTemplateArgumentDeduction(
     FD = const_cast<FunctionDecl *>(FDFriend);
     Owner = FD->getLexicalDeclContext();
   }
+
+  if (CheckNonDependent(/*NonInstOnly=*/true))
+    return TemplateDeductionResult::NonDependentConversionFailure;
   // C++20 [temp.deduct.general]p5: [CWG2369]
   //   If the function template has associated constraints, those constraints
   //   are checked for satisfaction. If the constraints are not satisfied, type
@@ -3995,7 +3998,7 @@ TemplateDeductionResult Sema::FinishTemplateArgumentDeduction(
   //   P with a type that was non-dependent before substitution of any
   //   explicitly-specified template arguments, if the corresponding argument
   //   A cannot be implicitly converted to P, deduction fails.
-  if (CheckNonDependent())
+  if (CheckNonDependent(/*NonInstOnly=*/false))
     return TemplateDeductionResult::NonDependentConversionFailure;
 
   MultiLevelTemplateArgumentList SubstArgs(
@@ -4485,7 +4488,7 @@ TemplateDeductionResult Sema::DeduceTemplateArguments(
     FunctionDecl *&Specialization, TemplateDeductionInfo &Info,
     bool PartialOverloading, bool AggregateDeductionCandidate,
     QualType ObjectType, Expr::Classification ObjectClassification,
-    llvm::function_ref<bool(ArrayRef<QualType>)> CheckNonDependent) {
+    llvm::function_ref<bool(ArrayRef<QualType>, bool)> CheckNonDependent) {
   if (FunctionTemplate->isInvalidDecl())
     return TemplateDeductionResult::Invalid;
 
@@ -4699,9 +4702,9 @@ TemplateDeductionResult Sema::DeduceTemplateArguments(
   runWithSufficientStackSpace(Info.getLocation(), [&] {
     Result = FinishTemplateArgumentDeduction(
         FunctionTemplate, Deduced, NumExplicitlySpecified, Specialization, Info,
-        &OriginalCallArgs, PartialOverloading, [&, CallingCtx]() {
+        &OriginalCallArgs, PartialOverloading, [&, CallingCtx](bool NonInstOnly) {
           ContextRAII SavedContext(*this, CallingCtx);
-          return CheckNonDependent(ParamTypesForArgChecking);
+          return CheckNonDependent(ParamTypesForArgChecking, NonInstOnly);
         });
   });
   return Result;
diff --git a/clang/test/SemaTemplate/concepts-recursive-inst.cpp b/clang/test/SemaTemplate/concepts-recursive-inst.cpp
index 30a410cef91ee9..f251ba6dc5b09d 100644
--- a/clang/test/SemaTemplate/concepts-recursive-inst.cpp
+++ b/clang/test/SemaTemplate/concepts-recursive-inst.cpp
@@ -143,3 +143,172 @@ namespace GH60323 {
             Size().sizeparens(i);
   }
 }
+
+namespace CWG2369_Regressions {
+
+// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109397
+namespace GCC_103997 {
+
+template<typename _type, typename _stream>
+concept streamable = requires(_stream &s, _type &&v) {
+  s << static_cast<_type &&>(v);
+};
+
+struct type_a {
+  template<typename _arg>
+  type_a &operator<<(_arg &&) {
+    // std::clog << "type_a" << std::endl;
+    return *this;
+  }
+};
+
+struct type_b {
+  type_b &operator<<(type_a const &) {
+    // std::clog << "type_b" << std::endl;
+    return *this;
+  }
+};
+
+struct type_c {
+  type_b b;
+  template<typename _arg>
+  requires streamable<_arg, type_b>
+  friend type_c &operator<<(type_c &c, _arg &&a) {
+    // std::clog << "type_c" << std::endl;
+    c.b << static_cast<_arg &&>(a);
+    return c;
+  }
+};
+
+void foo() {
+  type_a a;
+  type_c c;
+  a << c; // "type_a\n" (gcc gives error here)
+  c << a; // "type_c\ntype_b\n"
+}
+
+}
+
+// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108393
+namespace GCC_108393 {
+
+template<class>
+struct iterator_traits
+{};
+
+template<class T>
+  requires requires(T __t, T __u) { __t == __u; }
+struct iterator_traits<T>
+{};
+
+template<class T>
+concept C = requires { typename iterator_traits<T>::A; };
+
+struct unreachable_sentinel_t
+{
+  template<C _Iter>
+  friend constexpr bool operator==(unreachable_sentinel_t, const _Iter&) noexcept;
+};
+
+template<class T>
+struct S
+{};
+
+static_assert(!C<S<unreachable_sentinel_t>>);
+
+}
+
+// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=107429
+namespace GCC_107429 {
+
+struct tag_foo { } inline constexpr foo;
+struct tag_bar { } inline constexpr bar;
+
+template<typename... T>
+auto f(tag_foo, T... x)
+{
+  return (x + ...);
+}
+
+template<typename... T>
+concept fooable = requires (T... x) { f(foo, x...); };
+
+template<typename... T> requires (fooable<T...>)
+auto f(tag_bar, T... x)
+{
+  return f(foo, x...);
+}
+
+auto test()
+{
+  return f(bar, 1, 2, 3);
+}
+
+}
+
+namespace GCC_99599 {
+
+struct foo_tag {};
+struct bar_tag {};
+
+template <class T>
+concept fooable = requires(T it) {
+  invoke_tag(foo_tag{}, it); // <-- here
+};
+
+template <class T> auto invoke_tag(foo_tag, T in) { return in; }
+
+template <fooable T> auto invoke_tag(bar_tag, T it) { return it; }
+
+int main() {
+  // Neither line below compiles in GCC 11, independently of the other
+  return invoke_tag(foo_tag{}, 2) + invoke_tag(bar_tag{}, 2);
+}
+
+}
+
+// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99599#c22
+namespace GCC_99599_2 {
+
+template<typename T> class indirect {
+public:
+  template<typename U> requires
+    requires (const T& t, const U& u) { t == u; }
+  friend constexpr bool operator==(const indirect&, const U&) { return false; }
+
+private:
+  T* _M_ptr{};
+};
+
+indirect<int> i;
+bool b = i == 1;
+
+}
+
+namespace FAILED_GCC_110160 {
+// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=110160
+// Current heuristic FAILED; GCC trunk also failed
+// https://godbolt.org/z/r3Pz9Tehz
+#if 0
+#include <sstream>
+#include <string>
+
+template <class T>
+concept StreamCanReceiveString = requires(T& t, std::string s) {
+    { t << s };
+};
+
+struct NotAStream {};
+struct UnrelatedType {};
+
+template <StreamCanReceiveString S>
+S& operator<<(S& s, UnrelatedType) {
+    return s;
+}
+
+static_assert(!StreamCanReceiveString<NotAStream>);
+
+static_assert(StreamCanReceiveString<std::stringstream>);
+#endif
+}
+}



More information about the llvm-branch-commits mailing list