[c++20] Synthesis of defaulted comparison functions.

Array members are not yet handled. In addition, defaulted comparisons
can't yet find comparison operators by unqualified lookup (only by
member lookup and ADL). These issues will be fixed in follow-on changes.
This commit is contained in:
Richard Smith 2019-12-04 15:25:27 -08:00
parent 27f5d35137
commit cafc7416ba
16 changed files with 743 additions and 59 deletions

View File

@ -2115,6 +2115,16 @@ public:
FunctionDeclBits.IsExplicitlyDefaulted = ED; FunctionDeclBits.IsExplicitlyDefaulted = ED;
} }
/// True if this method is user-declared and was not
/// deleted or defaulted on its first declaration.
bool isUserProvided() const {
auto *DeclAsWritten = this;
if (FunctionDecl *Pattern = getTemplateInstantiationPattern())
DeclAsWritten = Pattern;
return !(DeclAsWritten->isDeleted() ||
DeclAsWritten->getCanonicalDecl()->isDefaulted());
}
/// Whether falling off this function implicitly returns null/zero. /// Whether falling off this function implicitly returns null/zero.
/// If a more specific implicit return value is required, front-ends /// If a more specific implicit return value is required, front-ends
/// should synthesize the appropriate return statements. /// should synthesize the appropriate return statements.

View File

@ -1995,16 +1995,6 @@ public:
return const_cast<CXXMethodDecl*>(this)->getMostRecentDecl(); return const_cast<CXXMethodDecl*>(this)->getMostRecentDecl();
} }
/// True if this method is user-declared and was not
/// deleted or defaulted on its first declaration.
bool isUserProvided() const {
auto *DeclAsWritten = this;
if (auto *Pattern = getTemplateInstantiationPattern())
DeclAsWritten = cast<CXXMethodDecl>(Pattern);
return !(DeclAsWritten->isDeleted() ||
DeclAsWritten->getCanonicalDecl()->isDefaulted());
}
void addOverriddenMethod(const CXXMethodDecl *MD); void addOverriddenMethod(const CXXMethodDecl *MD);
using method_iterator = const CXXMethodDecl *const *; using method_iterator = const CXXMethodDecl *const *;

View File

@ -1732,7 +1732,10 @@ def note_ivar_decl : Note<"instance variable is declared here">;
def note_bitfield_decl : Note<"bit-field is declared here">; def note_bitfield_decl : Note<"bit-field is declared here">;
def note_implicit_param_decl : Note<"%0 is an implicit parameter">; def note_implicit_param_decl : Note<"%0 is an implicit parameter">;
def note_member_synthesized_at : Note< def note_member_synthesized_at : Note<
"in implicit %sub{select_special_member_kind}0 for %1 " "in %select{implicit|defaulted}0 %sub{select_special_member_kind}1 for %2 "
"first required here">;
def note_comparison_synthesized_at : Note<
"in defaulted %sub{select_defaulted_comparison_kind}0 for %1 "
"first required here">; "first required here">;
def err_missing_default_ctor : Error< def err_missing_default_ctor : Error<
"%select{constructor for %1 must explicitly initialize the|" "%select{constructor for %1 must explicitly initialize the|"

View File

@ -3319,7 +3319,12 @@ public:
const UnresolvedSetImpl &Fns, const UnresolvedSetImpl &Fns,
Expr *LHS, Expr *RHS, Expr *LHS, Expr *RHS,
bool RequiresADL = true, bool RequiresADL = true,
bool AllowRewrittenCandidates = true); bool AllowRewrittenCandidates = true,
FunctionDecl *DefaultedFn = nullptr);
ExprResult BuildSynthesizedThreeWayComparison(SourceLocation OpLoc,
const UnresolvedSetImpl &Fns,
Expr *LHS, Expr *RHS,
FunctionDecl *DefaultedFn);
ExprResult CreateOverloadedArraySubscriptExpr(SourceLocation LLoc, ExprResult CreateOverloadedArraySubscriptExpr(SourceLocation LLoc,
SourceLocation RLoc, SourceLocation RLoc,
@ -6516,6 +6521,8 @@ public:
bool CheckExplicitlyDefaultedComparison(FunctionDecl *MD, bool CheckExplicitlyDefaultedComparison(FunctionDecl *MD,
DefaultedComparisonKind DCK); DefaultedComparisonKind DCK);
void DefineDefaultedComparison(SourceLocation Loc, FunctionDecl *FD,
DefaultedComparisonKind DCK);
//===--------------------------------------------------------------------===// //===--------------------------------------------------------------------===//
// C++ Derived Classes // C++ Derived Classes

View File

@ -6830,6 +6830,36 @@ public:
return StmtVisitorTy::Visit(Source); return StmtVisitorTy::Visit(Source);
} }
bool VisitPseudoObjectExpr(const PseudoObjectExpr *E) {
for (const Expr *SemE : E->semantics()) {
if (auto *OVE = dyn_cast<OpaqueValueExpr>(SemE)) {
// FIXME: We can't handle the case where an OpaqueValueExpr is also the
// result expression: there could be two different LValues that would
// refer to the same object in that case, and we can't model that.
if (SemE == E->getResultExpr())
return Error(E);
// Unique OVEs get evaluated if and when we encounter them when
// emitting the rest of the semantic form, rather than eagerly.
if (OVE->isUnique())
continue;
LValue LV;
if (!Evaluate(Info.CurrentCall->createTemporary(
OVE, getStorageType(Info.Ctx, OVE), false, LV),
Info, OVE->getSourceExpr()))
return false;
} else if (SemE == E->getResultExpr()) {
if (!StmtVisitorTy::Visit(SemE))
return false;
} else {
if (!EvaluateIgnoredValue(Info, SemE))
return false;
}
}
return true;
}
bool VisitCallExpr(const CallExpr *E) { bool VisitCallExpr(const CallExpr *E) {
APValue Result; APValue Result;
if (!handleCallExpr(E, Result, nullptr)) if (!handleCallExpr(E, Result, nullptr))

View File

@ -7085,7 +7085,8 @@ namespace {
/// ///
/// This is accomplished by performing two visitation steps over the eventual /// This is accomplished by performing two visitation steps over the eventual
/// body of the function. /// body of the function.
template<typename Derived, typename Result, typename Subobject> template<typename Derived, typename ResultList, typename Result,
typename Subobject>
class DefaultedComparisonVisitor { class DefaultedComparisonVisitor {
public: public:
using DefaultedComparisonKind = Sema::DefaultedComparisonKind; using DefaultedComparisonKind = Sema::DefaultedComparisonKind;
@ -7094,45 +7095,54 @@ public:
DefaultedComparisonKind DCK) DefaultedComparisonKind DCK)
: S(S), RD(RD), FD(FD), DCK(DCK) {} : S(S), RD(RD), FD(FD), DCK(DCK) {}
Result visit() { ResultList visit() {
// The type of an lvalue naming a parameter of this function. // The type of an lvalue naming a parameter of this function.
QualType ParamLvalType = QualType ParamLvalType =
FD->getParamDecl(0)->getType().getNonReferenceType(); FD->getParamDecl(0)->getType().getNonReferenceType();
ResultList Results;
switch (DCK) { switch (DCK) {
case DefaultedComparisonKind::None: case DefaultedComparisonKind::None:
llvm_unreachable("not a defaulted comparison"); llvm_unreachable("not a defaulted comparison");
case DefaultedComparisonKind::Equal: case DefaultedComparisonKind::Equal:
case DefaultedComparisonKind::ThreeWay: case DefaultedComparisonKind::ThreeWay:
return getDerived().visitSubobjects(RD, ParamLvalType.getQualifiers()); getDerived().visitSubobjects(Results, RD, ParamLvalType.getQualifiers());
return Results;
case DefaultedComparisonKind::NotEqual: case DefaultedComparisonKind::NotEqual:
case DefaultedComparisonKind::Relational: case DefaultedComparisonKind::Relational:
return getDerived().visitExpandedSubobject( Results.add(getDerived().visitExpandedSubobject(
ParamLvalType, getDerived().getCompleteObject()); ParamLvalType, getDerived().getCompleteObject()));
return Results;
} }
} }
protected: protected:
Derived &getDerived() { return static_cast<Derived&>(*this); } Derived &getDerived() { return static_cast<Derived&>(*this); }
Result visitSubobjects(CXXRecordDecl *Record, Qualifiers Quals) { /// Visit the expanded list of subobjects of the given type, as specified in
Result R; /// C++2a [class.compare.default].
// C++ [class.compare.default]p5: ///
// The direct base class subobjects of C [...] /// \return \c true if the ResultList object said we're done, \c false if not.
bool visitSubobjects(ResultList &Results, CXXRecordDecl *Record,
Qualifiers Quals) {
// C++2a [class.compare.default]p4:
// The direct base class subobjects of C
for (CXXBaseSpecifier &Base : Record->bases()) for (CXXBaseSpecifier &Base : Record->bases())
if (R.add(getDerived().visitSubobject( if (Results.add(getDerived().visitSubobject(
S.Context.getQualifiedType(Base.getType(), Quals), S.Context.getQualifiedType(Base.getType(), Quals),
getDerived().getBase(&Base)))) getDerived().getBase(&Base))))
return R; return true;
// followed by the non-static data members of C [...]
// followed by the non-static data members of C
for (FieldDecl *Field : Record->fields()) { for (FieldDecl *Field : Record->fields()) {
// Recursively expand anonymous structs. // Recursively expand anonymous structs.
if (Field->isAnonymousStructOrUnion()) { if (Field->isAnonymousStructOrUnion()) {
if (R.add( if (visitSubobjects(Results, Field->getType()->getAsCXXRecordDecl(),
visitSubobjects(Field->getType()->getAsCXXRecordDecl(), Quals))) Quals))
return R; return true;
continue; continue;
} }
@ -7143,12 +7153,13 @@ protected:
QualType FieldType = QualType FieldType =
S.Context.getQualifiedType(Field->getType(), FieldQuals); S.Context.getQualifiedType(Field->getType(), FieldQuals);
if (R.add(getDerived().visitSubobject(FieldType, if (Results.add(getDerived().visitSubobject(
getDerived().getField(Field)))) FieldType, getDerived().getField(Field))))
return R; return true;
} }
// form a list of subobjects. // form a list of subobjects.
return R; return false;
} }
Result visitSubobject(QualType Type, Subobject Subobj) { Result visitSubobject(QualType Type, Subobject Subobj) {
@ -7199,6 +7210,7 @@ struct DefaultedComparisonSubobject {
/// whether that body would be deleted or constexpr. /// whether that body would be deleted or constexpr.
class DefaultedComparisonAnalyzer class DefaultedComparisonAnalyzer
: public DefaultedComparisonVisitor<DefaultedComparisonAnalyzer, : public DefaultedComparisonVisitor<DefaultedComparisonAnalyzer,
DefaultedComparisonInfo,
DefaultedComparisonInfo, DefaultedComparisonInfo,
DefaultedComparisonSubobject> { DefaultedComparisonSubobject> {
public: public:
@ -7407,6 +7419,260 @@ private:
return R; return R;
} }
}; };
/// A list of statements.
struct StmtListResult {
bool IsInvalid = false;
llvm::SmallVector<Stmt*, 16> Stmts;
bool add(const StmtResult &S) {
IsInvalid |= S.isInvalid();
if (IsInvalid)
return true;
Stmts.push_back(S.get());
return false;
}
};
/// A visitor over the notional body of a defaulted comparison that synthesizes
/// the actual body.
class DefaultedComparisonSynthesizer
: public DefaultedComparisonVisitor<DefaultedComparisonSynthesizer,
StmtListResult, StmtResult,
std::pair<ExprResult, ExprResult>> {
SourceLocation Loc;
public:
using Base = DefaultedComparisonVisitor;
using ExprPair = std::pair<ExprResult, ExprResult>;
friend Base;
DefaultedComparisonSynthesizer(Sema &S, CXXRecordDecl *RD, FunctionDecl *FD,
DefaultedComparisonKind DCK,
SourceLocation BodyLoc)
: Base(S, RD, FD, DCK), Loc(BodyLoc) {}
/// Build a suitable function body for this defaulted comparison operator.
StmtResult build() {
Sema::CompoundScopeRAII CompoundScope(S);
StmtListResult Stmts = visit();
if (Stmts.IsInvalid)
return StmtError();
ExprResult RetVal;
switch (DCK) {
case DefaultedComparisonKind::None:
llvm_unreachable("not a defaulted comparison");
case DefaultedComparisonKind::Equal:
// C++2a [class.eq]p3:
// [...] compar[e] the corresponding elements [...] until the first
// index i where xi == yi yields [...] false. If no such index exists,
// V is true. Otherwise, V is false.
//
// Join the comparisons with '&&'s and return the result. Use a right
// fold because that short-circuits more naturally.
for (Stmt *EAsStmt : llvm::reverse(Stmts.Stmts)) {
Expr *E = cast<Expr>(EAsStmt);
if (RetVal.isUnset()) {
RetVal = E;
continue;
}
RetVal = S.CreateBuiltinBinOp(Loc, BO_LAnd, E, RetVal.get());
if (RetVal.isInvalid())
return StmtError();
}
// If no such index exists, V is true.
if (RetVal.isUnset())
RetVal = S.ActOnCXXBoolLiteral(Loc, tok::kw_true);
Stmts.Stmts.clear();
break;
case DefaultedComparisonKind::ThreeWay: {
// Per C++2a [class.spaceship]p3, as a fallback add:
// return static_cast<R>(std::strong_ordering::equal);
QualType StrongOrdering = S.CheckComparisonCategoryType(
ComparisonCategoryType::StrongOrdering, Loc);
if (StrongOrdering.isNull())
return StmtError();
VarDecl *EqualVD = S.Context.CompCategories.getInfoForType(StrongOrdering)
.getValueInfo(ComparisonCategoryResult::Equal)
->VD;
RetVal = S.BuildDeclarationNameExpr(
CXXScopeSpec(), DeclarationNameInfo(), EqualVD);
if (RetVal.isInvalid())
return StmtError();
RetVal = buildStaticCastToR(RetVal.get());
break;
}
case DefaultedComparisonKind::NotEqual:
case DefaultedComparisonKind::Relational:
RetVal = cast<Expr>(Stmts.Stmts.pop_back_val());
break;
}
// Build the final return statement.
if (RetVal.isInvalid())
return StmtError();
StmtResult ReturnStmt = S.BuildReturnStmt(Loc, RetVal.get());
if (ReturnStmt.isInvalid())
return StmtError();
Stmts.Stmts.push_back(ReturnStmt.get());
return S.ActOnCompoundStmt(Loc, Loc, Stmts.Stmts, /*IsStmtExpr=*/false);
}
private:
ExprResult getParam(unsigned I) {
ParmVarDecl *PD = FD->getParamDecl(I);
return S.BuildDeclarationNameExpr(
CXXScopeSpec(), DeclarationNameInfo(PD->getDeclName(), Loc), PD);
}
ExprPair getCompleteObject() {
unsigned Param = 0;
ExprResult LHS;
if (isa<CXXMethodDecl>(FD)) {
// LHS is '*this'.
LHS = S.ActOnCXXThis(Loc);
if (!LHS.isInvalid())
LHS = S.CreateBuiltinUnaryOp(Loc, UO_Deref, LHS.get());
} else {
LHS = getParam(Param++);
}
ExprResult RHS = getParam(Param++);
assert(Param == FD->getNumParams());
return {LHS, RHS};
}
ExprPair getBase(CXXBaseSpecifier *Base) {
ExprPair Obj = getCompleteObject();
if (Obj.first.isInvalid() || Obj.second.isInvalid())
return {ExprError(), ExprError()};
CXXCastPath Path = {Base};
return {S.ImpCastExprToType(Obj.first.get(), Base->getType(),
CK_DerivedToBase, VK_LValue, &Path),
S.ImpCastExprToType(Obj.second.get(), Base->getType(),
CK_DerivedToBase, VK_LValue, &Path)};
}
ExprPair getField(FieldDecl *Field) {
ExprPair Obj = getCompleteObject();
if (Obj.first.isInvalid() || Obj.second.isInvalid())
return {ExprError(), ExprError()};
DeclAccessPair Found = DeclAccessPair::make(Field, Field->getAccess());
DeclarationNameInfo NameInfo(Field->getDeclName(), Loc);
return {S.BuildFieldReferenceExpr(Obj.first.get(), /*IsArrow=*/false, Loc,
CXXScopeSpec(), Field, Found, NameInfo),
S.BuildFieldReferenceExpr(Obj.second.get(), /*IsArrow=*/false, Loc,
CXXScopeSpec(), Field, Found, NameInfo)};
}
// FIXME: When expanding a subobject, register a note in the code synthesis
// stack to say which subobject we're comparing.
// FIXME: Build a loop for an array subobject.
StmtResult visitExpandedSubobject(QualType Type, ExprPair Obj) {
UnresolvedSet<4> Fns; // FIXME: Track this.
if (Obj.first.isInvalid() || Obj.second.isInvalid())
return StmtError();
OverloadedOperatorKind OO = FD->getOverloadedOperator();
ExprResult Op = S.CreateOverloadedBinOp(
Loc, BinaryOperator::getOverloadedOpcode(OO), Fns,
Obj.first.get(), Obj.second.get(), /*PerformADL=*/true,
/*AllowRewrittenCandidates=*/true, FD);
if (Op.isInvalid())
return StmtError();
switch (DCK) {
case DefaultedComparisonKind::None:
llvm_unreachable("not a defaulted comparison");
case DefaultedComparisonKind::Equal:
// Per C++2a [class.eq]p2, each comparison is individually contextually
// converted to bool.
Op = S.PerformContextuallyConvertToBool(Op.get());
if (Op.isInvalid())
return StmtError();
return Op.get();
case DefaultedComparisonKind::ThreeWay: {
// Per C++2a [class.spaceship]p3, form:
// if (R cmp = static_cast<R>(op); cmp != 0)
// return cmp;
QualType R = FD->getReturnType();
Op = buildStaticCastToR(Op.get());
if (Op.isInvalid())
return StmtError();
// R cmp = ...;
IdentifierInfo *Name = &S.Context.Idents.get("cmp");
VarDecl *VD =
VarDecl::Create(S.Context, S.CurContext, Loc, Loc, Name, R,
S.Context.getTrivialTypeSourceInfo(R, Loc), SC_None);
S.AddInitializerToDecl(VD, Op.get(), /*DirectInit=*/false);
Stmt *InitStmt = new (S.Context) DeclStmt(DeclGroupRef(VD), Loc, Loc);
// cmp != 0
ExprResult VDRef = S.BuildDeclarationNameExpr(
CXXScopeSpec(), DeclarationNameInfo(Name, Loc), VD);
if (VDRef.isInvalid())
return StmtError();
llvm::APInt ZeroVal(S.Context.getIntWidth(S.Context.IntTy), 0);
Expr *Zero =
IntegerLiteral::Create(S.Context, ZeroVal, S.Context.IntTy, Loc);
ExprResult Comp = S.CreateOverloadedBinOp(Loc, BO_NE, Fns, VDRef.get(),
Zero, true, true, FD);
if (Comp.isInvalid())
return StmtError();
Sema::ConditionResult Cond = S.ActOnCondition(
nullptr, Loc, Comp.get(), Sema::ConditionKind::Boolean);
if (Cond.isInvalid())
return StmtError();
// return cmp;
VDRef = S.BuildDeclarationNameExpr(
CXXScopeSpec(), DeclarationNameInfo(Name, Loc), VD);
if (VDRef.isInvalid())
return StmtError();
StmtResult ReturnStmt = S.BuildReturnStmt(Loc, VDRef.get());
if (ReturnStmt.isInvalid())
return StmtError();
// if (...)
return S.ActOnIfStmt(Loc, /*IsConstexpr=*/false, InitStmt, Cond,
ReturnStmt.get(), /*ElseLoc=*/SourceLocation(),
/*Else=*/nullptr);
}
case DefaultedComparisonKind::NotEqual:
case DefaultedComparisonKind::Relational:
// C++2a [class.compare.secondary]p2:
// Otherwise, the operator function yields x @ y.
return Op.get();
}
}
/// Build "static_cast<R>(E)".
ExprResult buildStaticCastToR(Expr *E) {
QualType R = FD->getReturnType();
assert(!R->isUndeducedType() && "type should have been deduced already");
// Don't bother forming a no-op cast in the common case.
if (E->isRValue() && S.Context.hasSameType(E->getType(), R))
return E;
return S.BuildCXXNamedCast(Loc, tok::kw_static_cast,
S.Context.getTrivialTypeSourceInfo(R, Loc), E,
SourceRange(Loc, Loc), SourceRange(Loc, Loc));
}
};
} }
bool Sema::CheckExplicitlyDefaultedComparison(FunctionDecl *FD, bool Sema::CheckExplicitlyDefaultedComparison(FunctionDecl *FD,
@ -7540,6 +7806,43 @@ bool Sema::CheckExplicitlyDefaultedComparison(FunctionDecl *FD,
return false; return false;
} }
void Sema::DefineDefaultedComparison(SourceLocation UseLoc, FunctionDecl *FD,
DefaultedComparisonKind DCK) {
assert(FD->isDefaulted() && !FD->isDeleted() &&
!FD->doesThisDeclarationHaveABody());
if (FD->willHaveBody() || FD->isInvalidDecl())
return;
SynthesizedFunctionScope Scope(*this, FD);
// The exception specification is needed because we are defining the
// function.
// FIXME: Handle this better. Computing the exception specification will
// eventually need the function body.
ResolveExceptionSpec(UseLoc, FD->getType()->castAs<FunctionProtoType>());
// Add a context note for diagnostics produced after this point.
Scope.addContextNote(UseLoc);
// Build and set up the function body.
{
CXXRecordDecl *RD = cast<CXXRecordDecl>(FD->getLexicalParent());
SourceLocation BodyLoc =
FD->getEndLoc().isValid() ? FD->getEndLoc() : FD->getLocation();
StmtResult Body =
DefaultedComparisonSynthesizer(*this, RD, FD, DCK, BodyLoc).build();
if (Body.isInvalid()) {
FD->setInvalidDecl();
return;
}
FD->setBody(Body.get());
FD->markUsed(Context);
}
if (ASTMutationListener *L = getASTMutationListener())
L->CompletedImplicitDefinition(FD);
}
void Sema::CheckDelayedMemberExceptionSpecs() { void Sema::CheckDelayedMemberExceptionSpecs() {
decltype(DelayedOverridingExceptionSpecChecks) Overriding; decltype(DelayedOverridingExceptionSpecChecks) Overriding;
decltype(DelayedEquivalentExceptionSpecChecks) Equivalent; decltype(DelayedEquivalentExceptionSpecChecks) Equivalent;

View File

@ -15416,9 +15416,8 @@ static OdrUseContext isOdrUseContext(Sema &SemaRef) {
} }
static bool isImplicitlyDefinableConstexprFunction(FunctionDecl *Func) { static bool isImplicitlyDefinableConstexprFunction(FunctionDecl *Func) {
CXXMethodDecl *MD = dyn_cast<CXXMethodDecl>(Func);
return Func->isConstexpr() && return Func->isConstexpr() &&
(Func->isImplicitlyInstantiable() || (MD && !MD->isUserProvided())); (Func->isImplicitlyInstantiable() || !Func->isUserProvided());
} }
/// Mark a function referenced, and check whether it is odr-used /// Mark a function referenced, and check whether it is odr-used
@ -15566,6 +15565,12 @@ void Sema::MarkFunctionReferenced(SourceLocation Loc, FunctionDecl *Func,
MarkVTableUsed(Loc, MethodDecl->getParent()); MarkVTableUsed(Loc, MethodDecl->getParent());
} }
if (Func->isDefaulted() && !Func->isDeleted()) {
DefaultedComparisonKind DCK = getDefaultedComparisonKind(Func);
if (DCK != DefaultedComparisonKind::None)
DefineDefaultedComparison(Loc, Func, DCK);
}
// Implicit instantiation of function templates and member functions of // Implicit instantiation of function templates and member functions of
// class templates. // class templates.
if (Func->isImplicitlyInstantiable()) { if (Func->isImplicitlyInstantiable()) {

View File

@ -12835,11 +12835,19 @@ void Sema::LookupOverloadedBinOp(OverloadCandidateSet &CandidateSet,
/// ///
/// \param LHS Left-hand argument. /// \param LHS Left-hand argument.
/// \param RHS Right-hand argument. /// \param RHS Right-hand argument.
/// \param PerformADL Whether to consider operator candidates found by ADL.
/// \param AllowRewrittenCandidates Whether to consider candidates found by
/// C++20 operator rewrites.
/// \param DefaultedFn If we are synthesizing a defaulted operator function,
/// the function in question. Such a function is never a candidate in
/// our overload resolution. This also enables synthesizing a three-way
/// comparison from < and == as described in C++20 [class.spaceship]p1.
ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc, ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
BinaryOperatorKind Opc, BinaryOperatorKind Opc,
const UnresolvedSetImpl &Fns, Expr *LHS, const UnresolvedSetImpl &Fns, Expr *LHS,
Expr *RHS, bool PerformADL, Expr *RHS, bool PerformADL,
bool AllowRewrittenCandidates) { bool AllowRewrittenCandidates,
FunctionDecl *DefaultedFn) {
Expr *Args[2] = { LHS, RHS }; Expr *Args[2] = { LHS, RHS };
LHS=RHS=nullptr; // Please use only Args instead of LHS/RHS couple LHS=RHS=nullptr; // Please use only Args instead of LHS/RHS couple
@ -12906,6 +12914,8 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
OverloadCandidateSet CandidateSet( OverloadCandidateSet CandidateSet(
OpLoc, OverloadCandidateSet::CSK_Operator, OpLoc, OverloadCandidateSet::CSK_Operator,
OverloadCandidateSet::OperatorRewriteInfo(Op, AllowRewrittenCandidates)); OverloadCandidateSet::OperatorRewriteInfo(Op, AllowRewrittenCandidates));
if (DefaultedFn)
CandidateSet.exclude(DefaultedFn);
LookupOverloadedBinOp(CandidateSet, Op, Fns, Args, PerformADL); LookupOverloadedBinOp(CandidateSet, Op, Fns, Args, PerformADL);
bool HadMultipleCandidates = (CandidateSet.size() > 1); bool HadMultipleCandidates = (CandidateSet.size() > 1);
@ -13113,6 +13123,15 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
if (Opc == BO_Comma) if (Opc == BO_Comma)
break; break;
// When defaulting an 'operator<=>', we can try to synthesize a three-way
// compare result using '==' and '<'.
if (DefaultedFn && Opc == BO_Cmp) {
ExprResult E = BuildSynthesizedThreeWayComparison(OpLoc, Fns, Args[0],
Args[1], DefaultedFn);
if (E.isInvalid() || E.isUsable())
return E;
}
// For class as left operand for assignment or compound assignment // For class as left operand for assignment or compound assignment
// operator do not fall through to handling in built-in, but report that // operator do not fall through to handling in built-in, but report that
// no overloaded assignment operator found // no overloaded assignment operator found
@ -13194,6 +13213,111 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
return CreateBuiltinBinOp(OpLoc, Opc, Args[0], Args[1]); return CreateBuiltinBinOp(OpLoc, Opc, Args[0], Args[1]);
} }
ExprResult Sema::BuildSynthesizedThreeWayComparison(
SourceLocation OpLoc, const UnresolvedSetImpl &Fns, Expr *LHS, Expr *RHS,
FunctionDecl *DefaultedFn) {
const ComparisonCategoryInfo *Info =
Context.CompCategories.lookupInfoForType(DefaultedFn->getReturnType());
// If we're not producing a known comparison category type, we can't
// synthesize a three-way comparison. Let the caller diagnose this.
if (!Info)
return ExprResult((Expr*)nullptr);
// If we ever want to perform this synthesis more generally, we will need to
// apply the temporary materialization conversion to the operands.
assert(LHS->isGLValue() && RHS->isGLValue() &&
"cannot use prvalue expressions more than once");
Expr *OrigLHS = LHS;
Expr *OrigRHS = RHS;
// Replace the LHS and RHS with OpaqueValueExprs; we're going to refer to
// each of them multiple times below.
LHS = new (Context)
OpaqueValueExpr(LHS->getExprLoc(), LHS->getType(), LHS->getValueKind(),
LHS->getObjectKind(), LHS);
RHS = new (Context)
OpaqueValueExpr(RHS->getExprLoc(), RHS->getType(), RHS->getValueKind(),
RHS->getObjectKind(), RHS);
ExprResult Eq = CreateOverloadedBinOp(OpLoc, BO_EQ, Fns, LHS, RHS, true, true,
DefaultedFn);
if (Eq.isInvalid())
return ExprError();
ExprResult Less;
if (Info->isOrdered()) {
Less = CreateOverloadedBinOp(OpLoc, BO_LT, Fns, LHS, RHS, true, true,
DefaultedFn);
if (Less.isInvalid())
return ExprError();
}
ExprResult Greater;
if (Info->isOrdered()) {
Greater = CreateOverloadedBinOp(OpLoc, BO_LT, Fns, RHS, LHS, true, true,
DefaultedFn);
if (Greater.isInvalid())
return ExprError();
}
// Form the list of comparisons we're going to perform.
struct Comparison {
ExprResult Cmp;
ComparisonCategoryResult Result;
} Comparisons[4] =
{ {Eq, Info->isStrong() ? ComparisonCategoryResult::Equal
: ComparisonCategoryResult::Equivalent},
{Less, ComparisonCategoryResult::Less},
{Greater, ComparisonCategoryResult::Greater},
{ExprResult(), ComparisonCategoryResult::Unordered},
};
int I;
if (Info->isEquality()) {
Comparisons[1].Result = Info->isStrong()
? ComparisonCategoryResult::Nonequal
: ComparisonCategoryResult::Nonequivalent;
I = 1;
} else if (!Info->isPartial()) {
I = 2;
} else {
I = 3;
}
// Combine the comparisons with suitable conditional expressions.
ExprResult Result;
for (; I >= 0; --I) {
// Build a reference to the comparison category constant.
auto *VI = Info->lookupValueInfo(Comparisons[I].Result);
// FIXME: Missing a constant for a comparison category. Diagnose this?
if (!VI)
return ExprResult((Expr*)nullptr);
ExprResult ThisResult =
BuildDeclarationNameExpr(CXXScopeSpec(), DeclarationNameInfo(), VI->VD);
if (ThisResult.isInvalid())
return ExprError();
// Build a conditional unless this is the final case.
if (Result.get()) {
Result = ActOnConditionalOp(OpLoc, OpLoc, Comparisons[I].Cmp.get(),
ThisResult.get(), Result.get());
if (Result.isInvalid())
return ExprError();
} else {
Result = ThisResult;
}
}
// Build a PseudoObjectExpr to model the rewriting of an <=> operator, and to
// bind the OpaqueValueExprs before they're (repeatedly) used.
Expr *SyntacticForm = new (Context)
BinaryOperator(OrigLHS, OrigRHS, BO_Cmp, Result.get()->getType(),
Result.get()->getValueKind(),
Result.get()->getObjectKind(), OpLoc, FPFeatures);
Expr *SemanticForm[] = {LHS, RHS, Result.get()};
return PseudoObjectExpr::Create(Context, SyntacticForm, SemanticForm, 2);
}
ExprResult ExprResult
Sema::CreateOverloadedArraySubscriptExpr(SourceLocation LLoc, Sema::CreateOverloadedArraySubscriptExpr(SourceLocation LLoc,
SourceLocation RLoc, SourceLocation RLoc,

View File

@ -672,13 +672,23 @@ void Sema::PrintInstantiationStack() {
break; break;
case CodeSynthesisContext::DefiningSynthesizedFunction: { case CodeSynthesisContext::DefiningSynthesizedFunction: {
// FIXME: For synthesized members other than special members, produce a note. // FIXME: For synthesized functions that are not defaulted,
auto *MD = dyn_cast<CXXMethodDecl>(Active->Entity); // produce a note.
auto CSM = MD ? getSpecialMember(MD) : CXXInvalid; auto *FD = dyn_cast<FunctionDecl>(Active->Entity);
if (CSM != CXXInvalid) { DefaultedFunctionKind DFK =
FD ? getDefaultedFunctionKind(FD) : DefaultedFunctionKind();
if (DFK.isSpecialMember()) {
auto *MD = cast<CXXMethodDecl>(FD);
Diags.Report(Active->PointOfInstantiation, Diags.Report(Active->PointOfInstantiation,
diag::note_member_synthesized_at) diag::note_member_synthesized_at)
<< CSM << Context.getTagDeclType(MD->getParent()); << MD->isExplicitlyDefaulted() << DFK.asSpecialMember()
<< Context.getTagDeclType(MD->getParent());
} else if (DFK.isComparison()) {
Diags.Report(Active->PointOfInstantiation,
diag::note_comparison_synthesized_at)
<< (int)DFK.asComparison()
<< Context.getTagDeclType(
cast<CXXRecordDecl>(FD->getLexicalDeclContext()));
} }
break; break;
} }

View File

@ -14,7 +14,7 @@ struct A2 {
bool operator==(const A2&) const; bool operator==(const A2&) const;
bool operator!=(const A2&) const = default; bool operator!=(const A2&) const = default;
bool operator<=>(const A2&) const; int operator<=>(const A2&) const;
bool operator<(const A2&) const = default; bool operator<(const A2&) const = default;
bool operator<=(const A2&) const = default; bool operator<=(const A2&) const = default;
bool operator>(const A2&) const = default; bool operator>(const A2&) const = default;

View File

@ -0,0 +1,45 @@
// RUN: %clang_cc1 -std=c++2a -verify %s
// expected-no-diagnostics
namespace std {
struct strong_ordering {
int n;
constexpr operator int() const { return n; }
static const strong_ordering less, equal, greater;
};
constexpr strong_ordering strong_ordering::less{-1}, strong_ordering::equal{0}, strong_ordering::greater{1};
}
// Check that we compare subobjects in the right order.
struct Log {
char buff[8] = {};
int n = 0;
constexpr void add(char c) { buff[n++] = c; }
constexpr bool operator==(const char *p) const { return __builtin_strcmp(p, buff) == 0; }
};
template<char C> struct B {
Log *log;
constexpr bool operator==(const B&) const { log->add(C); return true; }
constexpr std::strong_ordering operator<=>(const B&) const { log->add(C); return {0}; }
};
struct C : B<'a'>, B<'b'> {
B<'c'> c;
B<'d'> d;
// FIXME: Test arrays once we handle them properly.
constexpr C(Log *p) : B<'a'>{p}, B<'b'>{p}, c{p}, d{p} {}
bool operator==(const C&) const = default;
std::strong_ordering operator<=>(const C&) const = default;
};
constexpr bool check(bool which) {
Log log;
C c(&log);
(void)(which ? c == c : c <=> c);
return log == "abcd";
}
static_assert(check(false));
static_assert(check(true));

View File

@ -8,13 +8,16 @@ struct D {
// expected-note@+1 {{candidate function (with reversed parameter order) not viable: 1st argument ('const}} // expected-note@+1 {{candidate function (with reversed parameter order) not viable: 1st argument ('const}}
bool operator==(D); bool operator==(D);
}; };
struct E { E(const E&) = delete; int operator==(E) const; }; struct E {
E(const E &) = delete; // expected-note {{deleted}}
int operator==(E) const; // expected-note {{passing}}
};
struct F { void operator==(F) const; }; struct F { void operator==(F) const; };
struct G { bool operator==(G) const = delete; }; // expected-note {{deleted here}} struct G { bool operator==(G) const = delete; }; // expected-note {{deleted here}}
template<typename T> struct X { template<typename T> struct X {
X(); X();
bool operator==(const X&) const = default; // expected-note 3{{deleted here}} bool operator==(const X&) const = default; // #x expected-note 3{{deleted here}}
T t; // expected-note 2{{because there is no viable comparison function for member 't'}} T t; // expected-note 2{{because there is no viable comparison function for member 't'}}
// expected-note@-1 {{because it would invoke a deleted comparison function for member 't'}} // expected-note@-1 {{because it would invoke a deleted comparison function for member 't'}}
}; };
@ -31,10 +34,13 @@ void test() {
void(X<D>() == X<D>()); // expected-error {{cannot be compared because its 'operator==' is implicitly deleted}} void(X<D>() == X<D>()); // expected-error {{cannot be compared because its 'operator==' is implicitly deleted}}
void(Mutable() == Mutable()); void(Mutable() == Mutable());
// FIXME: Not deleted, but once we start synthesizing comparison function definitions, we should reject this. // FIXME: We would benefit from a note identifying the member of 'X' we were comparing here and below.
void(X<E>() == X<E>()); // expected-error@#x {{call to deleted constructor of 'E'}}
// FIXME: Similarly, not deleted under P2002R0, but synthesized body is ill-formed. void(X<E>() == X<E>()); // expected-note {{in defaulted equality comparison operator for 'X<E>' first required here}}
void(X<F>() == X<F>());
// FIXME: We would benefit from a note pointing at the selected 'operator==' here.
// expected-error@#x {{value of type 'void' is not contextually convertible to 'bool'}}
void(X<F>() == X<F>()); // expected-note {{in defaulted equality comparison operator for 'X<F>' first required here}}
void(X<G>() == X<G>()); // expected-error {{cannot be compared because its 'operator==' is implicitly deleted}} void(X<G>() == X<G>()); // expected-error {{cannot be compared because its 'operator==' is implicitly deleted}}
} }

View File

@ -0,0 +1,11 @@
// RUN: %clang_cc1 -std=c++2a -verify %s
struct A {
int a, b, c;
bool operator==(const A&) const = default;
};
static_assert(A{1, 2, 3} == A{1, 2, 3});
static_assert(A{1, 2, 3} == A{0, 2, 3}); // expected-error {{failed}}
static_assert(A{1, 2, 3} == A{1, 0, 3}); // expected-error {{failed}}
static_assert(A{1, 2, 3} == A{1, 2, 0}); // expected-error {{failed}}

View File

@ -2,16 +2,23 @@
namespace Rel { namespace Rel {
struct A { struct A {
int operator<=>(A) const; int n;
constexpr int operator<=>(A a) const { return n - a.n; }
friend bool operator<(const A&, const A&) = default; friend bool operator<(const A&, const A&) = default;
friend bool operator<=(const A&, const A&) = default; friend bool operator<=(const A&, const A&) = default;
friend bool operator>(const A&, const A&) = default; friend bool operator>(const A&, const A&) = default;
friend bool operator>=(const A&, const A&) = default; friend bool operator>=(const A&, const A&) = default;
}; };
bool a1 = A() < A(); static_assert(A{0} < A{1});
bool a2 = A() <= A(); static_assert(A{1} < A{1}); // expected-error {{failed}}
bool a3 = A() > A(); static_assert(A{0} <= A{1});
bool a4 = A() >= A(); static_assert(A{1} <= A{1});
static_assert(A{2} <= A{1}); // expected-error {{failed}}
static_assert(A{1} > A{0});
static_assert(A{1} > A{1}); // expected-error {{failed}}
static_assert(A{1} >= A{0});
static_assert(A{1} >= A{1});
static_assert(A{1} >= A{2}); // expected-error {{failed}}
struct B { struct B {
bool operator<=>(B) const = delete; // expected-note 4{{deleted here}} expected-note-re 8{{candidate {{.*}} deleted}} bool operator<=>(B) const = delete; // expected-note 4{{deleted here}} expected-note-re 8{{candidate {{.*}} deleted}}
@ -37,10 +44,12 @@ namespace Rel {
// Under P2002R0, operator!= follows these rules too. // Under P2002R0, operator!= follows these rules too.
namespace NotEqual { namespace NotEqual {
struct A { struct A {
bool operator==(A) const; int n;
constexpr bool operator==(A a) const { return n == a.n; }
friend bool operator!=(const A&, const A&) = default; friend bool operator!=(const A&, const A&) = default;
}; };
bool a = A() != A(); static_assert(A{1} != A{2});
static_assert(A{1} != A{1}); // expected-error {{failed}}
struct B { struct B {
bool operator==(B) const = delete; // expected-note {{deleted here}} expected-note-re 2{{candidate {{.*}} deleted}} bool operator==(B) const = delete; // expected-note {{deleted here}} expected-note-re 2{{candidate {{.*}} deleted}}

View File

@ -1,13 +1,39 @@
// RUN: %clang_cc1 -std=c++2a -verify %s // RUN: %clang_cc1 -std=c++2a -verify %s -fcxx-exceptions
namespace std { namespace std {
struct strong_ordering { struct strong_ordering { // expected-note 3{{candidate}}
int n; int n;
constexpr operator int() const { return n; } constexpr operator int() const { return n; }
static const strong_ordering less, equal, greater; static const strong_ordering less, equal, greater;
}; };
constexpr strong_ordering strong_ordering::less{-1}, constexpr strong_ordering strong_ordering::less{-1},
strong_ordering::equal{0}, strong_ordering::greater{1}; strong_ordering::equal{0}, strong_ordering::greater{1};
struct weak_ordering {
int n;
constexpr weak_ordering(int n) : n(n) {}
constexpr weak_ordering(strong_ordering o) : n(o.n) {}
constexpr operator int() const { return n; }
static const weak_ordering less, equivalent, greater;
};
constexpr weak_ordering weak_ordering::less{-1},
weak_ordering::equivalent{0}, weak_ordering::greater{1};
struct partial_ordering {
double d;
constexpr partial_ordering(double d) : d(d) {}
constexpr partial_ordering(strong_ordering o) : d(o.n) {}
constexpr partial_ordering(weak_ordering o) : d(o.n) {}
constexpr operator double() const { return d; }
static const partial_ordering less, equivalent, greater, unordered;
};
constexpr partial_ordering partial_ordering::less{-1},
partial_ordering::equivalent{0}, partial_ordering::greater{1},
partial_ordering::unordered{__builtin_nan("")};
static_assert(!(partial_ordering::unordered < 0));
static_assert(!(partial_ordering::unordered == 0));
static_assert(!(partial_ordering::unordered > 0));
} }
namespace Deletedness { namespace Deletedness {
@ -59,7 +85,7 @@ namespace Deletedness {
// expected-note@#base {{deleted comparison function for base class 'E'}} // expected-note@#base {{deleted comparison function for base class 'E'}}
// expected-note@#base {{implied comparison for base class 'F' is ambiguous}} // expected-note@#base {{implied comparison for base class 'F' is ambiguous}}
template<typename T> struct Cmp : T { // #base template<typename T> struct Cmp : T { // #base
std::strong_ordering operator<=>(const Cmp&) const = default; // expected-note 5{{here}} std::strong_ordering operator<=>(const Cmp&) const = default; // #cmp expected-note 5{{here}}
}; };
void use(...); void use(...);
@ -72,10 +98,80 @@ namespace Deletedness {
Cmp<D2>() <=> Cmp<D2>(), // expected-error {{deleted}} Cmp<D2>() <=> Cmp<D2>(), // expected-error {{deleted}}
Cmp<E>() <=> Cmp<E>(), // expected-error {{deleted}} Cmp<E>() <=> Cmp<E>(), // expected-error {{deleted}}
Cmp<F>() <=> Cmp<F>(), // expected-error {{deleted}} Cmp<F>() <=> Cmp<F>(), // expected-error {{deleted}}
Cmp<G1>() <=> Cmp<G1>(), // FIXME: ok but synthesized body is ill-formed // FIXME: The following three errors are not very good.
Cmp<G2>() <=> Cmp<G2>(), // FIXME: ok but synthesized body is ill-formed // expected-error@#cmp {{value of type 'void' is not contextually convertible to 'bool'}}
Cmp<H>() <=> Cmp<H>(), // FIXME: ok but synthesized body is ill-formed Cmp<G1>() <=> Cmp<G1>(), // expected-note-re {{in defaulted three-way comparison operator for '{{.*}}Cmp<{{.*}}G1>' first required here}}j
// expected-error@#cmp {{value of type 'void' is not contextually convertible to 'bool'}}
Cmp<G2>() <=> Cmp<G2>(), // expected-note-re {{in defaulted three-way comparison operator for '{{.*}}Cmp<{{.*}}G2>' first required here}}j
// expected-error@#cmp {{no matching conversion for static_cast from 'void' to 'std::strong_ordering'}}
Cmp<H>() <=> Cmp<H>(), // expected-note-re {{in defaulted three-way comparison operator for '{{.*}}Cmp<{{.*}}H>' first required here}}j
0 0
); );
} }
} }
namespace Synthesis {
enum Result { False, True, Mu };
constexpr bool toBool(Result R) {
if (R == Mu) throw "should not ask this question";
return R == True;
}
struct Val {
Result equal, less;
constexpr bool operator==(const Val&) const { return toBool(equal); }
constexpr bool operator<(const Val&) const { return toBool(less); }
};
template<typename T> struct Cmp {
Val val;
friend T operator<=>(const Cmp&, const Cmp&) = default; // expected-note {{deleted}}
};
template<typename T> constexpr auto cmp(Result equal, Result less = Mu, Result reverse_less = Mu) {
return Cmp<T>{equal, less} <=> Cmp<T>{Mu, reverse_less};
}
static_assert(cmp<std::strong_ordering>(True) == 0);
static_assert(cmp<std::strong_ordering>(False, True) < 0);
static_assert(cmp<std::strong_ordering>(False, False) > 0);
static_assert(cmp<std::weak_ordering>(True) == 0);
static_assert(cmp<std::weak_ordering>(False, True) < 0);
static_assert(cmp<std::weak_ordering>(False, False) > 0);
static_assert(cmp<std::partial_ordering>(True) == 0);
static_assert(cmp<std::partial_ordering>(False, True) < 0);
static_assert(cmp<std::partial_ordering>(False, False, True) > 0);
static_assert(!(cmp<std::partial_ordering>(False, False, False) > 0));
static_assert(!(cmp<std::partial_ordering>(False, False, False) == 0));
static_assert(!(cmp<std::partial_ordering>(False, False, False) < 0));
// No synthesis is performed for a custom return type, even if it can be
// converted from a standard ordering.
struct custom_ordering {
custom_ordering(std::strong_ordering o);
};
void f(Cmp<custom_ordering> c) {
c <=> c; // expected-error {{deleted}}
}
}
namespace Preference {
struct A {
A(const A&) = delete; // expected-note {{deleted}}
// "usable" candidate that can't actually be called
friend void operator<=>(A, A); // expected-note {{passing}}
// Callable candidates for synthesis not considered.
friend bool operator==(A, A);
friend bool operator<(A, A);
};
struct B {
B();
A a;
std::strong_ordering operator<=>(const B&) const = default; // expected-error {{call to deleted constructor of 'Preference::A'}}
};
bool x = B() < B(); // expected-note {{in defaulted three-way comparison operator for 'Preference::B' first required here}}
}

View File

@ -0,0 +1,35 @@
// RUN: %clang_cc1 -std=c++2a -verify %s
namespace std {
struct strong_ordering {
int n;
constexpr operator int() const { return n; }
static const strong_ordering less, equal, greater;
};
constexpr strong_ordering strong_ordering::less{-1}, strong_ordering::equal{0}, strong_ordering::greater{1};
}
struct A {
int a, b, c;
std::strong_ordering operator<=>(const A&) const = default;
};
static_assert(A{1, 2, 3} <= A{1, 2, 3});
static_assert(A{1, 2, 3} <= A{0, 20, 3}); // expected-error {{failed}}
static_assert(A{1, 2, 3} <= A{1, 0, 30}); // expected-error {{failed}}
static_assert(A{1, 2, 3} <= A{1, 2, 0}); // expected-error {{failed}}
struct reverse_compare {
int n;
constexpr explicit reverse_compare(std::strong_ordering o) : n(-o.n) {}
constexpr operator int() const { return n; }
};
struct B {
int a, b, c;
friend reverse_compare operator<=>(const B&, const B&) = default;
};
static_assert(B{1, 2, 3} >= B{1, 2, 3});
static_assert(B{1, 2, 3} >= B{0, 20, 3}); // expected-error {{failed}}
static_assert(B{1, 2, 3} >= B{1, 0, 30}); // expected-error {{failed}}
static_assert(B{1, 2, 3} >= B{1, 2, 0}); // expected-error {{failed}}