https://github.com/zyn0217 updated https://github.com/llvm/llvm-project/pull/124231
>From c36dd4fcac367b206072b36ccc9be4106a22ec3b Mon Sep 17 00:00:00 2001 From: Younan Zhang <zyn7...@gmail.com> Date: Fri, 24 Jan 2025 13:52:37 +0800 Subject: [PATCH 1/2] 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 +} +} >From c3b1c7c4aef1090eb4987338a31e2d2c96fcce45 Mon Sep 17 00:00:00 2001 From: Younan Zhang <zyn7...@gmail.com> Date: Fri, 24 Jan 2025 14:53:36 +0800 Subject: [PATCH 2/2] fixup! Implement GCC's CWG 2369 heuristic --- clang/include/clang/Sema/Sema.h | 4 +++- clang/lib/Sema/SemaOverload.cpp | 10 ++++++---- clang/lib/Sema/SemaTemplateDeduction.cpp | 3 ++- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index fd4d1f7e0d8f9c..99ca65159106b5 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -12273,7 +12273,9 @@ class Sema final : public SemaBase { sema::TemplateDeductionInfo &Info, SmallVectorImpl<OriginalCallArg> const *OriginalCallArgs = nullptr, bool PartialOverloading = false, - llvm::function_ref<bool(bool)> CheckNonDependent = [](bool) { return false; }); + llvm::function_ref<bool(bool)> CheckNonDependent = [](bool) { + return false; + }); /// Perform template argument deduction from a function call /// (C++ [temp.deduct.call]). diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp index aded8abe5b4f7b..641fe622828332 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -7949,9 +7949,10 @@ bool Sema::CheckNonDependentConversions( // 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(); + if (auto *RD = dyn_cast<CXXRecordDecl>(RT->getDecl()); + RD && RD->hasDefinition() && + !RD->getVisibleConversionFunctions().empty()) + return true; // Otherwise, computing this conversion definitely won't induce template // instantiation. @@ -7978,7 +7979,8 @@ bool Sema::CheckNonDependentConversions( // For members, 'this' got ConvIdx = 0 previously. ConvIdx = ThisConversions + I; } - if (NonInstOnly && ConversionMightInduceInstantiation(ParamType, Args[I]->getType())) + if (NonInstOnly && + ConversionMightInduceInstantiation(ParamType, Args[I]->getType())) continue; Conversions[ConvIdx] = TryCopyInitialization(*this, Args[I], ParamType, diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp index a44ad00d2cccfe..dcaaed8613e47b 100644 --- a/clang/lib/Sema/SemaTemplateDeduction.cpp +++ b/clang/lib/Sema/SemaTemplateDeduction.cpp @@ -4702,7 +4702,8 @@ TemplateDeductionResult Sema::DeduceTemplateArguments( runWithSufficientStackSpace(Info.getLocation(), [&] { Result = FinishTemplateArgumentDeduction( FunctionTemplate, Deduced, NumExplicitlySpecified, Specialization, Info, - &OriginalCallArgs, PartialOverloading, [&, CallingCtx](bool NonInstOnly) { + &OriginalCallArgs, PartialOverloading, + [&, CallingCtx](bool NonInstOnly) { ContextRAII SavedContext(*this, CallingCtx); return CheckNonDependent(ParamTypesForArgChecking, NonInstOnly); }); _______________________________________________ llvm-branch-commits mailing list llvm-branch-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits