[Clang] Do not treat Foo -> const Foo conversion sequences as perfect (#148613)

For implicit object arguments.
This fixes a regression introduced by the "perfect match" overload
resolution mechanism introduced in 8c5a307.

Note that GCC allows the ambiguity between a const and non-const
candidate to be resolved. But this patch focuses on restoring the Clang
20 behavior, and to fix the cases which we did resolve incorrectly.

Fixes #147374
This commit is contained in:
Corentin Jabot
2025-07-15 10:18:25 +03:00
committed by GitHub
parent dae72bc659
commit acf07dc77c
3 changed files with 49 additions and 4 deletions

View File

@@ -350,6 +350,11 @@ class Sema;
LLVM_PREFERRED_TYPE(bool)
unsigned BindsToRvalue : 1;
/// Whether this was an identity conversion with qualification
/// conversion for the implicit object argument.
LLVM_PREFERRED_TYPE(bool)
unsigned IsImplicitObjectArgumentQualificationConversion : 1;
/// Whether this binds an implicit object argument to a
/// non-static member function without a ref-qualifier.
LLVM_PREFERRED_TYPE(bool)
@@ -448,11 +453,11 @@ class Sema;
#endif
return true;
}
if (!C.hasSameType(getFromType(), getToType(2)))
return false;
if (BindsToRvalue && IsLvalueReference)
return false;
return true;
if (IsImplicitObjectArgumentQualificationConversion)
return C.hasSameUnqualifiedType(getFromType(), getToType(2));
return C.hasSameType(getFromType(), getToType(2));
}
ImplicitConversionRank getRank() const;

View File

@@ -245,6 +245,7 @@ void StandardConversionSequence::setAsIdentityConversion() {
IsLvalueReference = true;
BindsToFunctionLvalue = false;
BindsToRvalue = false;
IsImplicitObjectArgumentQualificationConversion = false;
BindsImplicitObjectArgumentWithoutRefQualifier = false;
ObjCLifetimeConversionBinding = false;
FromBracedInitList = false;
@@ -5317,6 +5318,7 @@ TryReferenceInit(Sema &S, Expr *Init, QualType DeclType,
ICS.Standard.DirectBinding = BindsDirectly;
ICS.Standard.IsLvalueReference = !isRValRef;
ICS.Standard.BindsToFunctionLvalue = T2->isFunctionType();
ICS.Standard.IsImplicitObjectArgumentQualificationConversion = false;
ICS.Standard.BindsToRvalue = InitCategory.isRValue();
ICS.Standard.BindsImplicitObjectArgumentWithoutRefQualifier = false;
ICS.Standard.ObjCLifetimeConversionBinding =
@@ -5496,6 +5498,7 @@ TryReferenceInit(Sema &S, Expr *Init, QualType DeclType,
ICS.Standard.IsLvalueReference = !isRValRef;
ICS.Standard.BindsToFunctionLvalue = false;
ICS.Standard.BindsToRvalue = true;
ICS.Standard.IsImplicitObjectArgumentQualificationConversion = false;
ICS.Standard.BindsImplicitObjectArgumentWithoutRefQualifier = false;
ICS.Standard.ObjCLifetimeConversionBinding = false;
} else if (ICS.isUserDefined()) {
@@ -5518,6 +5521,8 @@ TryReferenceInit(Sema &S, Expr *Init, QualType DeclType,
ICS.UserDefined.After.IsLvalueReference = !isRValRef;
ICS.UserDefined.After.BindsToFunctionLvalue = false;
ICS.UserDefined.After.BindsToRvalue = !LValRefType;
ICS.UserDefined.After.IsImplicitObjectArgumentQualificationConversion =
false;
ICS.UserDefined.After.BindsImplicitObjectArgumentWithoutRefQualifier = false;
ICS.UserDefined.After.ObjCLifetimeConversionBinding = false;
ICS.UserDefined.After.FromBracedInitList = false;
@@ -5802,6 +5807,7 @@ TryListConversion(Sema &S, InitListExpr *From, QualType ToType,
StandardConversionSequence &SCS = Result.isStandard() ? Result.Standard :
Result.UserDefined.After;
SCS.ReferenceBinding = true;
SCS.IsImplicitObjectArgumentQualificationConversion = false;
SCS.IsLvalueReference = ToType->isLValueReferenceType();
SCS.BindsToRvalue = true;
SCS.BindsToFunctionLvalue = false;
@@ -5999,8 +6005,12 @@ static ImplicitConversionSequence TryObjectArgumentInitialization(
// affects the conversion rank.
QualType ClassTypeCanon = S.Context.getCanonicalType(ClassType);
ImplicitConversionKind SecondKind;
if (ClassTypeCanon == FromTypeCanon.getLocalUnqualifiedType()) {
bool IsQualificationConversion = false;
if (ImplicitParamType.getCanonicalType() == FromTypeCanon) {
SecondKind = ICK_Identity;
} else if (ClassTypeCanon == FromTypeCanon.getLocalUnqualifiedType()) {
SecondKind = ICK_Identity;
IsQualificationConversion = true;
} else if (S.IsDerivedFrom(Loc, FromType, ClassType)) {
SecondKind = ICK_Derived_To_Base;
} else if (!Method->isExplicitObjectMemberFunction()) {
@@ -6041,6 +6051,8 @@ static ImplicitConversionSequence TryObjectArgumentInitialization(
ICS.Standard.setFromType(FromType);
ICS.Standard.setAllToTypes(ImplicitParamType);
ICS.Standard.ReferenceBinding = true;
ICS.Standard.IsImplicitObjectArgumentQualificationConversion =
IsQualificationConversion;
ICS.Standard.DirectBinding = true;
ICS.Standard.IsLvalueReference = Method->getRefQualifier() != RQ_RValue;
ICS.Standard.BindsToFunctionLvalue = false;

View File

@@ -283,3 +283,31 @@ void f() {
}
#endif
namespace GH147374 {
struct String {};
template <typename T> void operator+(T, String &&) = delete;
struct Bar {
void operator+(String) const; // expected-note {{candidate function}}
friend void operator+(Bar, String) {}; // expected-note {{candidate function}}
};
struct Baz {
void operator+(String); // expected-note {{candidate function}}
friend void operator+(Baz, String) {}; // expected-note {{candidate function}}
};
void test() {
Bar a;
String b;
a + b;
//expected-error@-1 {{use of overloaded operator '+' is ambiguous (with operand types 'Bar' and 'String')}}
Baz z;
z + b;
//expected-error@-1 {{use of overloaded operator '+' is ambiguous (with operand types 'Baz' and 'String')}}
}
}