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

Reply via email to