Skip to content

[Clang] Better handle overload of explicit object member functions #147498

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

cor3ntin
Copy link
Contributor

@cor3ntin cor3ntin commented Jul 8, 2025

struct A {
    constexpr int f() const { return 1; }  // #1
};

struct B : A {
    using A::f;
    constexpr int f(this A) { return 2; }
};

static_assert(B{}.f() == 1);

In the above scenario, we failed to realize the second f was an overload.
This was caused by a few compounding deficiences in how we handle reference qualified member objects (and non-qualified implicit object member functions).

We also failed to consider that #1 has a better conversion sequence.

Fixes #99902

```cpp
struct A {
    constexpr int f() const { return 1; }  // #1
};

struct B : A {
    using A::f;
    constexpr int f(this A) { return 2; }
};

static_assert(B{}.f() == 1);
```

In the above scenario, we failed to realize the second `f`
was an overload.
This was caused by a few compounding deficiences in how
we handle reference qualified member objects (and non-qualified
implicit object member functions).

We also failed to consider that `#1` has a better conversion sequence.

Fixes llvm#99902
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Jul 8, 2025
@llvmbot
Copy link
Member

llvmbot commented Jul 8, 2025

@llvm/pr-subscribers-clang

Author: Corentin Jabot (cor3ntin)

Changes
struct A {
    constexpr int f() const { return 1; }  // #<!-- -->1
};

struct B : A {
    using A::f;
    constexpr int f(this A) { return 2; }
};

static_assert(B{}.f() == 1);

In the above scenario, we failed to realize the second f was an overload.
This was caused by a few compounding deficiences in how we handle reference qualified member objects (and non-qualified implicit object member functions).

We also failed to consider that #<!-- -->1 has a better conversion sequence.

Fixes #99902


Full diff: https://github.com/llvm/llvm-project/pull/147498.diff

3 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+1)
  • (modified) clang/lib/Sema/SemaOverload.cpp (+16-7)
  • (modified) clang/test/SemaCXX/cxx2b-deducing-this.cpp (+43)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index a6be59f1d6bd7..ba0c9cdcd11a6 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -903,6 +903,7 @@ Bug Fixes to C++ Support
 - Fix a bug where private access specifier of overloaded function not respected. (#GH107629)
 - Correctly handle allocations in the condition of a ``if constexpr``.(#GH120197) (#GH134820)
 - Fixed a crash when handling invalid member using-declaration in C++20+ mode. (#GH63254)
+- Fix the handling of overload sets mixing explicit and implicit member functions. (#GH99902)
 
 Bug Fixes to AST Handling
 ^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 34f512b13d6a8..c859268afa3b2 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -1483,8 +1483,8 @@ static bool IsOverloadOrOverrideImpl(Sema &SemaRef, FunctionDecl *New,
     if (BS.Ty != DS.Ty)
       return false;
 
-    if (Base->isLValueReferenceType())
-      return D->isLValueReferenceType();
+    if (Base->isLValueReferenceType() || D->isLValueReferenceType())
+      return Base->isLValueReferenceType() == D->isLValueReferenceType();
     return Base->isRValueReferenceType() == D->isRValueReferenceType();
   };
 
@@ -1536,10 +1536,19 @@ static bool IsOverloadOrOverrideImpl(Sema &SemaRef, FunctionDecl *New,
                !F->isExplicitObjectMemberFunction();
       };
 
-      if (IsImplicitWithNoRefQual(Old) != IsImplicitWithNoRefQual(New) &&
-          CompareType(OldObjectType.getNonReferenceType(),
-                      NewObjectType.getNonReferenceType()))
-        return true;
+      if ((Old->getRefQualifier() != RQ_None ||
+           Old->isExplicitObjectMemberFunction()) &&
+          IsImplicitWithNoRefQual(New) &&
+          OldObjectType->isRValueReferenceType())
+        NewObjectType =
+            SemaRef.getASTContext().getRValueReferenceType(NewObjectType);
+      else if ((New->getRefQualifier() != RQ_None ||
+                New->isExplicitObjectMemberFunction()) &&
+               IsImplicitWithNoRefQual(Old) &&
+               NewObjectType->isRValueReferenceType())
+        OldObjectType =
+            SemaRef.getASTContext().getRValueReferenceType(OldObjectType);
+
       return CompareType(OldObjectType, NewObjectType);
     }(OldMethod, NewMethod);
 
@@ -5922,7 +5931,7 @@ static ImplicitConversionSequence TryObjectArgumentInitialization(
   if (Method->isExplicitObjectMemberFunction()) {
     if (ExplicitParameterType.isNull())
       ExplicitParameterType = Method->getFunctionObjectParameterReferenceType();
-    OpaqueValueExpr TmpExpr(Loc, FromType.getNonReferenceType(),
+    OpaqueValueExpr TmpExpr(Loc, FromType,
                             ValueKindFromClassification(FromClassification));
     ImplicitConversionSequence ICS = TryCopyInitialization(
         S, &TmpExpr, ExplicitParameterType, SuppressUserConversion,
diff --git a/clang/test/SemaCXX/cxx2b-deducing-this.cpp b/clang/test/SemaCXX/cxx2b-deducing-this.cpp
index 2286da8d1c0e5..a4a5224782f5e 100644
--- a/clang/test/SemaCXX/cxx2b-deducing-this.cpp
+++ b/clang/test/SemaCXX/cxx2b-deducing-this.cpp
@@ -1168,3 +1168,46 @@ struct S {
   bool g() { return f(); } // expected-error {{no viable conversion from returned value of type 'S' to function return type 'bool'}}
 };
 }
+
+namespace GH99902 {
+struct A {
+    constexpr int f() const { return 1; }
+    constexpr int g() const & { return 1; }
+    constexpr int h() const && { return 1; }
+
+    constexpr int i() { return 1; }
+    constexpr int j() & { return 1; }
+    constexpr int k() && { return 1; }
+
+};
+
+struct B : A {
+    using A::f;
+    using A::g;
+    using A::h;
+    using A::i;
+    using A::j;
+    using A::k;
+    constexpr int f(this A) { return 2; }
+    constexpr int g(this A) { return 2; }
+    constexpr int h(this A) { return 2; }
+    constexpr int i(this A) { return 2; }
+    constexpr int j(this A) { return 2; }
+    constexpr int k(this A) { return 2; }
+};
+
+constexpr B b;
+static_assert(B{}.f() == 1);
+static_assert(B{}.g() == 1);
+static_assert(B{}.h() == 1);
+static_assert(B{}.i() == 1);
+static_assert(B{}.j() == 2);
+static_assert(B{}.k() == 1);
+static_assert(b.f() == 1);
+static_assert(b.g() == 1);
+static_assert(b.h() == 2);
+static_assert(b.i() == 2);
+static_assert(b.j() == 2);
+static_assert(b.k() == 2);
+
+}

CompareType(OldObjectType.getNonReferenceType(),
NewObjectType.getNonReferenceType()))
return true;
if ((Old->getRefQualifier() != RQ_None ||
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps too early in the morning, but this whole predicate (and the one below it) are both... rough :) Is there a good way to make them more readable by extracting named variables or something?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Clang chooses the incorrect overload when explicit and implicit member functions are involved
3 participants