DR1687: When overload resolution selects a built-in operator, implicit

conversions are only applied to operands of class type, and the second
standard conversion sequence is not applied.

When diagnosing an invalid builtin binary operator, talk about the
original types rather than the converted types. If these differ by a
user-defined conversion, tell the user what happened.

llvm-svn: 335781
This commit is contained in:
Richard Smith 2018-06-27 20:30:34 +00:00
parent 03e0d2d82b
commit 1ef7554efd
13 changed files with 168 additions and 74 deletions

View File

@ -2850,6 +2850,10 @@ public:
return const_cast<CastExpr *>(this)->getSubExprAsWritten();
}
/// If this cast applies a user-defined conversion, retrieve the conversion
/// function that it invokes.
NamedDecl *getConversionFunction() const;
typedef CXXBaseSpecifier **path_iterator;
typedef const CXXBaseSpecifier * const *path_const_iterator;
bool path_empty() const { return CastExprBits.BasePathSize == 0; }

View File

@ -5802,6 +5802,8 @@ def err_objc_object_assignment : Error<
"cannot assign to class object (%0 invalid)">;
def err_typecheck_invalid_operands : Error<
"invalid operands to binary expression (%0 and %1)">;
def note_typecheck_invalid_operands_converted : Note<
"%select{first|second}0 operand was implicitly converted to type %1">;
def err_typecheck_logical_vector_expr_gnu_cpp_restrict : Error<
"logical expression with vector %select{type %1 and non-vector type %2|types"
" %1 and %2}0 is only supported in C++">;

View File

@ -9316,9 +9316,16 @@ public:
/// A functional-style cast.
CCK_FunctionalCast,
/// A cast other than a C-style cast.
CCK_OtherCast
CCK_OtherCast,
/// A conversion for an operand of a builtin overloaded operator.
CCK_ForBuiltinOverloadedOp
};
static bool isCast(CheckedConversionKind CCK) {
return CCK == CCK_CStyleCast || CCK == CCK_FunctionalCast ||
CCK == CCK_OtherCast;
}
/// ImpCastExprToType - If Expr is not of type 'Type', insert an implicit
/// cast. If there is already an implicit cast, merge into the existing one.
/// If isLvalue, the result of the cast is an lvalue.

View File

@ -1673,23 +1673,22 @@ const char *CastExpr::getCastKindName(CastKind CK) {
}
namespace {
Expr *skipImplicitTemporary(Expr *expr) {
const Expr *skipImplicitTemporary(const Expr *E) {
// Skip through reference binding to temporary.
if (MaterializeTemporaryExpr *Materialize
= dyn_cast<MaterializeTemporaryExpr>(expr))
expr = Materialize->GetTemporaryExpr();
if (auto *Materialize = dyn_cast<MaterializeTemporaryExpr>(E))
E = Materialize->GetTemporaryExpr();
// Skip any temporary bindings; they're implicit.
if (CXXBindTemporaryExpr *Binder = dyn_cast<CXXBindTemporaryExpr>(expr))
expr = Binder->getSubExpr();
if (auto *Binder = dyn_cast<CXXBindTemporaryExpr>(E))
E = Binder->getSubExpr();
return expr;
return E;
}
}
Expr *CastExpr::getSubExprAsWritten() {
Expr *SubExpr = nullptr;
CastExpr *E = this;
const Expr *SubExpr = nullptr;
const CastExpr *E = this;
do {
SubExpr = skipImplicitTemporary(E->getSubExpr());
@ -1702,15 +1701,33 @@ Expr *CastExpr::getSubExprAsWritten() {
assert((isa<CXXMemberCallExpr>(SubExpr) ||
isa<BlockExpr>(SubExpr)) &&
"Unexpected SubExpr for CK_UserDefinedConversion.");
if (isa<CXXMemberCallExpr>(SubExpr))
SubExpr = cast<CXXMemberCallExpr>(SubExpr)->getImplicitObjectArgument();
if (auto *MCE = dyn_cast<CXXMemberCallExpr>(SubExpr))
SubExpr = MCE->getImplicitObjectArgument();
}
// If the subexpression we're left with is an implicit cast, look
// through that, too.
} while ((E = dyn_cast<ImplicitCastExpr>(SubExpr)));
return SubExpr;
return const_cast<Expr*>(SubExpr);
}
NamedDecl *CastExpr::getConversionFunction() const {
const Expr *SubExpr = nullptr;
for (const CastExpr *E = this; E; E = dyn_cast<ImplicitCastExpr>(SubExpr)) {
SubExpr = skipImplicitTemporary(E->getSubExpr());
if (E->getCastKind() == CK_ConstructorConversion)
return cast<CXXConstructExpr>(SubExpr)->getConstructor();
if (E->getCastKind() == CK_UserDefinedConversion) {
if (auto *MCE = dyn_cast<CXXMemberCallExpr>(SubExpr))
return MCE->getMethodDecl();
}
}
return nullptr;
}
CXXBaseSpecifier **CastExpr::path_buffer() {

View File

@ -8130,18 +8130,57 @@ Sema::CheckSingleAssignmentConstraints(QualType LHSType, ExprResult &CallerRHS,
RHS = E;
return Compatible;
}
if (ConvertRHS)
RHS = ImpCastExprToType(E, Ty, Kind);
}
return result;
}
namespace {
/// The original operand to an operator, prior to the application of the usual
/// arithmetic conversions and converting the arguments of a builtin operator
/// candidate.
struct OriginalOperand {
explicit OriginalOperand(Expr *Op) : Orig(Op), Conversion(nullptr) {
if (auto *MTE = dyn_cast<MaterializeTemporaryExpr>(Op))
Op = MTE->GetTemporaryExpr();
if (auto *BTE = dyn_cast<CXXBindTemporaryExpr>(Op))
Op = BTE->getSubExpr();
if (auto *ICE = dyn_cast<ImplicitCastExpr>(Op)) {
Orig = ICE->getSubExprAsWritten();
Conversion = ICE->getConversionFunction();
}
}
QualType getType() const { return Orig->getType(); }
Expr *Orig;
NamedDecl *Conversion;
};
}
QualType Sema::InvalidOperands(SourceLocation Loc, ExprResult &LHS,
ExprResult &RHS) {
OriginalOperand OrigLHS(LHS.get()), OrigRHS(RHS.get());
Diag(Loc, diag::err_typecheck_invalid_operands)
<< LHS.get()->getType() << RHS.get()->getType()
<< OrigLHS.getType() << OrigRHS.getType()
<< LHS.get()->getSourceRange() << RHS.get()->getSourceRange();
// If a user-defined conversion was applied to either of the operands prior
// to applying the built-in operator rules, tell the user about it.
if (OrigLHS.Conversion) {
Diag(OrigLHS.Conversion->getLocation(),
diag::note_typecheck_invalid_operands_converted)
<< 0 << LHS.get()->getType();
}
if (OrigRHS.Conversion) {
Diag(OrigRHS.Conversion->getLocation(),
diag::note_typecheck_invalid_operands_converted)
<< 1 << RHS.get()->getType();
}
return QualType();
}
@ -9887,7 +9926,7 @@ static QualType checkArithmeticOrEnumeralThreeWayCompare(Sema &S,
// type E, the operator yields the result of converting the operands
// to the underlying type of E and applying <=> to the converted operands.
if (!S.Context.hasSameUnqualifiedType(LHSStrippedType, RHSStrippedType)) {
S.InvalidOperands(Loc, LHSStripped, RHSStripped);
S.InvalidOperands(Loc, LHS, RHS);
return QualType();
}
QualType IntType =

View File

@ -3767,6 +3767,10 @@ Sema::PerformImplicitConversion(Expr *From, QualType ToType,
const ImplicitConversionSequence &ICS,
AssignmentAction Action,
CheckedConversionKind CCK) {
// C++ [over.match.oper]p7: [...] operands of class type are converted [...]
if (CCK == CCK_ForBuiltinOverloadedOp && !From->getType()->isRecordType())
return From;
switch (ICS.getKind()) {
case ImplicitConversionSequence::StandardConversion: {
ExprResult Res = PerformImplicitConversion(From, ToType, ICS.Standard,
@ -3826,6 +3830,12 @@ Sema::PerformImplicitConversion(Expr *From, QualType ToType,
From = CastArg.get();
// C++ [over.match.oper]p7:
// [...] the second standard conversion sequence of a user-defined
// conversion sequence is not applied.
if (CCK == CCK_ForBuiltinOverloadedOp)
return From;
return PerformImplicitConversion(From, ToType, ICS.UserDefined.After,
AA_Converting, CCK);
}
@ -4289,7 +4299,7 @@ Sema::PerformImplicitConversion(Expr *From, QualType ToType,
// If this conversion sequence succeeded and involved implicitly converting a
// _Nullable type to a _Nonnull one, complain.
if (CCK == CCK_ImplicitConversion)
if (!isCast(CCK))
diagnoseNullableToNonnullConversion(ToType, InitialFromType,
From->getLocStart());

View File

@ -3498,6 +3498,7 @@ static void addFixitForObjCARCConversion(Sema &S,
// We handle C-style and implicit casts here.
switch (CCK) {
case Sema::CCK_ImplicitConversion:
case Sema::CCK_ForBuiltinOverloadedOp:
case Sema::CCK_CStyleCast:
case Sema::CCK_OtherCast:
break;
@ -3651,11 +3652,13 @@ diagnoseObjCARCConversion(Sema &S, SourceRange castRange,
SourceLocation afterLParen = S.getLocForEndOfToken(castRange.getBegin());
SourceLocation noteLoc = afterLParen.isValid() ? afterLParen : loc;
unsigned convKindForDiag = Sema::isCast(CCK) ? 0 : 1;
// Bridge from an ARC type to a CF type.
if (castACTC == ACTC_retainable && isAnyRetainable(exprACTC)) {
S.Diag(loc, diag::err_arc_cast_requires_bridge)
<< unsigned(CCK == Sema::CCK_ImplicitConversion) // cast|implicit
<< convKindForDiag
<< 2 // of C pointer type
<< castExprType
<< unsigned(castType->isBlockPointerType()) // to ObjC|block type
@ -3697,7 +3700,7 @@ diagnoseObjCARCConversion(Sema &S, SourceRange castRange,
if (exprACTC == ACTC_retainable && isAnyRetainable(castACTC)) {
bool br = S.isKnownName("CFBridgingRetain");
S.Diag(loc, diag::err_arc_cast_requires_bridge)
<< unsigned(CCK == Sema::CCK_ImplicitConversion) // cast|implicit
<< convKindForDiag
<< unsigned(castExprType->isBlockPointerType()) // of ObjC|block type
<< castExprType
<< 2 // to C pointer type
@ -3734,7 +3737,7 @@ diagnoseObjCARCConversion(Sema &S, SourceRange castRange,
}
S.Diag(loc, diag::err_arc_mismatched_cast)
<< (CCK != Sema::CCK_ImplicitConversion)
<< !convKindForDiag
<< srcKind << castExprType << castType
<< castRange << castExpr->getSourceRange();
}
@ -4187,7 +4190,7 @@ Sema::CheckObjCConversion(SourceRange castRange, QualType castType,
if (exprACTC == ACTC_indirectRetainable && castACTC == ACTC_voidPtr)
return ACR_okay;
if (castACTC == ACTC_indirectRetainable && exprACTC == ACTC_voidPtr &&
CCK != CCK_ImplicitConversion)
isCast(CCK))
return ACR_okay;
switch (ARCCastChecker(Context, exprACTC, castACTC, false).Visit(castExpr)) {
@ -4212,8 +4215,7 @@ Sema::CheckObjCConversion(SourceRange castRange, QualType castType,
// If this is a non-implicit cast from id or block type to a
// CoreFoundation type, delay complaining in case the cast is used
// in an acceptable context.
if (exprACTC == ACTC_retainable && isAnyRetainable(castACTC) &&
CCK != CCK_ImplicitConversion)
if (exprACTC == ACTC_retainable && isAnyRetainable(castACTC) && isCast(CCK))
return ACR_unbridged;
// Issue a diagnostic about a missing @-sign when implicit casting a cstring

View File

@ -12191,7 +12191,8 @@ Sema::CreateOverloadedUnaryOp(SourceLocation OpLoc, UnaryOperatorKind Opc,
// break out so that we will build the appropriate built-in
// operator node.
ExprResult InputRes = PerformImplicitConversion(
Input, Best->BuiltinParamTypes[0], Best->Conversions[0], AA_Passing);
Input, Best->BuiltinParamTypes[0], Best->Conversions[0], AA_Passing,
CCK_ForBuiltinOverloadedOp);
if (InputRes.isInvalid())
return ExprError();
Input = InputRes.get();
@ -12435,16 +12436,16 @@ Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
// We matched a built-in operator. Convert the arguments, then
// break out so that we will build the appropriate built-in
// operator node.
ExprResult ArgsRes0 =
PerformImplicitConversion(Args[0], Best->BuiltinParamTypes[0],
Best->Conversions[0], AA_Passing);
ExprResult ArgsRes0 = PerformImplicitConversion(
Args[0], Best->BuiltinParamTypes[0], Best->Conversions[0],
AA_Passing, CCK_ForBuiltinOverloadedOp);
if (ArgsRes0.isInvalid())
return ExprError();
Args[0] = ArgsRes0.get();
ExprResult ArgsRes1 =
PerformImplicitConversion(Args[1], Best->BuiltinParamTypes[1],
Best->Conversions[1], AA_Passing);
ExprResult ArgsRes1 = PerformImplicitConversion(
Args[1], Best->BuiltinParamTypes[1], Best->Conversions[1],
AA_Passing, CCK_ForBuiltinOverloadedOp);
if (ArgsRes1.isInvalid())
return ExprError();
Args[1] = ArgsRes1.get();
@ -12647,16 +12648,16 @@ Sema::CreateOverloadedArraySubscriptExpr(SourceLocation LLoc,
// We matched a built-in operator. Convert the arguments, then
// break out so that we will build the appropriate built-in
// operator node.
ExprResult ArgsRes0 =
PerformImplicitConversion(Args[0], Best->BuiltinParamTypes[0],
Best->Conversions[0], AA_Passing);
ExprResult ArgsRes0 = PerformImplicitConversion(
Args[0], Best->BuiltinParamTypes[0], Best->Conversions[0],
AA_Passing, CCK_ForBuiltinOverloadedOp);
if (ArgsRes0.isInvalid())
return ExprError();
Args[0] = ArgsRes0.get();
ExprResult ArgsRes1 =
PerformImplicitConversion(Args[1], Best->BuiltinParamTypes[1],
Best->Conversions[1], AA_Passing);
ExprResult ArgsRes1 = PerformImplicitConversion(
Args[1], Best->BuiltinParamTypes[1], Best->Conversions[1],
AA_Passing, CCK_ForBuiltinOverloadedOp);
if (ArgsRes1.isInvalid())
return ExprError();
Args[1] = ArgsRes1.get();

View File

@ -112,9 +112,9 @@ namespace dr1512 { // dr1512: 4
#endif
}
template<typename T> struct Wrap { operator T(); };
void test_overload() {
#if __cplusplus >= 201103L
template<typename T> struct Wrap { operator T(); }; // expected-note 4{{converted to type 'nullptr_t'}} expected-note 4{{converted to type 'int *'}}
void test_overload() {
using nullptr_t = decltype(nullptr);
void(Wrap<nullptr_t>() == Wrap<nullptr_t>());
void(Wrap<nullptr_t>() != Wrap<nullptr_t>());
@ -123,16 +123,16 @@ namespace dr1512 { // dr1512: 4
void(Wrap<nullptr_t>() <= Wrap<nullptr_t>()); // expected-error {{invalid operands}}
void(Wrap<nullptr_t>() >= Wrap<nullptr_t>()); // expected-error {{invalid operands}}
// The wording change fails to actually disallow this. This is valid
// via the builtin operator<(int*, int*) etc.
// Under dr1213, this is ill-formed: we select the builtin operator<(int*, int*)
// but then only convert as far as 'nullptr_t', which we then can't convert to 'int*'.
void(Wrap<nullptr_t>() == Wrap<int*>());
void(Wrap<nullptr_t>() != Wrap<int*>());
void(Wrap<nullptr_t>() < Wrap<int*>());
void(Wrap<nullptr_t>() > Wrap<int*>());
void(Wrap<nullptr_t>() <= Wrap<int*>());
void(Wrap<nullptr_t>() >= Wrap<int*>());
#endif
void(Wrap<nullptr_t>() < Wrap<int*>()); // expected-error {{invalid operands to binary expression ('Wrap<nullptr_t>' and 'Wrap<int *>')}}
void(Wrap<nullptr_t>() > Wrap<int*>()); // expected-error {{invalid operands}}
void(Wrap<nullptr_t>() <= Wrap<int*>()); // expected-error {{invalid operands}}
void(Wrap<nullptr_t>() >= Wrap<int*>()); // expected-error {{invalid operands}}
}
#endif
}
namespace dr1518 { // dr1518: 4

View File

@ -1,7 +1,8 @@
// RUN: %clang_cc1 -std=c++98 -triple x86_64-unknown-unknown %s -verify -fexceptions -fcxx-exceptions -pedantic-errors
// RUN: %clang_cc1 -std=c++11 -triple x86_64-unknown-unknown %s -verify -fexceptions -fcxx-exceptions -pedantic-errors
// RUN: %clang_cc1 -std=c++14 -triple x86_64-unknown-unknown %s -verify -fexceptions -fcxx-exceptions -pedantic-errors
// RUN: %clang_cc1 -std=c++1z -triple x86_64-unknown-unknown %s -verify -fexceptions -fcxx-exceptions -pedantic-errors
// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-unknown %s -verify -fexceptions -fcxx-exceptions -pedantic-errors
// RUN: %clang_cc1 -std=c++2a -triple x86_64-unknown-unknown %s -verify -fexceptions -fcxx-exceptions -pedantic-errors
#if __cplusplus < 201103L
// expected-error@+1 {{variadic macro}}
@ -249,3 +250,22 @@ namespace dr1672 { // dr1672: 7
static_assert(!__is_standard_layout(Y<H>), "");
static_assert(!__is_standard_layout(Y<X>), "");
}
namespace dr1687 { // dr1687: 7
template<typename T> struct To {
operator T(); // expected-note 2{{first operand was implicitly converted to type 'int *'}}
// expected-note@-1 {{second operand was implicitly converted to type 'double'}}
#if __cplusplus > 201703L
// expected-note@-3 2{{operand was implicitly converted to type 'dr1687::E}}
#endif
};
int *a = To<int*>() + 100.0; // expected-error {{invalid operands to binary expression ('To<int *>' and 'double')}}
int *b = To<int*>() + To<double>(); // expected-error {{invalid operands to binary expression ('To<int *>' and 'To<double>')}}
#if __cplusplus > 201703L
enum E1 {};
enum E2 {};
auto c = To<E1>() <=> To<E2>(); // expected-error {{invalid operands to binary expression ('To<dr1687::E1>' and 'To<dr1687::E2>')}}
#endif
}

View File

@ -1,7 +1,7 @@
// RUN: %clang_cc1 -std=c++11 -verify %s -Wno-tautological-compare
struct A { operator decltype(nullptr)(); };
struct B { operator const int *(); };
struct A { operator decltype(nullptr)(); }; // expected-note 16{{implicitly converted}}
struct B { operator const int *(); }; // expected-note 8{{implicitly converted}}
void f(A a, B b, volatile int *pi) {
(void)(a == a);
(void)(a != a);
@ -12,39 +12,31 @@ void f(A a, B b, volatile int *pi) {
(void)(a == b);
(void)(a != b);
// FIXME: These cases were intended to be made ill-formed by N3624, but it
// fails to actually achieve this goal.
(void)(a < b);
(void)(a > b);
(void)(a <= b);
(void)(a >= b);
(void)(a < b); // expected-error {{invalid operands}}
(void)(a > b); // expected-error {{invalid operands}}
(void)(a <= b); // expected-error {{invalid operands}}
(void)(a >= b); // expected-error {{invalid operands}}
(void)(b == a);
(void)(b != a);
// FIXME: These cases were intended to be made ill-formed by N3624, but it
// fails to actually achieve this goal.
(void)(b < a);
(void)(b > a);
(void)(b <= a);
(void)(b >= a);
(void)(b < a); // expected-error {{invalid operands}}
(void)(b > a); // expected-error {{invalid operands}}
(void)(b <= a); // expected-error {{invalid operands}}
(void)(b >= a); // expected-error {{invalid operands}}
(void)(a == pi);
(void)(a != pi);
// FIXME: These cases were intended to be made ill-formed by N3624, but it
// fails to actually achieve this goal.
(void)(a < pi);
(void)(a > pi);
(void)(a <= pi);
(void)(a >= pi);
(void)(a < pi); // expected-error {{invalid operands}}
(void)(a > pi); // expected-error {{invalid operands}}
(void)(a <= pi); // expected-error {{invalid operands}}
(void)(a >= pi); // expected-error {{invalid operands}}
(void)(pi == a);
(void)(pi != a);
// FIXME: These cases were intended to be made ill-formed by N3624, but it
// fails to actually achieve this goal.
(void)(pi < a);
(void)(pi > a);
(void)(pi <= a);
(void)(pi >= a);
(void)(pi < a); // expected-error {{invalid operands}}
(void)(pi > a); // expected-error {{invalid operands}}
(void)(pi <= a); // expected-error {{invalid operands}}
(void)(pi >= a); // expected-error {{invalid operands}}
(void)(b == pi);
(void)(b != pi);

View File

@ -14,7 +14,7 @@ void PR16799() {
const char str[] = "string";
a(str);
// CHECK: :15:3: error: invalid operands to binary expression
// CHECK: ('const char *' and 'int')
// CHECK: ('const char [7]' and 'int')
// CHECK: a(str);
// CHECK: ^~~~~~
// CHECK: :3:11: note: expanded from macro 'a'

View File

@ -29,7 +29,7 @@ kernel void float_ops() {
#if __OPENCL_C_VERSION__ < 120
// expected-error@-2{{invalid operands}}
#endif
float ibaf = 0 & 0.0f; // expected-error {{invalid operands}}
float ibaf = 0 & 0.0f; // expected-error {{invalid operands to binary expression ('int' and 'float')}}
float ibof = 0 | 0.0f; // expected-error {{invalid operands}}
float bnf = ~0.0f;// expected-error {{invalid argument type}}
float lnf = !0.0f;