https://github.com/JoostHouben created https://github.com/llvm/llvm-project/pull/230328
Fix for #75083. The first commit is a targeted fix for the issue as reported. The second commit is a broader fix in the presence of templates Assisted-by: OpenAI Codex ## Targeted fix Pass the parser function-body scope through defaulting so that the comparison checker captures the same ordinary operator candidates as a handwritten body. Keep in-class checking and instantiation on their existing paths, and save floating-point settings together with scoped comparison lookups. Cover member and friend comparisons, aliases and operator templates, qualified definitions, using declarations and directives, namespace hiding, ADL, three-way comparisons, and non-default FP settings. ## Broader fix Capture ordinary comparison lookup at an out-of-line defaulted definition before deferring dependent comparisons. Substitute the saved declarations when instantiating the definition, preserving empty lookup sets, access information, and floating-point features. Check first-declaration rules against the written template definition, rather than the instantiated declaration updated in place, so invalid comparisons are diagnosed instead of being deleted and then synthesized. Add regression coverage for definition-time lookup, nested instantiation, access, spaceship comparisons, PCH serialization, and FP features. >From 08cd652324d2336634802b09898d3855f284421b Mon Sep 17 00:00:00 2001 From: Joost Houben <[email protected]> Date: Thu, 8 Oct 2026 13:55:13 -0700 Subject: [PATCH 1/2] [clang] Perform ordinary lookup for out-of-line defaulted comparisons Pass the parser function-body scope through defaulting so that the comparison checker captures the same ordinary operator candidates as a handwritten body. Keep in-class checking and instantiation on their existing paths, and save floating-point settings together with scoped comparison lookups. Cover member and friend comparisons, aliases and operator templates, qualified definitions, using declarations and directives, namespace hiding, ADL, three-way comparisons, and non-default FP settings. Fixes #75083 Assisted-by: OpenAI Codex --- clang/include/clang/Sema/Sema.h | 6 +- clang/lib/Parse/Parser.cpp | 3 +- clang/lib/Sema/SemaDeclCXX.cpp | 14 +- .../SemaCXX/defaulted-comparison-lookup.cpp | 222 ++++++++++++++++++ 4 files changed, 236 insertions(+), 9 deletions(-) create mode 100644 clang/test/SemaCXX/defaulted-comparison-lookup.cpp diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 2c4986d56eede4..dab835ddc88271 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -6559,9 +6559,11 @@ class Sema final : public SemaBase { void ActOnPureSpecifier(Decl *D, SourceLocation PureSpecLoc); void SetDeclDeleted(Decl *dcl, SourceLocation DelLoc, StringLiteral *Message = nullptr); - void SetDeclDefaulted(Decl *dcl, SourceLocation DefaultLoc); + void SetDeclDefaulted(Decl *dcl, SourceLocation DefaultLoc, + Scope *S = nullptr); - void SetFunctionBodyKind(Decl *D, SourceLocation Loc, FnBodyKind BodyKind, + void SetFunctionBodyKind(Scope *S, Decl *D, SourceLocation Loc, + FnBodyKind BodyKind, StringLiteral *DeletedMessage = nullptr); void ActOnStartTrailingRequiresClause(Scope *S, Declarator &D); ExprResult ActOnFinishTrailingRequiresClause(ExprResult ConstraintExpr); diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp index c5d6a17fbfcd34..d000ef56e4984c 100644 --- a/clang/lib/Parse/Parser.cpp +++ b/clang/lib/Parse/Parser.cpp @@ -1377,7 +1377,8 @@ Decl *Parser::ParseFunctionDefinition(ParsingDeclarator &D, D.getMutableDeclSpec().abort(); if (BodyKind != Sema::FnBodyKind::Other) { - Actions.SetFunctionBodyKind(Res, KWLoc, BodyKind, DeletedMessage); + Actions.SetFunctionBodyKind(getCurScope(), Res, KWLoc, BodyKind, + DeletedMessage); Stmt *GeneratedBody = Res ? Res->getBody() : nullptr; Actions.ActOnFinishFunctionBody(Res, GeneratedBody, false); return Res; diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index f80fb2a3446b38..2c2680201ce216 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -18976,7 +18976,7 @@ void Sema::SetDeclDeleted(Decl *Dcl, SourceLocation DelLoc, Fn->setDeletedAsWritten(true, Message); } -void Sema::SetDeclDefaulted(Decl *Dcl, SourceLocation DefaultLoc) { +void Sema::SetDeclDefaulted(Decl *Dcl, SourceLocation DefaultLoc, Scope *S) { if (!Dcl || Dcl->isInvalidDecl()) return; @@ -19051,8 +19051,9 @@ void Sema::SetDeclDefaulted(Decl *Dcl, SourceLocation DefaultLoc) { // Only allocate DefaultedOrDeletedFunctionInfo if we actually have // non-default FP features to stash. This avoids memory overhead for - // the vast majority of defaulted functions. - if (!FD->getDefaultedOrDeletedInfo() && + // the vast majority of defaulted functions. Comparisons with a scope save + // these features along with their unqualified lookups below. + if (!FD->getDefaultedOrDeletedInfo() && (!DefKind.isComparison() || !S) && CurFPFeatureOverrides().requiresTrailingStorage()) { FD->setDefaultedOrDeletedInfo( FunctionDecl::DefaultedOrDeletedFunctionInfo::Create( @@ -19060,7 +19061,7 @@ void Sema::SetDeclDefaulted(Decl *Dcl, SourceLocation DefaultLoc) { } if (DefKind.isComparison()) { - if (CheckExplicitlyDefaultedComparison(nullptr, FD, DefKind.asComparison())) + if (CheckExplicitlyDefaultedComparison(S, FD, DefKind.asComparison())) FD->setInvalidDecl(); else DefineDefaultedComparison(DefaultLoc, FD, DefKind.asComparison()); @@ -19094,14 +19095,15 @@ void Sema::DiagnoseReturnInConstructorExceptionHandler(CXXTryStmt *TryBlock) { } } -void Sema::SetFunctionBodyKind(Decl *D, SourceLocation Loc, FnBodyKind BodyKind, +void Sema::SetFunctionBodyKind(Scope *S, Decl *D, SourceLocation Loc, + FnBodyKind BodyKind, StringLiteral *DeletedMessage) { switch (BodyKind) { case FnBodyKind::Delete: SetDeclDeleted(D, Loc, DeletedMessage); break; case FnBodyKind::Default: - SetDeclDefaulted(D, Loc); + SetDeclDefaulted(D, Loc, S); break; case FnBodyKind::Other: llvm_unreachable( diff --git a/clang/test/SemaCXX/defaulted-comparison-lookup.cpp b/clang/test/SemaCXX/defaulted-comparison-lookup.cpp new file mode 100644 index 00000000000000..378b8b7984ac5b --- /dev/null +++ b/clang/test/SemaCXX/defaulted-comparison-lookup.cpp @@ -0,0 +1,222 @@ +// RUN: %clang_cc1 -std=c++20 -verify %s +// RUN: %clang_cc1 -std=c++23 -verify %s +// RUN: %clang_cc1 -std=c++2c -verify %s + +#include "Inputs/std-compare.h" + +// GH75083: out-of-class defaulting must perform the ordinary operator lookup +// that would be performed in the function body, not just ADL. +namespace Foreign { +struct S { + int value; +}; +template <class T, int> struct CT { + T value; +}; +} // namespace Foreign + +namespace Member { +struct C { + Foreign::S s; + constexpr bool operator==(const C &) const; +}; + +// Deliberately declared after C, but before the defaulted definition. +constexpr bool operator==(Foreign::S a, Foreign::S b) { + return a.value == b.value; +} +constexpr bool C::operator==(const C &) const = default; +static_assert(C{{1}} == C{{1}}); +static_assert(!(C{{1}} == C{{2}})); + +struct Inline { + Foreign::S s; + constexpr bool operator==(const Inline &) const = default; +}; +static_assert(Inline{{1}} == Inline{{1}}); +static_assert(!(Inline{{1}} == Inline{{2}})); +} // namespace Member + +namespace Friend { +struct C { + Foreign::S s; + friend constexpr bool operator==(const C &, const C &); +}; +constexpr bool operator==(Foreign::S a, Foreign::S b) { + return a.value == b.value; +} +constexpr bool operator==(const C &, const C &) = default; +static_assert(C{{1}} == C{{1}}); +static_assert(!(C{{1}} == C{{2}})); + +struct ByValue { + Foreign::S s; + friend constexpr bool operator==(ByValue, ByValue); +}; +constexpr bool operator==(ByValue, ByValue) = default; +static_assert(ByValue{{1}} == ByValue{{1}}); +static_assert(!(ByValue{{1}} == ByValue{{2}})); + +struct Manual { + Foreign::S s; + friend constexpr bool operator==(const Manual &a, const Manual &b) { + return a.s == b.s; + } +}; +static_assert(Manual{{1}} == Manual{{1}}); +static_assert(!(Manual{{1}} == Manual{{2}})); +} // namespace Friend + +namespace OperatorTemplate { +using Alias = Foreign::CT<int, 2>; +struct C { + Alias a; + Foreign::CT<float, 2> b; + friend constexpr bool operator==(const C &, const C &); +}; +template <class T> +constexpr bool operator==(Foreign::CT<T, 2> a, Foreign::CT<T, 2> b) { + return a.value == b.value; +} +constexpr bool operator==(const C &, const C &) = default; +static_assert(C{{1}, {2.f}} == C{{1}, {2.f}}); +static_assert(!(C{{1}, {2.f}} == C{{2}, {2.f}})); +static_assert(!(C{{1}, {2.f}} == C{{1}, {3.f}})); +} // namespace OperatorTemplate + +namespace Qualified { +struct C { + Foreign::S s; + constexpr bool operator==(const C &) const; +}; +constexpr bool operator==(Foreign::S a, Foreign::S b) { + return a.value == b.value; +} +struct Manual { + Foreign::S s; + constexpr bool equal(const Manual &) const; +}; +} // namespace Qualified + +// The lexical namespace here is not the member function's semantic namespace. +constexpr bool Qualified::C::operator==(const C &) const = default; +constexpr bool Qualified::Manual::equal(const Manual &rhs) const { + return s == rhs.s; +} +static_assert(Qualified::C{{1}} == Qualified::C{{1}}); +static_assert(!(Qualified::C{{1}} == Qualified::C{{2}})); +static_assert(Qualified::Manual{{1}}.equal(Qualified::Manual{{1}})); + +namespace Imported { +constexpr bool operator==(Foreign::S a, Foreign::S b) { + return a.value == b.value; +} +} // namespace Imported + +namespace UsingDirective { +using namespace Imported; +struct C { + Foreign::S s; + constexpr bool operator==(const C &) const; +}; +constexpr bool C::operator==(const C &) const = default; +static_assert(C{{1}} == C{{1}}); +static_assert(!(C{{1}} == C{{2}})); +} // namespace UsingDirective + +namespace UsingDeclaration { +using Imported::operator==; +struct C { + Foreign::S s; + friend constexpr bool operator==(const C &, const C &); +}; +constexpr bool operator==(const C &, const C &) = default; +static_assert(C{{1}} == C{{1}}); +static_assert(!(C{{1}} == C{{2}})); +} // namespace UsingDeclaration + +namespace Hiding { +bool operator==(Foreign::S, Foreign::S); +namespace Inner { +struct Other {}; +bool operator==(Other, Other); // expected-note 2{{candidate function not viable}} +struct C { + Foreign::S s; // expected-note {{no viable 'operator==' for member 's'}} + bool operator==(const C &) const; +}; +bool C::operator==(const C &) const = default; // expected-error {{would delete it after its first declaration}} + +struct Manual { + Foreign::S s; + bool equal(const Manual &rhs) const { + return s == rhs.s; // expected-error {{invalid operands to binary expression}} + } +}; +} // namespace Inner +} // namespace Hiding + +namespace Associated { +struct S { + int value; +}; +} // namespace Associated +namespace ADL { +struct C { + Associated::S s; + friend constexpr bool operator==(const C &, const C &); +}; +} // namespace ADL +namespace Associated { +constexpr bool operator==(S a, S b) { return a.value == b.value; } +} // namespace Associated +namespace ADL { +constexpr bool operator==(const C &, const C &) = default; +static_assert(C{{1}} == C{{1}}); +static_assert(!(C{{1}} == C{{2}})); +} // namespace ADL + +namespace DirectThreeWay { +struct C { + Foreign::S s; + constexpr std::strong_ordering operator<=>(const C &) const; +}; +constexpr std::strong_ordering operator<=>(Foreign::S a, Foreign::S b) { + return a.value <=> b.value; +} +constexpr std::strong_ordering C::operator<=>(const C &) const = default; +static_assert((C{{1}} <=> C{{1}}) == 0); +static_assert((C{{1}} <=> C{{2}}) < 0); +static_assert((C{{2}} <=> C{{1}}) > 0); +} // namespace DirectThreeWay + +namespace ThreeWay { +struct C { + Foreign::S s; + constexpr std::strong_ordering operator<=>(const C &) const; +}; +constexpr bool operator==(Foreign::S a, Foreign::S b) { + return a.value == b.value; +} +constexpr bool operator<(Foreign::S a, Foreign::S b) { + return a.value < b.value; +} +// Explicit-category three-way comparison falls back to ordinary == and <. +constexpr std::strong_ordering C::operator<=>(const C &) const = default; +static_assert((C{{1}} <=> C{{1}}) == 0); +static_assert((C{{1}} <=> C{{2}}) < 0); +static_assert((C{{2}} <=> C{{1}}) > 0); +} // namespace ThreeWay + +namespace FPFeatures { +#pragma clang fp contract(fast) +struct C { + Foreign::S s; + constexpr bool operator==(const C &) const; +}; +constexpr bool operator==(Foreign::S a, Foreign::S b) { + return a.value == b.value; +} +constexpr bool C::operator==(const C &) const = default; +static_assert(C{{1}} == C{{1}}); +static_assert(!(C{{1}} == C{{2}})); +} // namespace FPFeatures >From 8eb25918736910086afc70df219d2b07f9e18ea8 Mon Sep 17 00:00:00 2001 From: Joost Houben <[email protected]> Date: Thu, 8 Oct 2026 14:07:56 -0700 Subject: [PATCH 2/2] [clang] Preserve definition-time lookup for defaulted template comparisons Capture ordinary comparison lookup at an out-of-line defaulted definition before deferring dependent comparisons. Substitute the saved declarations when instantiating the definition, preserving empty lookup sets, access information, and floating-point features. Check first-declaration rules against the written template definition, rather than the instantiated declaration updated in place, so invalid comparisons are diagnosed instead of being deleted and then synthesized. Add regression coverage for definition-time lookup, nested instantiation, access, spaceship comparisons, PCH serialization, and FP features. Assisted-by: OpenAI Codex --- clang/lib/Sema/SemaDeclCXX.cpp | 45 ++-- .../lib/Sema/SemaTemplateInstantiateDecl.cpp | 26 ++- .../defaulted-function-fp-features.cpp | 24 +++ clang/test/PCH/cxx2a-defaulted-comparison.cpp | 21 ++ .../defaulted-comparison-lookup-template.cpp | 196 ++++++++++++++++++ 5 files changed, 296 insertions(+), 16 deletions(-) create mode 100644 clang/test/SemaCXX/defaulted-comparison-lookup-template.cpp diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 2c2680201ce216..8313b7fccfa3e0 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -9049,20 +9049,25 @@ static void lookupOperatorsForDefaultedComparison(Sema &Self, Scope *S, } } +static void captureDefaultedComparisonLookups(Sema &Self, Scope *S, + FunctionDecl *FD) { + UnresolvedSet<32> Operators; + lookupOperatorsForDefaultedComparison(Self, S, Operators, + FD->getOverloadedOperator()); + // An empty set also records that lookup has already been performed. + FD->setDefaultedOrDeletedInfo( + FunctionDecl::DefaultedOrDeletedFunctionInfo::Create( + Self.Context, Operators.pairs(), Self.CurFPFeatureOverrides())); +} + bool Sema::CheckExplicitlyDefaultedComparison(Scope *S, FunctionDecl *FD, DefaultedComparisonKind DCK) { assert(DCK != DefaultedComparisonKind::None && "not a defaulted comparison"); // Perform any unqualified lookups we're going to need to default this // function. - if (S) { - UnresolvedSet<32> Operators; - lookupOperatorsForDefaultedComparison(*this, S, Operators, - FD->getOverloadedOperator()); - FD->setDefaultedOrDeletedInfo( - FunctionDecl::DefaultedOrDeletedFunctionInfo::Create( - Context, Operators.pairs(), CurFPFeatureOverrides())); - } + if (S && !FD->getDefaultedOrDeletedInfo()) + captureDefaultedComparisonLookups(*this, S, FD); // C++2a [class.compare.default]p1: // A defaulted comparison operator function for some class C shall be a @@ -9261,7 +9266,13 @@ bool Sema::CheckExplicitlyDefaultedComparison(Scope *S, FunctionDecl *FD, DefaultedComparisonInfo Info = DefaultedComparisonAnalyzer(*this, RD, FD, DCK).visit(); - bool First = FD == FD->getCanonicalDecl(); + // Function-body instantiation updates a declaration in place. Its canonical + // identity and lexical context need not describe the written definition. + const FunctionDecl *DeclAsWritten = FD; + if (const FunctionDecl *Pattern = FD->getTemplateInstantiationPattern()) + if (const FunctionDecl *Definition = Pattern->getDefinition()) + DeclAsWritten = Definition; + bool First = DeclAsWritten == DeclAsWritten->getCanonicalDecl(); if (!First) { if (Info.Deleted) { @@ -9278,13 +9289,13 @@ bool Sema::CheckExplicitlyDefaultedComparison(Scope *S, FunctionDecl *FD, .visit(); return true; } - if (isa<CXXRecordDecl>(FD->getLexicalDeclContext())) { + if (isa<CXXRecordDecl>(DeclAsWritten->getLexicalDeclContext())) { // C++20 [class.compare.default]p1: // [...] A definition of a comparison operator as defaulted that appears // in a class shall be the first declaration of that function. Diag(FD->getLocation(), diag::err_non_first_default_compare_in_class) << (int)DCK; - Diag(FD->getCanonicalDecl()->getLocation(), + Diag(DeclAsWritten->getCanonicalDecl()->getLocation(), diag::note_previous_declaration); return true; } @@ -19019,6 +19030,12 @@ void Sema::SetDeclDefaulted(Decl *Dcl, SourceLocation DefaultLoc, Scope *S) { FD->setExplicitlyDefaulted(); FD->setDefaultLoc(DefaultLoc); + // Save out-of-class comparison lookup while its function-body scope is + // available, even if checking and synthesis must wait for instantiation. + // In-class comparisons still perform lookup when the class is complete. + if (S && DefKind.isComparison() && !FD->getDefaultedOrDeletedInfo()) + captureDefaultedComparisonLookups(*this, S, FD); + // Defer checking functions that are defaulted in a dependent context. if (FD->isDependentContext()) return; @@ -19051,9 +19068,9 @@ void Sema::SetDeclDefaulted(Decl *Dcl, SourceLocation DefaultLoc, Scope *S) { // Only allocate DefaultedOrDeletedFunctionInfo if we actually have // non-default FP features to stash. This avoids memory overhead for - // the vast majority of defaulted functions. Comparisons with a scope save - // these features along with their unqualified lookups below. - if (!FD->getDefaultedOrDeletedInfo() && (!DefKind.isComparison() || !S) && + // the vast majority of defaulted functions. Scoped comparisons have already + // saved these features along with their unqualified lookups. + if (!FD->getDefaultedOrDeletedInfo() && CurFPFeatureOverrides().requiresTrailingStorage()) { FD->setDefaultedOrDeletedInfo( FunctionDecl::DefaultedOrDeletedFunctionInfo::Create( diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp index 5e72f05c863a0d..b4f63c956a06b7 100644 --- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -5834,8 +5834,10 @@ TemplateDeclInstantiator::InitMethodInstantiation(CXXMethodDecl *New, return false; } -bool TemplateDeclInstantiator::SubstDefaultedFunction(FunctionDecl *New, - FunctionDecl *Tmpl) { +static bool +substDefaultedFunctionInfo(Sema &SemaRef, FunctionDecl *New, + const FunctionDecl *Tmpl, + const MultiLevelTemplateArgumentList &TemplateArgs) { // Transfer across any unqualified lookups. if (auto *DFI = Tmpl->getDefaultedOrDeletedInfo()) { SmallVector<DeclAccessPair, 32> Lookups; @@ -5857,6 +5859,13 @@ bool TemplateDeclInstantiator::SubstDefaultedFunction(FunctionDecl *New, : DFI); } + return false; +} + +bool TemplateDeclInstantiator::SubstDefaultedFunction(FunctionDecl *New, + FunctionDecl *Tmpl) { + if (substDefaultedFunctionInfo(SemaRef, New, Tmpl, TemplateArgs)) + return true; SemaRef.SetDeclDefaulted(New, Tmpl->getLocation()); return false; } @@ -6226,6 +6235,19 @@ void Sema::InstantiateFunctionDefinition(SourceLocation PointOfInstantiation, }; if (PatternDecl->isDefaulted()) { + ContextRAII SavedContext(*this, Function); + if (!Function->getDefaultedOrDeletedInfo() && + PatternDecl->getDefaultedOrDeletedInfo()) { + MultiLevelTemplateArgumentList TemplateArgs = + getTemplateInstantiationArgs( + Function, PatternDecl->getLexicalDeclContext(), /*Final=*/false, + std::nullopt, /*RelativeToPrimary=*/false, PatternDecl); + if (substDefaultedFunctionInfo(*this, Function, PatternDecl, + TemplateArgs)) { + Function->setInvalidDecl(); + return; + } + } RebuildTypeSourceInfoForDefaultSpecialMembers(); SetDeclDefaulted(Function, PatternDecl->getLocation()); } else { diff --git a/clang/test/CodeGenCXX/defaulted-function-fp-features.cpp b/clang/test/CodeGenCXX/defaulted-function-fp-features.cpp index 809c1fc864618d..335c756230ec90 100644 --- a/clang/test/CodeGenCXX/defaulted-function-fp-features.cpp +++ b/clang/test/CodeGenCXX/defaulted-function-fp-features.cpp @@ -9,6 +9,8 @@ // CHECK-DAG: define {{.*}} @_ZeqRK14TemplateTargetIdES2_({{.*}}) [[NORMAL_ATTRS]] // CHECK-DAG: define {{.*}} @_ZeqRK14TemplateTargetIfES2_({{.*}}) [[NORMAL_ATTRS]] // CHECK-DAG: define {{.*}} @_ZeqRK20StrictTemplateTargetIdES2_({{.*}}) [[STRICT_ATTRS]] +// CHECK-DAG: define {{.*}} @_ZNK23OutOfLineTemplateTargetIdEeqERKS0_({{.*}}) [[NORMAL_ATTRS]] +// CHECK-DAG: define {{.*}} @_ZNK29StrictOutOfLineTemplateTargetIdEeqERKS0_({{.*}}) [[STRICT_ATTRS]] // --- NON-STRICT DECLARATIONS (at top of file, default FP is non-strict) --- @@ -40,6 +42,13 @@ bool test_template_non_strict(TemplateTarget<double> a, TemplateTarget<double> b return a == b; } +template <typename T> +struct OutOfLineTemplateTarget { + T d; + bool operator==(const OutOfLineTemplateTarget &) const; +}; +template <typename T> +bool OutOfLineTemplateTarget<T>::operator==(const OutOfLineTemplateTarget &) const = default; // --- STRICT CONTEXT (pragmas enabled) --- #pragma STDC FENV_ACCESS ON @@ -60,6 +69,10 @@ bool test_template_strict(TemplateTarget<float> a, TemplateTarget<float> b) { return a == b; } +bool test_out_of_line_non_strict(OutOfLineTemplateTarget<double> a, + OutOfLineTemplateTarget<double> b) { + return a == b; +} // Strict declarations (must get strictfp) struct StrictTarget { @@ -85,6 +98,13 @@ struct StrictTemplateTarget { friend bool operator==(const StrictTemplateTarget&, const StrictTemplateTarget&) = default; }; +template <typename T> +struct StrictOutOfLineTemplateTarget { + T d; + bool operator==(const StrictOutOfLineTemplateTarget &) const; +}; +template <typename T> +bool StrictOutOfLineTemplateTarget<T>::operator==(const StrictOutOfLineTemplateTarget &) const = default; // --- NON-STRICT CONTEXT AGAIN --- #pragma STDC FENV_ACCESS OFF @@ -95,6 +115,10 @@ bool test_strict_template_non_strict(StrictTemplateTarget<double> a, StrictTempl return a == b; } +bool test_strict_out_of_line_non_strict(StrictOutOfLineTemplateTarget<double> a, + StrictOutOfLineTemplateTarget<double> b) { + return a == b; +} // CHECK-DAG: attributes [[STRICT_ATTRS]] = { {{.*}}strictfp{{.*}} } // CHECK-DAG: attributes [[NORMAL_ATTRS]] = { {{.*}} } diff --git a/clang/test/PCH/cxx2a-defaulted-comparison.cpp b/clang/test/PCH/cxx2a-defaulted-comparison.cpp index bdf649919659da..b10fa5ba7c5990 100644 --- a/clang/test/PCH/cxx2a-defaulted-comparison.cpp +++ b/clang/test/PCH/cxx2a-defaulted-comparison.cpp @@ -39,6 +39,21 @@ namespace LookupContext { } } +namespace Foreign { + struct S { int value; }; +} +namespace OutOfLine { + template<class T> struct C { + T m; + constexpr bool operator==(const C &) const; + }; + constexpr bool operator==(Foreign::S a, Foreign::S b) { + return a.value == b.value; + } + template<class T> + constexpr bool C<T>::operator==(const C &) const = default; +} + #else namespace LookupContext { @@ -50,4 +65,10 @@ namespace LookupContext { } } +namespace Caller { + bool operator==(Foreign::S, Foreign::S) = delete; + static_assert(OutOfLine::C<Foreign::S>{{1}} == OutOfLine::C<Foreign::S>{{1}}); + static_assert(!(OutOfLine::C<Foreign::S>{{1}} == OutOfLine::C<Foreign::S>{{2}})); +} + #endif diff --git a/clang/test/SemaCXX/defaulted-comparison-lookup-template.cpp b/clang/test/SemaCXX/defaulted-comparison-lookup-template.cpp new file mode 100644 index 00000000000000..dc14a1641e256a --- /dev/null +++ b/clang/test/SemaCXX/defaulted-comparison-lookup-template.cpp @@ -0,0 +1,196 @@ +// RUN: %clang_cc1 -std=c++20 -verify %s +// RUN: %clang_cc1 -std=c++23 -verify %s +// RUN: %clang_cc1 -std=c++2c -verify %s + +#include "Inputs/std-compare.h" + +namespace Foreign { +struct S { + int value; +}; +struct Uncomparable {}; +} // namespace Foreign + +namespace Definition { +template <class T> struct C { + T m; + constexpr bool operator==(const C &) const; +}; +// This must be found at the defaulted definition, not the class declaration. +constexpr bool operator==(Foreign::S a, Foreign::S b) { + return a.value == b.value; +} +template <class T> +constexpr bool C<T>::operator==(const C &) const = default; +} // namespace Definition + +namespace Caller { +bool operator==(Foreign::S, Foreign::S) = delete; +static_assert(Definition::C<Foreign::S>{{1}} == Definition::C<Foreign::S>{{1}}); +static_assert(!(Definition::C<Foreign::S>{{1}} == Definition::C<Foreign::S>{{2}})); +} // namespace Caller + +namespace TooLate { +template <class T> struct C { + T m; // expected-note {{no viable 'operator==' for member 'm'}} + bool operator==(const C &) const; +}; +template <class T> +bool C<T>::operator==(const C &) const = default; // expected-error {{would delete it after its first declaration}} + +// Ordinary lookup must not gain candidates declared after the definition, +// even though the saved lookup set was empty. +bool operator==(Foreign::S, Foreign::S); +bool use(C<Foreign::S> a, C<Foreign::S> b) { + return a == b; // expected-note {{in instantiation of member function}} +} +} // namespace TooLate + +namespace DeletedMember { +template <class T> struct C { + T m; // expected-note {{no viable 'operator==' for member 'm'}} + bool operator==(const C &) const; +}; +template <class T> +bool C<T>::operator==(const C &) const = default; // expected-error {{would delete it after its first declaration}} + +bool use(C<Foreign::Uncomparable> a, C<Foreign::Uncomparable> b) { + return a == b; // expected-note {{in instantiation of member function}} +} +// An empty saved lookup set is also valid for builtin comparisons. +bool use(C<int> a, C<int> b) { return a == b; } +} // namespace DeletedMember + +namespace InlineTiming { +template <class T> struct C { + T m; // expected-note {{no viable 'operator==' for member 'm'}} + bool operator==(const C &) const = default; // expected-note {{explicitly defaulted function was implicitly deleted here}} +}; +bool operator==(Foreign::S, Foreign::S); +bool use(C<Foreign::S> a, C<Foreign::S> b) { + return a == b; // expected-error {{because its 'operator==' is implicitly deleted}} +} +} // namespace InlineTiming + +namespace Nested { +constexpr bool operator==(Foreign::S a, Foreign::S b) { + return a.value == b.value; +} +template <class T> struct Outer { + template <class U> struct Inner { + Foreign::S m; + constexpr bool operator==(const Inner &) const; + }; +}; +template <class T> +template <class U> +constexpr bool Outer<T>::Inner<U>::operator==(const Inner &) const = default; + +// Force partial instantiation before instantiating the inner comparison. +template struct Outer<int>; +using C = Outer<int>::Inner<float>; +static_assert(C{{1}} == C{{1}}); +static_assert(!(C{{1}} == C{{2}})); +} // namespace Nested + +namespace NestedTooLate { +template <class T> struct Outer { + template <class U> struct Inner { + Foreign::S m; // expected-note {{no viable 'operator==' for member 'm'}} + bool operator==(const Inner &) const; + }; +}; +template <class T> +template <class U> +bool Outer<T>::Inner<U>::operator==(const Inner &) const = default; // expected-error {{would delete it after its first declaration}} +bool operator==(Foreign::S, Foreign::S); +template struct Outer<int>; +bool use(Outer<int>::Inner<float> a, Outer<int>::Inner<float> b) { + return a == b; // expected-note {{in instantiation of member function}} +} +} // namespace NestedTooLate + +namespace Access { +template <class T> struct C; +struct M { + int value; + +private: + constexpr bool operator==(const M &rhs) const { + return value == rhs.value; + } + template <class T> friend struct C; +}; +template <class T> struct C { + M m; + constexpr bool operator==(const C &) const; +}; +template <class T> +constexpr bool C<T>::operator==(const C &) const = default; +static_assert(C<int>{{1}} == C<int>{{1}}); +static_assert(!(C<int>{{1}} == C<int>{{2}})); +} // namespace Access + +namespace LaterADL { +struct S { + int value; +}; +} // namespace LaterADL +namespace ADL { +template <class T> struct C { + T m; + constexpr bool operator==(const C &) const; +}; +template <class T> +constexpr bool C<T>::operator==(const C &) const = default; +} // namespace ADL +namespace LaterADL { +// Unlike ordinary lookup, ADL can find this at instantiation. +constexpr bool operator==(S a, S b) { return a.value == b.value; } +} // namespace LaterADL +static_assert(ADL::C<LaterADL::S>{{1}} == ADL::C<LaterADL::S>{{1}}); +static_assert(!(ADL::C<LaterADL::S>{{1}} == ADL::C<LaterADL::S>{{2}})); + +namespace ImplicitEquality { +template <class T> struct Value { + T value; +}; +} // namespace ImplicitEquality +namespace Spaceship { +template <class T> +constexpr bool operator==(ImplicitEquality::Value<T> a, + ImplicitEquality::Value<T> b) { + return a.value == b.value; +} +template <class T> +constexpr std::strong_ordering operator<=>(ImplicitEquality::Value<T> a, + ImplicitEquality::Value<T> b) { + return a.value <=> b.value; +} +template <class T> struct C { + ImplicitEquality::Value<T> m; + constexpr auto operator<=>(const C &) const = default; +}; +static_assert(C<int>{{1}} == C<int>{{1}}); +static_assert(!(C<int>{{1}} == C<int>{{2}})); +static_assert((C<int>{{1}} <=> C<int>{{1}}) == 0); +static_assert((C<int>{{1}} <=> C<int>{{2}}) < 0); +} // namespace Spaceship + +namespace OutOfLineSpaceship { +template <class T> struct C { + T m; + constexpr std::strong_ordering operator<=>(const C &) const; +}; +constexpr bool operator==(Foreign::S a, Foreign::S b) { + return a.value == b.value; +} +constexpr bool operator<(Foreign::S a, Foreign::S b) { + return a.value < b.value; +} +template <class T> +constexpr std::strong_ordering C<T>::operator<=>(const C &) const = default; +static_assert((C<Foreign::S>{{1}} <=> C<Foreign::S>{{1}}) == 0); +static_assert((C<Foreign::S>{{1}} <=> C<Foreign::S>{{2}}) < 0); +static_assert((C<Foreign::S>{{2}} <=> C<Foreign::S>{{1}}) > 0); +} // namespace OutOfLineSpaceship _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
