[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