diff options
| -rw-r--r-- | clang/include/clang/AST/Decl.h | 10 | ||||
| -rw-r--r-- | clang/include/clang/AST/DeclCXX.h | 10 | ||||
| -rw-r--r-- | clang/include/clang/Basic/DiagnosticSemaKinds.td | 5 | ||||
| -rw-r--r-- | clang/include/clang/Sema/Sema.h | 9 | ||||
| -rw-r--r-- | clang/lib/AST/ExprConstant.cpp | 30 | ||||
| -rw-r--r-- | clang/lib/Sema/SemaDeclCXX.cpp | 341 | ||||
| -rw-r--r-- | clang/lib/Sema/SemaExpr.cpp | 9 | ||||
| -rw-r--r-- | clang/lib/Sema/SemaOverload.cpp | 126 | ||||
| -rw-r--r-- | clang/lib/Sema/SemaTemplateInstantiate.cpp | 20 | ||||
| -rw-r--r-- | clang/test/CXX/class/class.compare/class.compare.default/p2.cpp | 2 | ||||
| -rw-r--r-- | clang/test/CXX/class/class.compare/class.compare.default/p5.cpp | 45 | ||||
| -rw-r--r-- | clang/test/CXX/class/class.compare/class.eq/p2.cpp | 18 | ||||
| -rw-r--r-- | clang/test/CXX/class/class.compare/class.eq/p3.cpp | 11 | ||||
| -rw-r--r-- | clang/test/CXX/class/class.compare/class.rel/p2.cpp | 23 | ||||
| -rw-r--r-- | clang/test/CXX/class/class.compare/class.spaceship/p1.cpp | 108 | ||||
| -rw-r--r-- | clang/test/CXX/class/class.compare/class.spaceship/p3.cpp | 35 |
16 files changed, 743 insertions, 59 deletions
diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h index f4913540bab..2d1a30657cb 100644 --- a/clang/include/clang/AST/Decl.h +++ b/clang/include/clang/AST/Decl.h @@ -2115,6 +2115,16 @@ public: 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. /// If a more specific implicit return value is required, front-ends /// should synthesize the appropriate return statements. diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h index 0f2018fb9e8..0043ce1a92d 100644 --- a/clang/include/clang/AST/DeclCXX.h +++ b/clang/include/clang/AST/DeclCXX.h @@ -1995,16 +1995,6 @@ public: 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); using method_iterator = const CXXMethodDecl *const *; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index dcfc8fb5de9..939287014d9 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -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_implicit_param_decl : Note<"%0 is an implicit parameter">; 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">; def err_missing_default_ctor : Error< "%select{constructor for %1 must explicitly initialize the|" diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index ed1f1370b33..2b0db07e6ba 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -3319,7 +3319,12 @@ public: const UnresolvedSetImpl &Fns, Expr *LHS, Expr *RHS, 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, SourceLocation RLoc, @@ -6516,6 +6521,8 @@ public: bool CheckExplicitlyDefaultedComparison(FunctionDecl *MD, DefaultedComparisonKind DCK); + void DefineDefaultedComparison(SourceLocation Loc, FunctionDecl *FD, + DefaultedComparisonKind DCK); //===--------------------------------------------------------------------===// // C++ Derived Classes diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 3151ec044cb..5aa151984e5 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -6830,6 +6830,36 @@ public: 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) { APValue Result; if (!handleCallExpr(E, Result, nullptr)) diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index ba516b66608..c8b95983f03 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -7085,7 +7085,8 @@ namespace { /// /// This is accomplished by performing two visitation steps over the eventual /// body of the function. -template<typename Derived, typename Result, typename Subobject> +template<typename Derived, typename ResultList, typename Result, + typename Subobject> class DefaultedComparisonVisitor { public: using DefaultedComparisonKind = Sema::DefaultedComparisonKind; @@ -7094,45 +7095,54 @@ public: DefaultedComparisonKind DCK) : S(S), RD(RD), FD(FD), DCK(DCK) {} - Result visit() { + ResultList visit() { // The type of an lvalue naming a parameter of this function. QualType ParamLvalType = FD->getParamDecl(0)->getType().getNonReferenceType(); + ResultList Results; + switch (DCK) { case DefaultedComparisonKind::None: llvm_unreachable("not a defaulted comparison"); case DefaultedComparisonKind::Equal: case DefaultedComparisonKind::ThreeWay: - return getDerived().visitSubobjects(RD, ParamLvalType.getQualifiers()); + getDerived().visitSubobjects(Results, RD, ParamLvalType.getQualifiers()); + return Results; case DefaultedComparisonKind::NotEqual: case DefaultedComparisonKind::Relational: - return getDerived().visitExpandedSubobject( - ParamLvalType, getDerived().getCompleteObject()); + Results.add(getDerived().visitExpandedSubobject( + ParamLvalType, getDerived().getCompleteObject())); + return Results; } } protected: Derived &getDerived() { return static_cast<Derived&>(*this); } - Result visitSubobjects(CXXRecordDecl *Record, Qualifiers Quals) { - Result R; - // C++ [class.compare.default]p5: - // The direct base class subobjects of C [...] + /// Visit the expanded list of subobjects of the given type, as specified in + /// C++2a [class.compare.default]. + /// + /// \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()) - if (R.add(getDerived().visitSubobject( + if (Results.add(getDerived().visitSubobject( S.Context.getQualifiedType(Base.getType(), Quals), getDerived().getBase(&Base)))) - return R; - // followed by the non-static data members of C [...] + return true; + + // followed by the non-static data members of C for (FieldDecl *Field : Record->fields()) { // Recursively expand anonymous structs. if (Field->isAnonymousStructOrUnion()) { - if (R.add( - visitSubobjects(Field->getType()->getAsCXXRecordDecl(), Quals))) - return R; + if (visitSubobjects(Results, Field->getType()->getAsCXXRecordDecl(), + Quals)) + return true; continue; } @@ -7143,12 +7153,13 @@ protected: QualType FieldType = S.Context.getQualifiedType(Field->getType(), FieldQuals); - if (R.add(getDerived().visitSubobject(FieldType, - getDerived().getField(Field)))) - return R; + if (Results.add(getDerived().visitSubobject( + FieldType, getDerived().getField(Field)))) + return true; } + // form a list of subobjects. - return R; + return false; } Result visitSubobject(QualType Type, Subobject Subobj) { @@ -7200,6 +7211,7 @@ struct DefaultedComparisonSubobject { class DefaultedComparisonAnalyzer : public DefaultedComparisonVisitor<DefaultedComparisonAnalyzer, DefaultedComparisonInfo, + DefaultedComparisonInfo, DefaultedComparisonSubobject> { public: enum DiagnosticKind { NoDiagnostics, ExplainDeleted, ExplainConstexpr }; @@ -7407,6 +7419,260 @@ private: 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, @@ -7540,6 +7806,43 @@ bool Sema::CheckExplicitlyDefaultedComparison(FunctionDecl *FD, 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() { decltype(DelayedOverridingExceptionSpecChecks) Overriding; decltype(DelayedEquivalentExceptionSpecChecks) Equivalent; diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 5eeeba3c2d1..b97352e27e1 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -15416,9 +15416,8 @@ static OdrUseContext isOdrUseContext(Sema &SemaRef) { } static bool isImplicitlyDefinableConstexprFunction(FunctionDecl *Func) { - CXXMethodDecl *MD = dyn_cast<CXXMethodDecl>(Func); return Func->isConstexpr() && - (Func->isImplicitlyInstantiable() || (MD && !MD->isUserProvided())); + (Func->isImplicitlyInstantiable() || !Func->isUserProvided()); } /// 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()); } + 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 // class templates. if (Func->isImplicitlyInstantiable()) { diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp index 27e1101b482..344e54b7f3f 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -12835,11 +12835,19 @@ void Sema::LookupOverloadedBinOp(OverloadCandidateSet &CandidateSet, /// /// \param LHS Left-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, BinaryOperatorKind Opc, const UnresolvedSetImpl &Fns, Expr *LHS, Expr *RHS, bool PerformADL, - bool AllowRewrittenCandidates) { + bool AllowRewrittenCandidates, + FunctionDecl *DefaultedFn) { Expr *Args[2] = { LHS, RHS }; LHS=RHS=nullptr; // Please use only Args instead of LHS/RHS couple @@ -12906,6 +12914,8 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc, OverloadCandidateSet CandidateSet( OpLoc, OverloadCandidateSet::CSK_Operator, OverloadCandidateSet::OperatorRewriteInfo(Op, AllowRewrittenCandidates)); + if (DefaultedFn) + CandidateSet.exclude(DefaultedFn); LookupOverloadedBinOp(CandidateSet, Op, Fns, Args, PerformADL); bool HadMultipleCandidates = (CandidateSet.size() > 1); @@ -13113,6 +13123,15 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc, if (Opc == BO_Comma) 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 // operator do not fall through to handling in built-in, but report that // no overloaded assignment operator found @@ -13194,6 +13213,111 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc, 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 Sema::CreateOverloadedArraySubscriptExpr(SourceLocation LLoc, SourceLocation RLoc, diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp index 2496c919311..4d54ec17b99 100644 --- a/clang/lib/Sema/SemaTemplateInstantiate.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp @@ -672,13 +672,23 @@ void Sema::PrintInstantiationStack() { break; case CodeSynthesisContext::DefiningSynthesizedFunction: { - // FIXME: For synthesized members other than special members, produce a note. - auto *MD = dyn_cast<CXXMethodDecl>(Active->Entity); - auto CSM = MD ? getSpecialMember(MD) : CXXInvalid; - if (CSM != CXXInvalid) { + // FIXME: For synthesized functions that are not defaulted, + // produce a note. + auto *FD = dyn_cast<FunctionDecl>(Active->Entity); + DefaultedFunctionKind DFK = + FD ? getDefaultedFunctionKind(FD) : DefaultedFunctionKind(); + if (DFK.isSpecialMember()) { + auto *MD = cast<CXXMethodDecl>(FD); Diags.Report(Active->PointOfInstantiation, 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; } diff --git a/clang/test/CXX/class/class.compare/class.compare.default/p2.cpp b/clang/test/CXX/class/class.compare/class.compare.default/p2.cpp index bbc9060df30..cdffd445f7c 100644 --- a/clang/test/CXX/class/class.compare/class.compare.default/p2.cpp +++ b/clang/test/CXX/class/class.compare/class.compare.default/p2.cpp @@ -14,7 +14,7 @@ struct A2 { bool operator==(const A2&) const; 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; diff --git a/clang/test/CXX/class/class.compare/class.compare.default/p5.cpp b/clang/test/CXX/class/class.compare/class.compare.default/p5.cpp new file mode 100644 index 00000000000..f863ed09bc6 --- /dev/null +++ b/clang/test/CXX/class/class.compare/class.compare.default/p5.cpp @@ -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)); diff --git a/clang/test/CXX/class/class.compare/class.eq/p2.cpp b/clang/test/CXX/class/class.compare/class.eq/p2.cpp index 1a515dee490..d53d071f36f 100644 --- a/clang/test/CXX/class/class.compare/class.eq/p2.cpp +++ b/clang/test/CXX/class/class.compare/class.eq/p2.cpp @@ -8,13 +8,16 @@ struct D { // expected-note@+1 {{candidate function (with reversed parameter order) not viable: 1st argument ('const}} 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 G { bool operator==(G) const = delete; }; // expected-note {{deleted here}} template<typename T> struct 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'}} // 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(Mutable() == Mutable()); - // FIXME: Not deleted, but once we start synthesizing comparison function definitions, we should reject this. - void(X<E>() == X<E>()); - // FIXME: Similarly, not deleted under P2002R0, but synthesized body is ill-formed. - void(X<F>() == X<F>()); + // FIXME: We would benefit from a note identifying the member of 'X' we were comparing here and below. + // expected-error@#x {{call to deleted constructor of 'E'}} + void(X<E>() == X<E>()); // expected-note {{in defaulted equality comparison operator for 'X<E>' first required here}} + + // 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}} } diff --git a/clang/test/CXX/class/class.compare/class.eq/p3.cpp b/clang/test/CXX/class/class.compare/class.eq/p3.cpp new file mode 100644 index 00000000000..f58b9daa523 --- /dev/null +++ b/clang/test/CXX/class/class.compare/class.eq/p3.cpp @@ -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}} diff --git a/clang/test/CXX/class/class.compare/class.rel/p2.cpp b/clang/test/CXX/class/class.compare/class.rel/p2.cpp index d81c9634131..2abe7fb8d79 100644 --- a/clang/test/CXX/class/class.compare/class.rel/p2.cpp +++ b/clang/test/CXX/class/class.compare/class.rel/p2.cpp @@ -2,16 +2,23 @@ namespace Rel { 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; }; - bool a1 = A() < A(); - bool a2 = A() <= A(); - bool a3 = A() > A(); - bool a4 = A() >= A(); + static_assert(A{0} < A{1}); + static_assert(A{1} < A{1}); // expected-error {{failed}} + static_assert(A{0} <= A{1}); + 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 { 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. namespace NotEqual { 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; }; - bool a = A() != A(); + static_assert(A{1} != A{2}); + static_assert(A{1} != A{1}); // expected-error {{failed}} struct B { bool operator==(B) const = delete; // expected-note {{deleted here}} expected-note-re 2{{candidate {{.*}} deleted}} diff --git a/clang/test/CXX/class/class.compare/class.spaceship/p1.cpp b/clang/test/CXX/class/class.compare/class.spaceship/p1.cpp index 928fe203615..fafa99ff5cf 100644 --- a/clang/test/CXX/class/class.compare/class.spaceship/p1.cpp +++ b/clang/test/CXX/class/class.compare/class.spaceship/p1.cpp @@ -1,13 +1,39 @@ -// RUN: %clang_cc1 -std=c++2a -verify %s +// RUN: %clang_cc1 -std=c++2a -verify %s -fcxx-exceptions namespace std { - struct strong_ordering { + struct strong_ordering { // expected-note 3{{candidate}} 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 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 { @@ -59,7 +85,7 @@ namespace Deletedness { // expected-note@#base {{deleted comparison function for base class 'E'}} // expected-note@#base {{implied comparison for base class 'F' is ambiguous}} 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(...); @@ -72,10 +98,80 @@ namespace Deletedness { Cmp<D2>() <=> Cmp<D2>(), // expected-error {{deleted}} Cmp<E>() <=> Cmp<E>(), // expected-error {{deleted}} Cmp<F>() <=> Cmp<F>(), // expected-error {{deleted}} - Cmp<G1>() <=> Cmp<G1>(), // FIXME: ok but synthesized body is ill-formed - Cmp<G2>() <=> Cmp<G2>(), // FIXME: ok but synthesized body is ill-formed - Cmp<H>() <=> Cmp<H>(), // FIXME: ok but synthesized body is ill-formed + // FIXME: The following three errors are not very good. + // expected-error@#cmp {{value of type 'void' is not contextually convertible to 'bool'}} + 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 ); } } + +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}} +} diff --git a/clang/test/CXX/class/class.compare/class.spaceship/p3.cpp b/clang/test/CXX/class/class.compare/class.spaceship/p3.cpp new file mode 100644 index 00000000000..e4be892cd7c --- /dev/null +++ b/clang/test/CXX/class/class.compare/class.spaceship/p3.cpp @@ -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}} |

