Index: include/clang/AST/ComparisonCategories.h =================================================================== --- include/clang/AST/ComparisonCategories.h +++ include/clang/AST/ComparisonCategories.h @@ -202,6 +202,23 @@ static StringRef getCategoryString(ComparisonCategoryType Kind); static StringRef getResultString(ComparisonCategoryResult Kind); + /// \brief Return the comparison category type which would be returned + /// for a builtin comparison operator taking the specified type, or None if no + /// such type exists. + /// + /// \param Ty The composite comparison type + /// \param IsMixedNullCompare True if exactly one of the operands is a null + /// pointer constant. + static Optional + computeComparisonTypeForBuiltin(QualType Ty, bool IsMixedNullCompare = false); + + /// \brief Return the comparison category information for the + /// "common comparison type" for a specified list of types. If there is no + /// such common comparison type, or if any of the specified types are not + /// comparison category types, null is returned. + const ComparisonCategoryInfo * + computeCommonComparisonType(ArrayRef Types) const; + /// \brief Return the list of results which are valid for the specified /// comparison category type. static std::vector Index: include/clang/AST/Decl.h =================================================================== --- include/clang/AST/Decl.h +++ include/clang/AST/Decl.h @@ -2162,6 +2162,9 @@ /// true through IsAligned. bool isReplaceableGlobalAllocationFunction(bool *IsAligned = nullptr) const; + /// \brief Determine whether this is a C++2a default comparison operator. + bool isDefaultComparisonOperator(bool IsExplicitlyDefault = false) const; + /// \brief Determine whether this is a destroying operator delete. bool isDestroyingOperatorDelete() const; Index: include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- include/clang/Basic/DiagnosticSemaKinds.td +++ include/clang/Basic/DiagnosticSemaKinds.td @@ -1377,6 +1377,9 @@ def note_pure_qualified_call_kext : Note< "qualified call to %0::%1 is treated as a virtual call to %1 due to -fapple-kext">; +def err_defaulted_decl_not_first : Error< + "defaulted definition must be first declaration">; + def err_deleted_decl_not_first : Error< "deleted definition must be first declaration">; @@ -1628,7 +1631,7 @@ def note_implicit_param_decl : Note<"%0 is an implicit parameter">; def note_member_synthesized_at : Note< "in implicit %select{default constructor|copy constructor|move constructor|" - "copy assignment operator|move assignment operator|destructor}0 for %1 " + "copy assignment operator|move assignment operator|destructor|comparison operator}0 for %1 " "first required here">; def err_missing_default_ctor : Error< "%select{constructor for %1 must explicitly initialize the|" @@ -1660,8 +1663,8 @@ "implicit default constructor suppressed by user-declared constructor">; def note_nontrivial_no_copy : Note< "because no %select{<>|constructor|constructor|assignment operator|" - "assignment operator|<>}2 can be used to " - "%select{<>|copy|move|copy|move|<>}2 " + "assignment operator|<>|<>}2 can be used to " + "%select{<>|copy|move|copy|move|<>|<>}2 " "%select{base class|field|an object}0 of type %3">; def note_nontrivial_user_provided : Note< "because %select{base class of |field of |}0type %1 has a user-provided " @@ -3878,9 +3881,9 @@ "overload resolution selected %select{unavailable|deleted}0 operator '%1'%2">; def err_ovl_deleted_special_oper : Error< "object of type %0 cannot be %select{constructed|copied|moved|assigned|" - "assigned|destroyed}1 because its %select{default constructor|" + "assigned|destroyed|compared}1 because its %select{default constructor|" "copy constructor|move constructor|copy assignment operator|" - "move assignment operator|destructor}1 is implicitly deleted">; + "move assignment operator|destructor|comparison operator}1 is implicitly deleted">; def err_ovl_no_viable_subscript : Error<"no viable overloaded operator[] for type %0">; def err_ovl_no_oper : @@ -4662,11 +4665,11 @@ def err_definition_of_implicitly_declared_member : Error< "definition of implicitly declared %select{default constructor|copy " "constructor|move constructor|copy assignment operator|move assignment " - "operator|destructor|function}1">; + "operator|destructor|comparison operator|function}1">; def err_definition_of_explicitly_defaulted_member : Error< "definition of explicitly defaulted %select{default constructor|copy " "constructor|move constructor|copy assignment operator|move assignment " - "operator|destructor|function}0">; + "operator|destructor|comparison operator|function}0">; def err_redefinition_extern_inline : Error< "redefinition of a 'extern inline' function %0 is not supported in " "%select{C99 mode|C++}1">; @@ -4685,12 +4688,12 @@ def note_deleted_special_member_class_subobject : Note< "%select{default constructor of|copy constructor of|move constructor of|" "copy assignment operator of|move assignment operator of|destructor of|" - "constructor inherited by}0 " + "comparison operator of|constructor inherited by}0 " "%1 is implicitly deleted because " "%select{base class %3|%select{||||variant }4field %3}2 has " "%select{no|a deleted|multiple|an inaccessible|a non-trivial}4 " "%select{%select{default constructor|copy constructor|move constructor|copy " - "assignment operator|move assignment operator|destructor|" + "assignment operator|move assignment operator|destructor|comparison operator|" "%select{default|corresponding|default|default|default}4 constructor}0|" "destructor}5" "%select{||s||}4">; @@ -6453,6 +6456,8 @@ "member %0 declared here">; def note_member_first_declared_here : Note< "member %0 first declared here">; +def note_first_declared_here : Note< + "%0 first declared here">; def err_decrement_bool : Error<"cannot decrement expression of type bool">; def warn_increment_bool : Warning< "incrementing expression of type bool is deprecated and " @@ -7743,13 +7748,16 @@ // C++11 defaulted functions def err_defaulted_special_member_params : Error< - "an explicitly-defaulted %select{|copy |move }0constructor cannot " + "an explicitly-defaulted %select{default constructor|copy constructor|" + "move constructor|<>|<>|<>|comparison operator}0 cannot " "have default arguments">; def err_defaulted_special_member_variadic : Error< - "an explicitly-defaulted %select{|copy |move }0constructor cannot " + "an explicitly-defaulted %select{default constructor|copy constructor|" + "move constructor|<>|<>|<>|comparison operator}0 cannot " "be variadic">; def err_defaulted_special_member_return_type : Error< - "explicitly-defaulted %select{copy|move}0 assignment operator must " + "explicitly-defaulted %select{<>|<>|<>|copy assignment|" + "move assignment|<>|comparison}0 operator must " "return %1">; def err_defaulted_special_member_quals : Error< "an explicitly-defaulted %select{copy|move}0 assignment operator may not " @@ -7757,7 +7765,7 @@ def err_defaulted_special_member_volatile_param : Error< "the parameter for an explicitly-defaulted %select{<>|" "copy constructor|move constructor|copy assignment operator|" - "move assignment operator|<>}0 may not be volatile">; + "move assignment operator|<>|comparison operator}0 may not be volatile">; def err_defaulted_special_member_move_const_param : Error< "the parameter for an explicitly-defaulted move " "%select{constructor|assignment operator}0 may not be const">; @@ -7771,16 +7779,17 @@ def err_incorrect_defaulted_exception_spec : Error< "exception specification of explicitly defaulted %select{default constructor|" "copy constructor|move constructor|copy assignment operator|move assignment " - "operator|destructor}0 does not match the " + "operator|destructor|comparison operator}0 does not match the " "calculated one">; def err_incorrect_defaulted_constexpr : Error< "defaulted definition of %select{default constructor|copy constructor|" - "move constructor|copy assignment operator|move assignment operator}0 " + "move constructor|copy assignment operator|move assignment operator|" + "destructor|comparison operator}0 " "is not constexpr">; def err_out_of_line_default_deletes : Error< "defaulting this %select{default constructor|copy constructor|move " - "constructor|copy assignment operator|move assignment operator|destructor}0 " - "would delete it after its first declaration">; + "constructor|copy assignment operator|move assignment operator|destructor|" + "comparison operator}0 would delete it after its first declaration">; def warn_vbase_moved_multiple_times : Warning< "defaulted move assignment operator of %0 will move assign virtual base " "class %1 multiple times">, InGroup>; @@ -7793,6 +7802,17 @@ "specification redeclared with an %select{implicit|explicit}0 exception " "specification">, InGroup>; +// C++2a defaulted comparison operators +def err_defaulted_comparison_operator_template : Error< + "an explicitly-defaulted comparison operator cannot be a template">; +def err_defaulted_comparison_operator_decl_scope : Error< + "an explicitly-defaulted comparison operator not declared at class scope">; +def err_defaulted_comparison_operator_param_type : Error< + "the parameter for this explicitly-defaulted comparison operator must have " + "type %0, but parameter has type %1">; +def err_defaulted_comparison_operator_member_non_const : Error< + "explicitly-defaulted comparison operator is not const qualified">; + def warn_ptr_arith_precedes_bounds : Warning< "the pointer decremented by %0 refers before the beginning of the array">, InGroup, DefaultIgnore; Index: include/clang/Sema/Sema.h =================================================================== --- include/clang/Sema/Sema.h +++ include/clang/Sema/Sema.h @@ -609,14 +609,15 @@ SmallVector, 2> DelayedExceptionSpecChecks; - /// \brief All the members seen during a class definition which were both - /// explicitly defaulted and had explicitly-specified exception - /// specifications, along with the function type containing their - /// user-specified exception specification. Those exception specifications - /// were overridden with the default specifications, but we still need to - /// check whether they are compatible with the default specification, and - /// we can't do that until the nesting set of class definitions is complete. - SmallVector, 2> + /// \brief All the members or comparison friend functions seen during a + /// class definition which were both explicitly defaulted and had + /// explicitly-specified exception specifications, along with the function + /// type containing their user-specified exception specification. Those + /// exception specifications were overridden with the default specifications, + /// but we still need to check whether they are compatible with the default + /// specification, and we can't do that until the nesting set of class + /// definitions is complete. + SmallVector, 2> DelayedDefaultedMemberExceptionSpecs; typedef llvm::MapVector Pair; - - public: - SpecialMemberOverloadResult() : Pair() {} - SpecialMemberOverloadResult(CXXMethodDecl *MD) - : Pair(MD, MD->isDeleted() ? NoMemberOrDeleted : Success) {} + union { + struct { + FunctionDecl *Function; + } FunctionInfo; + struct { + void *ArgType; + DeclAccessPair ConversionDecl; + } BuiltinInfo; + }; - CXXMethodDecl *getMethod() const { return Pair.getPointer(); } - void setMethod(CXXMethodDecl *MD) { Pair.setPointer(MD); } + unsigned char LookupKind : 2; + unsigned char IsBuiltin : 1; - Kind getKind() const { return static_cast(Pair.getInt()); } - void setKind(Kind K) { Pair.setInt(K); } + public: + SpecialMemberOverloadResult() + : FunctionInfo{nullptr}, LookupKind(0), IsBuiltin(false) {} + SpecialMemberOverloadResult(FunctionDecl *FD) + : FunctionInfo{FD}, + LookupKind(FD->isDeleted() ? NoMemberOrDeleted : Success), + IsBuiltin(false) {} + + FunctionDecl *getFunction() const { + if (IsBuiltin) + return nullptr; + return FunctionInfo.Function; + } + void setFunction(FunctionDecl *FD) { + FunctionInfo.Function = FD; + IsBuiltin = false; + } + + DeclAccessPair getBuiltinConversionDecl() const { + assert(IsBuiltin); + return BuiltinInfo.ConversionDecl; + } + QualType getBuiltinArgType() const { + if (!IsBuiltin) + return QualType(); + return QualType::getFromOpaquePtr(BuiltinInfo.ArgType); + } + void setBuiltinInfo(QualType Ty, DeclAccessPair ConvDecl) { + BuiltinInfo.ArgType = Ty.getAsOpaquePtr(); + BuiltinInfo.ConversionDecl = ConvDecl; + IsBuiltin = true; + } + bool hasBuiltin() const { return IsBuiltin; } + bool hasFunction() const { return !IsBuiltin && FunctionInfo.Function; } + + Kind getKind() const { return static_cast(LookupKind); } + void setKind(Kind K) { LookupKind = K; } }; class SpecialMemberOverloadResultEntry @@ -1125,7 +1164,8 @@ /// of -Wselector. llvm::MapVector ReferencedSelectors; - /// Kinds of C++ special members. + /// Kind of C++ members which can be defaulted. Including special members and + /// explicitly defaulted comparison operators. enum CXXSpecialMember { CXXDefaultConstructor, CXXCopyConstructor, @@ -1133,6 +1173,7 @@ CXXCopyAssignment, CXXMoveAssignment, CXXDestructor, + CXXComparisonOperator, CXXInvalid }; @@ -2233,10 +2274,11 @@ TAH_ConsiderTrivialABI }; - bool SpecialMemberIsTrivial(CXXMethodDecl *MD, CXXSpecialMember CSM, + bool SpecialMemberIsTrivial(FunctionDecl *FD, CXXSpecialMember CSM, TrivialABIHandling TAH = TAH_IgnoreTrivialABI, bool Diagnose = false); - CXXSpecialMember getSpecialMember(const CXXMethodDecl *MD); + CXXSpecialMember getSpecialMember(const FunctionDecl *FD, + bool IsExplicitlyDefault = false); void ActOnLastBitfield(SourceLocation DeclStart, SmallVectorImpl &AllIvarDecls); Decl *ActOnIvar(Scope *S, SourceLocation DeclStart, @@ -3193,6 +3235,14 @@ bool RValueThis, unsigned ThisQuals); CXXDestructorDecl *LookupDestructor(CXXRecordDecl *Class); + /// \brief Lookup the three-way comparison operator for the specified ArgTy. + /// The comparison is homogenious as required for explicitly-defaulted + /// comparison operators. ArgTy is transformed into a const lvalue before + /// lookup is performed. + SpecialMemberOverloadResult LookupThreeWayComparison(CXXRecordDecl *RD, + QualType ArgTy, + SourceLocation Loc); + bool checkLiteralOperatorId(const CXXScopeSpec &SS, const UnqualifiedId &Id); LiteralOperatorLookupResult LookupLiteralOperator(Scope *S, LookupResult &R, ArrayRef ArgTys, @@ -3200,6 +3250,7 @@ bool AllowTemplate, bool AllowStringTemplate, bool DiagnoseMissing); + bool isKnownName(StringRef name); void ArgumentDependentLookup(DeclarationName Name, SourceLocation Loc, @@ -4573,6 +4624,15 @@ QualType CheckComparisonCategoryType(ComparisonCategoryType Kind, SourceLocation Loc); + /// \brief Compute the 'common comparison type' (C++2a [class.spaceship]) for + /// the specified list of types. If any of the required comparison category + /// types or declarations are not found, a diagnostic is emitted. + /// + /// \return The common comparison type, if no error occurs. Otherwise a null + /// type. + QualType ComputeCommonComparisonType(ArrayRef Types, + SourceLocation Loc); + /// \brief Tests whether Ty is an instance of std::initializer_list and, if /// it is and Element is not NULL, assigns the element type to Element. bool isStdInitializerList(QualType Ty, QualType *Element); @@ -4756,7 +4816,7 @@ const QualType *data() const { return Exceptions.data(); } /// \brief Integrate another called method into the collected data. - void CalledDecl(SourceLocation CallLoc, const CXXMethodDecl *Method); + void CalledDecl(SourceLocation CallLoc, const FunctionDecl *FD); /// \brief Integrate an invoked expression into the collected data. void CalledExpr(Expr *E); @@ -4821,7 +4881,7 @@ /// \brief Evaluate the implicit exception specification for a defaulted /// special member function. - void EvaluateImplicitExceptionSpec(SourceLocation Loc, CXXMethodDecl *MD); + void EvaluateImplicitExceptionSpec(SourceLocation Loc, FunctionDecl *FD); /// Check the given noexcept-specifier, convert its expression, and compute /// the appropriate ExceptionSpecificationType. @@ -4854,9 +4914,9 @@ class InheritedConstructorInfo; - /// \brief Determine if a special member function should have a deleted - /// definition when it is defaulted. - bool ShouldDeleteSpecialMember(CXXMethodDecl *MD, CXXSpecialMember CSM, + /// \brief Determine if a special member function or defaulted comparison + /// operator, should have a deleted definition when it is defaulted. + bool ShouldDeleteSpecialMember(FunctionDecl *FD, CXXSpecialMember CSM, InheritedConstructorInfo *ICI = nullptr, bool Diagnose = false); @@ -4982,6 +5042,17 @@ /// it simply returns the passed in expression. ExprResult MaybeBindToTemporary(Expr *E); + /// \brief Check whether the specified explicitly-defaulted comparison + /// declaration is ollowed. + /// + /// \return true if an error occurred + bool checkDefaultedComparisonOperatorDecl(SourceLocation DefaultLoc, + Decl *Dcl); + + /// \brief Defines a explicitly-defaulted comparison operator. + void DefineDefaultedComparisonOperator(SourceLocation CurrentLocation, + FunctionDecl *FD); + bool CompleteConstructorCall(CXXConstructorDecl *Constructor, MultiExprArg ArgsPtr, SourceLocation Loc, @@ -5911,8 +5982,8 @@ StorageClass &SC); void CheckDeductionGuideTemplate(FunctionTemplateDecl *TD); - void CheckExplicitlyDefaultedSpecialMember(CXXMethodDecl *MD); - void CheckExplicitlyDefaultedMemberExceptionSpec(CXXMethodDecl *MD, + void CheckExplicitlyDefaultedMember(FunctionDecl *FD); + void CheckExplicitlyDefaultedMemberExceptionSpec(FunctionDecl *FD, const FunctionProtoType *T); void CheckDelayedMemberExceptionSpecs(); @@ -10372,11 +10443,13 @@ const FunctionProtoType *Proto, SourceLocation Loc); +public: void checkCall(NamedDecl *FDecl, const FunctionProtoType *Proto, const Expr *ThisArg, ArrayRef Args, bool IsMemberFunction, SourceLocation Loc, SourceRange Range, VariadicCallType CallType); +private: bool CheckObjCString(Expr *Arg); ExprResult CheckOSLogFormatStringArg(Expr *Arg); Index: lib/AST/ComparisonCategories.cpp =================================================================== --- lib/AST/ComparisonCategories.cpp +++ lib/AST/ComparisonCategories.cpp @@ -218,3 +218,83 @@ Values.push_back(CCR::Unordered); return Values; } + +Optional +ComparisonCategories::computeComparisonTypeForBuiltin(QualType Ty, + bool IsMixedNullCompare) { + using CCT = ComparisonCategoryType; + if (const ComplexType *CT = Ty->getAs()) { + if (CT->getElementType()->hasFloatingRepresentation()) + return CCT::WeakEquality; + return CCT::StrongEquality; + } + if (Ty->isIntegralOrEnumerationType()) + return CCT::StrongOrdering; + if (Ty->hasFloatingRepresentation()) + return CCT::PartialOrdering; + // C++2a [expr.spaceship]p7: If the composite pointer type is a function + // pointer type, a pointer-to-member type, or std::nullptr_t, the + // result is of type std::strong_equality + if (Ty->isFunctionPointerType() || Ty->isMemberPointerType() || + Ty->isNullPtrType()) + // FIXME: consider making the function pointer case produce + // strong_ordering not strong_equality, per P0946R0-Jax18 discussion + // and direction polls + return CCT::StrongEquality; + // C++2a [expr.spaceship]p8: If the composite pointer type is an object + // pointer type, p <=> q is of type std::strong_ordering. + if (Ty->isPointerType()) { + // P0946R0: Comparisons between a null pointer constant and an object + // pointer result in std::strong_equality + if (IsMixedNullCompare) + return ComparisonCategoryType::StrongEquality; + return CCT::StrongOrdering; + } + return None; +} + +/// C++2a [class.spaceship]p4 - compute the common category type. +const ComparisonCategoryInfo *ComparisonCategories::computeCommonComparisonType( + ArrayRef Types) const { + using CCT = ComparisonCategoryType; + std::array(CCT::Last) + 1> Seen = {}; + auto Count = [&](CCT T) { return Seen[static_cast(T)]; }; + + // Count the number of times each comparison category type occurs in the + // specified type list. If any type is not a comparison category, return + // nullptr. + for (auto Ty : Types) { + const ComparisonCategoryInfo *Info = lookupInfoForType(Ty); + // --- If any T is not a comparison category type, U is void. + if (!Info) + return nullptr; + Seen[static_cast(Info->Kind)]++; + } + // --- Otherwise, if at least one Ti is std::weak_equality, or at least one + // Ti is std::strong_equality and at least one Tj is + // std::partial_ordering or std::weak_ordering, U is + // std::weak_equality. + if (Count(CCT::WeakEquality) || + (Count(CCT::StrongEquality) && + (Count(CCT::PartialOrdering) || Count(CCT::WeakOrdering)))) + return lookupInfo(CCT::WeakEquality); + + // --- Otherwise, if at least one Ti is std::strong_equality, U is + // std::strong_equality + if (Count(CCT::StrongEquality)) + return lookupInfo(CCT::StrongEquality); + + // --- Otherwise, if at least one Ti is std::partial_ordering, U is + // std::partial_ordering. + if (Count(CCT::PartialOrdering)) + return lookupInfo(CCT::PartialOrdering); + + // --- Otherwise, if at least one Ti is std::weak_ordering, U is + // std::weak_ordering. + if (Count(CCT::WeakOrdering)) + return lookupInfo(CCT::WeakOrdering); + + // FIXME(EricWF): What if we don't find std::strong_ordering + // --- Otherwise, U is std::strong_ordering. + return lookupInfo(CCT::StrongOrdering); +} Index: lib/AST/Decl.cpp =================================================================== --- lib/AST/Decl.cpp +++ lib/AST/Decl.cpp @@ -2809,6 +2809,21 @@ RD->getIdentifier()->isStr("destroying_delete_t"); } +bool FunctionDecl::isDefaultComparisonOperator(bool IsExplicitlyDefault) const { + // C++2a [class.campare.default] A defaulted comparison operator function + // ([expr.spaceship], [expr.rel], [expr.eq]) for some class C shall be a + // non-template function declared in the member-specification of C that is + // (1.1) a non-static member of C having one parameter of type const C&, or + if ((!isDefaulted() && !IsExplicitlyDefault) || !isOverloadedOperator()) + return false; + if (!isa(this) && !isa(getLexicalParent())) + return false; + auto Opc = BinaryOperator::getOverloadedOpcode(getOverloadedOperator()); + if (BinaryOperator::isComparisonOp(Opc)) + return true; + return false; +} + LanguageLinkage FunctionDecl::getLanguageLinkage() const { return getDeclLanguageLinkage(*this); } Index: lib/Sema/SemaCUDA.cpp =================================================================== --- lib/Sema/SemaCUDA.cpp +++ lib/Sema/SemaCUDA.cpp @@ -304,10 +304,11 @@ /* ConstThis */ false, /* VolatileThis */ false); - if (!SMOR.getMethod()) + if (!SMOR.getFunction()) continue; - CUDAFunctionTarget BaseMethodTarget = IdentifyCUDATarget(SMOR.getMethod()); + CUDAFunctionTarget BaseMethodTarget = + IdentifyCUDATarget(SMOR.getFunction()); if (!InferredTarget.hasValue()) { InferredTarget = BaseMethodTarget; } else { @@ -347,11 +348,11 @@ /* ConstThis */ false, /* VolatileThis */ false); - if (!SMOR.getMethod()) + if (!SMOR.getFunction()) continue; CUDAFunctionTarget FieldMethodTarget = - IdentifyCUDATarget(SMOR.getMethod()); + IdentifyCUDATarget(SMOR.getFunction()); if (!InferredTarget.hasValue()) { InferredTarget = FieldMethodTarget; } else { Index: lib/Sema/SemaDecl.cpp =================================================================== --- lib/Sema/SemaDecl.cpp +++ lib/Sema/SemaDecl.cpp @@ -2796,24 +2796,25 @@ } // end anonymous namespace /// getSpecialMember - get the special member enum for a method. -Sema::CXXSpecialMember Sema::getSpecialMember(const CXXMethodDecl *MD) { +Sema::CXXSpecialMember Sema::getSpecialMember(const FunctionDecl *FD, + bool IsExplicitlyDefault) { + if (FD->isDefaultComparisonOperator(IsExplicitlyDefault)) + return Sema::CXXComparisonOperator; + if (const auto *MD = dyn_cast(FD)) { if (const CXXConstructorDecl *Ctor = dyn_cast(MD)) { if (Ctor->isDefaultConstructor()) return Sema::CXXDefaultConstructor; - if (Ctor->isCopyConstructor()) return Sema::CXXCopyConstructor; - if (Ctor->isMoveConstructor()) return Sema::CXXMoveConstructor; - } else if (isa(MD)) { + } else if (isa(MD)) return Sema::CXXDestructor; - } else if (MD->isCopyAssignmentOperator()) { + else if (MD->isCopyAssignmentOperator()) return Sema::CXXCopyAssignment; - } else if (MD->isMoveAssignmentOperator()) { + else if (MD->isMoveAssignmentOperator()) return Sema::CXXMoveAssignment; } - return Sema::CXXInvalid; } @@ -3217,11 +3218,36 @@ } if (getLangOpts().CPlusPlus) { + // Diagnose definitions of explicitly defaulted special members. We do this + // before comparing the exception specifications, since doing so first + // creates confusing diagnostics. For example: + // struct T { T() = default; }; + // T::T() = default; + // If the exception spec is checked first the expression is diagnosed as + // {{'T' is missing exception specification 'noexcept'}} but we want to + // produce the diagnostic {{definition of explicitly defaulted default + // constructor}} + Sema::CXXSpecialMember SM = getSpecialMember(Old); + bool IsFriend = New->getFriendObjectKind(); + if (Old->isImplicit() && IsFriend) { + New->setImplicit(); + } else if (Old->isImplicit() && !IsFriend) { + Diag(New->getLocation(), + diag::err_definition_of_implicitly_declared_member) + << New << SM; + return true; + } else if (Old->getFirstDecl()->isExplicitlyDefaulted() && !IsFriend) { + Diag(New->getLocation(), + diag::err_definition_of_explicitly_defaulted_member) + << SM; + return true; + } + // C++1z [over.load]p2 // Certain function declarations cannot be overloaded: // -- Function declarations that differ only in the return type, // the exception specification, or both cannot be overloaded. - + // // Check the exception specifications match. This may recompute the type of // both Old and New if it resolved exception specifications, so grab the // types again after this. Because this updates the type, we do this before @@ -3338,20 +3364,6 @@ // // As an exception, it's okay to befriend such methods in order // to permit the implicit constructor/destructor/operator calls. - } else if (OldMethod->isImplicit()) { - if (isFriend) { - NewMethod->setImplicit(); - } else { - Diag(NewMethod->getLocation(), - diag::err_definition_of_implicitly_declared_member) - << New << getSpecialMember(OldMethod); - return true; - } - } else if (OldMethod->getFirstDecl()->isExplicitlyDefaulted() && !isFriend) { - Diag(NewMethod->getLocation(), - diag::err_definition_of_explicitly_defaulted_member) - << getSpecialMember(OldMethod); - return true; } } Index: lib/Sema/SemaDeclCXX.cpp =================================================================== --- lib/Sema/SemaDeclCXX.cpp +++ lib/Sema/SemaDeclCXX.cpp @@ -43,6 +43,7 @@ #include "llvm/ADT/StringExtras.h" #include #include +#include using namespace clang; @@ -149,15 +150,13 @@ } } -void -Sema::ImplicitExceptionSpecification::CalledDecl(SourceLocation CallLoc, - const CXXMethodDecl *Method) { +void Sema::ImplicitExceptionSpecification::CalledDecl(SourceLocation CallLoc, + const FunctionDecl *FD) { // If we have an MSAny spec already, don't bother. - if (!Method || ComputedEST == EST_MSAny) + if (!FD || ComputedEST == EST_MSAny) return; - const FunctionProtoType *Proto - = Method->getType()->getAs(); + const FunctionProtoType *Proto = FD->getType()->getAs(); Proto = Self->ResolveExceptionSpec(CallLoc, Proto); if (!Proto) return; @@ -168,7 +167,7 @@ if (ComputedEST == EST_None) return; - if (EST == EST_None && Method->hasAttr()) + if (EST == EST_None && FD->hasAttr()) EST = EST_BasicNoexcept; switch (EST) { @@ -5762,27 +5761,30 @@ } } -static void DefineImplicitSpecialMember(Sema &S, CXXMethodDecl *MD, +static void DefineImplicitSpecialMember(Sema &S, FunctionDecl *FD, SourceLocation DefaultLoc) { - switch (S.getSpecialMember(MD)) { + switch (S.getSpecialMember(FD)) { case Sema::CXXDefaultConstructor: S.DefineImplicitDefaultConstructor(DefaultLoc, - cast(MD)); + cast(FD)); break; case Sema::CXXCopyConstructor: - S.DefineImplicitCopyConstructor(DefaultLoc, cast(MD)); + S.DefineImplicitCopyConstructor(DefaultLoc, cast(FD)); break; case Sema::CXXCopyAssignment: - S.DefineImplicitCopyAssignment(DefaultLoc, MD); + S.DefineImplicitCopyAssignment(DefaultLoc, cast(FD)); break; case Sema::CXXDestructor: - S.DefineImplicitDestructor(DefaultLoc, cast(MD)); + S.DefineImplicitDestructor(DefaultLoc, cast(FD)); break; case Sema::CXXMoveConstructor: - S.DefineImplicitMoveConstructor(DefaultLoc, cast(MD)); + S.DefineImplicitMoveConstructor(DefaultLoc, cast(FD)); break; case Sema::CXXMoveAssignment: - S.DefineImplicitMoveAssignment(DefaultLoc, MD); + S.DefineImplicitMoveAssignment(DefaultLoc, cast(FD)); + break; + case Sema::CXXComparisonOperator: + S.DefineDefaultedComparisonOperator(DefaultLoc, FD); break; case Sema::CXXInvalid: llvm_unreachable("Invalid special member."); @@ -6003,7 +6005,7 @@ bool HasMethodWithOverrideControl = false, HasOverridingMethodWithoutOverrideControl = false; if (!Record->isDependentType()) { - for (auto *M : Record->methods()) { + for (CXXMethodDecl *M : Record->methods()) { // See if a method overloads virtual methods in a base // class without overriding any. if (!M->isStatic()) @@ -6014,7 +6016,7 @@ HasOverridingMethodWithoutOverrideControl = true; // Check whether the explicitly-defaulted special members are valid. if (!M->isInvalidDecl() && M->isExplicitlyDefaulted()) - CheckExplicitlyDefaultedSpecialMember(M); + CheckExplicitlyDefaultedMember(M); // For an explicitly defaulted or deleted special member, we defer // determining triviality until the class is complete. That time is now! @@ -6034,7 +6036,8 @@ // Set triviality for the purpose of calls if this is a user-provided // copy/move constructor or destructor. if ((CSM == CXXCopyConstructor || CSM == CXXMoveConstructor || - CSM == CXXDestructor) && M->isUserProvided()) { + CSM == CXXDestructor) && + M->isUserProvided()) { M->setTrivialForCall(HasTrivialABI); Record->setTrivialForCallFlags(M); } @@ -6095,15 +6098,23 @@ /// Look up the special member function that would be called by a special /// member function for a subobject of class type. /// +/// \param EvaluatingClass The class type containing the subobject. /// \param Class The class type of the subobject. /// \param CSM The kind of special member function. /// \param FieldQuals If the subobject is a field, its cv-qualifiers. /// \param ConstRHS True if this is a copy operation with a const object /// on its RHS, that is, if the argument to the outer special member /// function is 'const' and this is not a field marked 'mutable'. -static Sema::SpecialMemberOverloadResult lookupCallFromSpecialMember( - Sema &S, CXXRecordDecl *Class, Sema::CXXSpecialMember CSM, +static Sema::SpecialMemberOverloadResult +lookupCallFromSpecialMember(Sema &S, CXXRecordDecl *EvaluatingClass, + CXXRecordDecl *Class, Sema::CXXSpecialMember CSM, unsigned FieldQuals, bool ConstRHS) { + if (CSM == Sema::CXXComparisonOperator) { + QualType ArgTy(Class->getTypeForDecl(), FieldQuals); + // FIXME(EricWF): Get a source location + return S.LookupThreeWayComparison(EvaluatingClass, ArgTy, + Class->getLocation()); + } unsigned LHSQuals = 0; if (CSM == Sema::CXXCopyAssignment || CSM == Sema::CXXMoveAssignment) LHSQuals = FieldQuals; @@ -6206,9 +6217,9 @@ /// Is the special member function which would be selected to perform the /// specified operation on the specified class type a constexpr constructor? static bool -specialMemberIsConstexpr(Sema &S, CXXRecordDecl *ClassDecl, - Sema::CXXSpecialMember CSM, unsigned Quals, - bool ConstRHS, +specialMemberIsConstexpr(Sema &S, CXXRecordDecl *EvaluatingClass, + CXXRecordDecl *ClassDecl, Sema::CXXSpecialMember CSM, + unsigned Quals, bool ConstRHS, CXXConstructorDecl *InheritedCtor = nullptr, Sema::InheritedConstructorInfo *Inherited = nullptr) { // If we're inheriting a constructor, see if we need to call it for this base @@ -6224,13 +6235,130 @@ if (CSM == Sema::CXXDefaultConstructor) return ClassDecl->hasConstexprDefaultConstructor(); - Sema::SpecialMemberOverloadResult SMOR = - lookupCallFromSpecialMember(S, ClassDecl, CSM, Quals, ConstRHS); - if (!SMOR.getMethod()) + Sema::SpecialMemberOverloadResult SMOR = lookupCallFromSpecialMember( + S, EvaluatingClass, ClassDecl, CSM, Quals, ConstRHS); + if (CSM == Sema::CXXComparisonOperator && SMOR.hasBuiltin()) { + DeclAccessPair ConvDecl = SMOR.getBuiltinConversionDecl(); + if (auto *ConvFD = cast_or_null(ConvDecl.getDecl())) + return ConvFD->isConstexpr(); + return true; + } + if (!SMOR.getFunction()) // A constructor we wouldn't select can't be "involved in initializing" // anything. return true; - return SMOR.getMethod()->isConstexpr(); + return SMOR.getFunction()->isConstexpr(); +} + +static bool specialMemberIsConstexpr( + Sema &S, CXXRecordDecl *ClassDecl, Sema::CXXSpecialMember CSM, + unsigned Quals, bool ConstRHS, CXXConstructorDecl *InheritedCtor = nullptr, + Sema::InheritedConstructorInfo *Inherited = nullptr) { + return specialMemberIsConstexpr(S, nullptr, ClassDecl, CSM, Quals, ConstRHS, + InheritedCtor, Inherited); +} + +struct DefaultedComparisonInfo { + llvm::SmallVector ReturnTypes; + QualType CommonCategoryType; + bool ShouldDelete = false; + bool IsConstexpr = true; +}; + +static bool deduceBuiltinComparisonReturnType(Sema &S, QualType ArgType, + SourceLocation Loc, + DefaultedComparisonInfo &Info) { + Optional ReturnKind = + ComparisonCategories::computeComparisonTypeForBuiltin(ArgType); + if (!ReturnKind) { + // Not a type supported for builtin comparisons. (likely an array type) + Info.ShouldDelete = true; + return true; + } + // Require that the header has already been included and that + // computed comparison category declaration has been found. + QualType CompType = S.CheckComparisonCategoryType(ReturnKind.getValue(), Loc); + if (CompType.isNull()) { + Info.ShouldDelete = true; + return true; + } + Info.ReturnTypes.push_back(CompType); + return false; +} + +static DefaultedComparisonInfo +getDefaultedComparisonInfo(Sema &S, FunctionDecl *CompareOperator, + CXXRecordDecl *ClassDecl, SourceLocation Loc) { + using OvlResult = Sema::SpecialMemberOverloadResult; + using ResultKind = OvlResult::Kind; + DefaultedComparisonInfo Info; + Info.CommonCategoryType = S.Context.VoidTy; + + auto CheckOverload = [&](QualType ArgType) { + // FIXME(EricWF): Get and pass a better source location here. + OvlResult Res = S.LookupThreeWayComparison(ClassDecl, ArgType, Loc); + switch (Res.getKind()) { + case ResultKind::Success: { + if (Res.hasBuiltin()) { + if (deduceBuiltinComparisonReturnType(S, Res.getBuiltinArgType(), Loc, + Info)) + return true; + } else { + FunctionDecl *FD = Res.getFunction(); + if (FD->getReturnType()->isUndeducedType()) { + // FIXME(EricWF): Do this correctly? + // And should we be complaining during the initial pass? + if (S.DeduceReturnType(FD, Loc, /*Complain*/ true)) { + Info.ShouldDelete = true; + return true; + } + } + + Info.ReturnTypes.push_back(FD->getReturnType()); + } + return false; + } + case ResultKind::Ambiguous: + case ResultKind::NoMemberOrDeleted: { + return true; + } + } + }; + for (const auto &B : ClassDecl->bases()) { + const RecordType *BaseType = B.getType()->getAs(); + if (!BaseType) + continue; + + CXXRecordDecl *BaseClassDecl = cast(BaseType->getDecl()); + QualType ArgTy(BaseClassDecl->getTypeForDecl(), 0); + if (CheckOverload(ArgTy)) + return Info; + } + + for (const auto *F : ClassDecl->fields()) { + if (F->isInvalidDecl()) + continue; + QualType BaseType = S.Context.getBaseElementType(F->getType()); + // If the field has a reference type, remove it. Expressions can't have + // reference types. + BaseType = BaseType.getNonReferenceType(); + if (CheckOverload(BaseType)) + return Info; + } + + // OK, we have a potentially OK comparison operator. Compute the common + // comparison type, diagnosing errors relating to the lookup of STL + // declarations. + QualType CommonType = S.ComputeCommonComparisonType(Info.ReturnTypes, Loc); + // An error occured. + if (CommonType.isNull()) { + // FIXME: Should we mark this decl as invalid? + CompareOperator->setInvalidDecl(); + return Info; + } + + Info.CommonCategoryType = CommonType; + return Info; } /// Determine whether the specified special member function would be constexpr @@ -6238,7 +6366,8 @@ static bool defaultedSpecialMemberIsConstexpr( Sema &S, CXXRecordDecl *ClassDecl, Sema::CXXSpecialMember CSM, bool ConstArg, CXXConstructorDecl *InheritedCtor = nullptr, - Sema::InheritedConstructorInfo *Inherited = nullptr) { + Sema::InheritedConstructorInfo *Inherited = nullptr, + FunctionDecl *FD = nullptr) { if (!S.getLangOpts().CPlusPlus11) return false; @@ -6269,7 +6398,11 @@ // In C++1y, we need to perform overload resolution. Ctor = false; break; - + case Sema::CXXComparisonOperator: { + assert(FD != nullptr && + "we require a function declaration when checking comparisons"); + break; + } case Sema::CXXDestructor: case Sema::CXXInvalid: return false; @@ -6297,6 +6430,30 @@ if (!Ctor && !ClassDecl->isLiteral()) return false; + if (CSM == Sema::CXXComparisonOperator && + FD->getOverloadedOperator() != OO_Spaceship) { + + QualType ArgTy(ClassDecl->getTypeForDecl(), Qualifiers::Const); + Sema::SpecialMemberOverloadResult SML = + S.LookupThreeWayComparison(ClassDecl, ArgTy, FD->getLocation()); + switch (SML.getKind()) { + case Sema::SpecialMemberOverloadResult::NoMemberOrDeleted: + case Sema::SpecialMemberOverloadResult::Ambiguous: + return false; + case Sema::SpecialMemberOverloadResult::Success: + break; + } + if (auto *FoundFD = SML.getFunction()) + return FoundFD->isConstexpr(); + // Otherwise, we have a call to a builtin. It's possible we're using a + // user-defined conversion to the arg type of the builtin. We need to + // determine if that's a valid constant expression + DeclAccessPair ConvDecl = SML.getBuiltinConversionDecl(); + auto *ConvFD = dyn_cast_or_null(ConvDecl.getDecl()); + assert(ConvFD && "we should have a conversion decl"); + return ConvFD->isConstexpr(); + } + // -- every constructor involved in initializing [...] base class // sub-objects shall be a constexpr constructor; // -- the assignment operator selected to copy/move each direct base @@ -6326,7 +6483,7 @@ QualType BaseType = S.Context.getBaseElementType(F->getType()); if (const RecordType *RecordTy = BaseType->getAs()) { CXXRecordDecl *FieldRecDecl = cast(RecordTy->getDecl()); - if (!specialMemberIsConstexpr(S, FieldRecDecl, CSM, + if (!specialMemberIsConstexpr(S, ClassDecl, FieldRecDecl, CSM, BaseType.getCVRQualifiers(), ConstArg && !F->isMutable())) return false; @@ -6340,17 +6497,18 @@ } static Sema::ImplicitExceptionSpecification -ComputeDefaultedSpecialMemberExceptionSpec( - Sema &S, SourceLocation Loc, CXXMethodDecl *MD, Sema::CXXSpecialMember CSM, +ComputeDefaultedSpecialMemberExceptionSpec(Sema &S, SourceLocation Loc, + FunctionDecl *FD, + Sema::CXXSpecialMember CSM, Sema::InheritedConstructorInfo *ICI); static Sema::ImplicitExceptionSpecification -computeImplicitExceptionSpec(Sema &S, SourceLocation Loc, CXXMethodDecl *MD) { - auto CSM = S.getSpecialMember(MD); +computeImplicitExceptionSpec(Sema &S, SourceLocation Loc, FunctionDecl *FD) { + auto CSM = S.getSpecialMember(FD); if (CSM != Sema::CXXInvalid) - return ComputeDefaultedSpecialMemberExceptionSpec(S, Loc, MD, CSM, nullptr); + return ComputeDefaultedSpecialMemberExceptionSpec(S, Loc, FD, CSM, nullptr); - auto *CD = cast(MD); + auto *CD = cast(FD); assert(CD->getInheritedConstructor() && "only special members have implicit exception specs"); Sema::InheritedConstructorInfo ICI( @@ -6360,54 +6518,69 @@ } static FunctionProtoType::ExtProtoInfo getImplicitMethodEPI(Sema &S, - CXXMethodDecl *MD) { + FunctionDecl *FD) { FunctionProtoType::ExtProtoInfo EPI; // Build an exception specification pointing back at this member. EPI.ExceptionSpec.Type = EST_Unevaluated; - EPI.ExceptionSpec.SourceDecl = MD; + EPI.ExceptionSpec.SourceDecl = FD; // Set the calling convention to the default for C++ instance methods. - EPI.ExtInfo = EPI.ExtInfo.withCallingConv( - S.Context.getDefaultCallingConvention(/*IsVariadic=*/false, - /*IsCXXMethod=*/true)); + EPI.ExtInfo = + EPI.ExtInfo.withCallingConv(S.Context.getDefaultCallingConvention( + /*IsVariadic=*/false, + /*IsCXXMethod=*/isa(FD))); return EPI; } -void Sema::EvaluateImplicitExceptionSpec(SourceLocation Loc, CXXMethodDecl *MD) { - const FunctionProtoType *FPT = MD->getType()->castAs(); +void Sema::EvaluateImplicitExceptionSpec(SourceLocation Loc, FunctionDecl *FD) { + const FunctionProtoType *FPT = FD->getType()->castAs(); if (FPT->getExceptionSpecType() != EST_Unevaluated) return; // Evaluate the exception specification. - auto IES = computeImplicitExceptionSpec(*this, Loc, MD); + auto IES = computeImplicitExceptionSpec(*this, Loc, FD); auto ESI = IES.getExceptionSpec(); // Update the type of the special member to use it. - UpdateExceptionSpec(MD, ESI); + UpdateExceptionSpec(FD, ESI); // A user-provided destructor can be defined outside the class. When that // happens, be sure to update the exception specification on both // declarations. const FunctionProtoType *CanonicalFPT = - MD->getCanonicalDecl()->getType()->castAs(); + FD->getCanonicalDecl()->getType()->castAs(); if (CanonicalFPT->getExceptionSpecType() == EST_Unevaluated) - UpdateExceptionSpec(MD->getCanonicalDecl(), ESI); + UpdateExceptionSpec(FD->getCanonicalDecl(), ESI); } -void Sema::CheckExplicitlyDefaultedSpecialMember(CXXMethodDecl *MD) { - CXXRecordDecl *RD = MD->getParent(); - CXXSpecialMember CSM = getSpecialMember(MD); +void Sema::CheckExplicitlyDefaultedMember(FunctionDecl *FD) { + CXXSpecialMember CSM = getSpecialMember(FD); - assert(MD->isExplicitlyDefaulted() && CSM != CXXInvalid && + assert(FD->isExplicitlyDefaulted() && CSM != CXXInvalid && "not an explicitly-defaulted special member"); // Whether this was the first-declared instance of the constructor. // This affects whether we implicitly add an exception spec and constexpr. - bool First = MD == MD->getCanonicalDecl(); + FunctionDecl *CanonFD = FD->getCanonicalDecl(); + bool First = FD == CanonFD; + + bool IsMethod = isa(FD); + + // Dig up the record decl in which this function was declared. If it's a + // defaulted non-method comparison operator, the canonical decls lexical + // context will provide this. + + CXXRecordDecl *RD; + if (IsMethod) + RD = cast(FD)->getParent(); + else + RD = dyn_cast(FD->getLexicalParent()); + assert(RD); bool HadError = false; + // C++11 [dcl.fct.def.default]p1: // A function that is explicitly defaulted shall // -- be a special member function (checked elsewhere), @@ -6418,20 +6591,25 @@ unsigned ExpectedParams = 1; if (CSM == CXXDefaultConstructor || CSM == CXXDestructor) ExpectedParams = 0; - if (MD->getNumParams() != ExpectedParams) { - // This also checks for default arguments: a copy or move constructor with a - // default argument is classified as a default constructor, and assignment - // operations and destructors can't have default arguments. - Diag(MD->getLocation(), diag::err_defaulted_special_member_params) - << CSM << MD->getSourceRange(); + else if (!IsMethod) { + assert(CSM == CXXComparisonOperator); + ExpectedParams = 2; + } + if (FD->getNumParams() != ExpectedParams) { + // This also checks for default arguments: a copy or move constructor with + // a default argument is classified as a default constructor, and + // assignment operations and destructors can't have default arguments. + Diag(FD->getLocation(), diag::err_defaulted_special_member_params) + << CSM << FD->getSourceRange(); + HadError = true; - } else if (MD->isVariadic()) { - Diag(MD->getLocation(), diag::err_defaulted_special_member_variadic) - << CSM << MD->getSourceRange(); + } else if (FD->isVariadic()) { + Diag(FD->getLocation(), diag::err_defaulted_special_member_variadic) + << CSM << FD->getSourceRange(); HadError = true; } - const FunctionProtoType *Type = MD->getType()->getAs(); + const FunctionProtoType *Type = FD->getType()->getAs(); bool CanHaveConstParam = false; if (CSM == CXXCopyConstructor) @@ -6446,14 +6624,14 @@ QualType ExpectedReturnType = Context.getLValueReferenceType(Context.getTypeDeclType(RD)); if (!Context.hasSameType(ReturnType, ExpectedReturnType)) { - Diag(MD->getLocation(), diag::err_defaulted_special_member_return_type) - << (CSM == CXXMoveAssignment) << ExpectedReturnType; + Diag(FD->getLocation(), diag::err_defaulted_special_member_return_type) + << CSM << ExpectedReturnType; HadError = true; } // A defaulted special member cannot have cv-qualifiers. if (Type->getTypeQuals()) { - Diag(MD->getLocation(), diag::err_defaulted_special_member_quals) + Diag(FD->getLocation(), diag::err_defaulted_special_member_quals) << (CSM == CXXMoveAssignment) << getLangOpts().CPlusPlus14; HadError = true; } @@ -6462,25 +6640,78 @@ // Check for parameter type matching. QualType ArgType = ExpectedParams ? Type->getParamType(0) : QualType(); bool HasConstParam = false; - if (ExpectedParams && ArgType->isReferenceType()) { + // FIXME(EricWF): Integrate this better + DefaultedComparisonInfo CompareInfo; + if (CSM == CXXComparisonOperator) { + CompareInfo = getDefaultedComparisonInfo(*this, FD, RD, FD->getLocation()); + + auto Opc = BinaryOperator::getOverloadedOpcode(FD->getOverloadedOperator()); + bool IsEqualityOrRelational = BinaryOperator::isEqualityOp(Opc) || + BinaryOperator::isRelationalOp(Opc); + ReturnType = FD->getReturnType(); + if (IsEqualityOrRelational) { + QualType ExpectTy = Context.BoolTy; + if (!Context.hasSameType(ReturnType, ExpectTy)) { + Diag(FD->getLocation(), diag::err_defaulted_special_member_return_type) + << CSM << ExpectTy; + HadError = true; + } + } else { + if (auto *AutoTy = FD->getReturnType()->getAs()) { + if (!CompareInfo.CommonCategoryType.isNull()) + ReturnType = CompareInfo.CommonCategoryType; + else + ReturnType = Context.VoidTy; + } + if (!FD->isInvalidDecl() && + Context.hasSameType(ReturnType, Context.VoidTy)) + CompareInfo.ShouldDelete = true; + } + + // FIXME(EricWF): This isn't currently required by the standard, however, + // not requiring it leads to issues. + if (IsMethod && !Type->isConst()) { + Diag(FD->getLocEnd(), + diag::err_defaulted_comparison_operator_member_non_const) + << FD->getSourceRange(); + HadError = true; + } + + // Check the types of the parameters. They must be 'T const&' where T is + // the record type it is being declared within. + if (FD->getNumParams() == ExpectedParams) { + QualType ExpectedParamType = Context.getLValueReferenceType( + Context.getTypeDeclType(RD).withConst()); + for (unsigned I = 0, End = FD->getNumParams(); I < End; ++I) { + QualType ParamTy = FD->getParamDecl(I)->getType(); + if (!ParamTy->isDependentType() && + !Context.hasSameType(ExpectedParamType, ParamTy)) { + Diag(FD->getLocation(), + diag::err_defaulted_comparison_operator_param_type) + << ExpectedParamType << ParamTy + << FD->getParamDecl(I)->getSourceRange(); + HadError = true; + } + } + } + } else if (ExpectedParams && ArgType->isReferenceType()) { // Argument must be reference to possibly-const T. QualType ReferentType = ArgType->getPointeeType(); HasConstParam = ReferentType.isConstQualified(); if (ReferentType.isVolatileQualified()) { - Diag(MD->getLocation(), - diag::err_defaulted_special_member_volatile_param) << CSM; + Diag(FD->getLocation(), diag::err_defaulted_special_member_volatile_param) + << CSM; HadError = true; } - if (HasConstParam && !CanHaveConstParam) { if (CSM == CXXCopyConstructor || CSM == CXXCopyAssignment) { - Diag(MD->getLocation(), + Diag(FD->getLocation(), diag::err_defaulted_special_member_copy_const_param) << (CSM == CXXCopyAssignment); // FIXME: Explain why this special member can't be const. } else { - Diag(MD->getLocation(), + Diag(FD->getLocation(), diag::err_defaulted_special_member_move_const_param) << (CSM == CXXMoveAssignment); } @@ -6490,7 +6721,7 @@ // A copy assignment operator can take its argument by value, but a // defaulted one cannot. assert(CSM == CXXCopyAssignment && "unexpected non-ref argument"); - Diag(MD->getLocation(), diag::err_defaulted_copy_assign_not_ref); + Diag(FD->getLocation(), diag::err_defaulted_copy_assign_not_ref); HadError = true; } @@ -6501,19 +6732,20 @@ // makes such functions always instantiate to constexpr functions. For // functions which cannot be constexpr (for non-constructors in C++11 and for // destructors in C++1y), this is checked elsewhere. - bool Constexpr = defaultedSpecialMemberIsConstexpr(*this, RD, CSM, - HasConstParam); - if ((getLangOpts().CPlusPlus14 ? !isa(MD) - : isa(MD)) && - MD->isConstexpr() && !Constexpr && - MD->getTemplatedKind() == FunctionDecl::TK_NonTemplate) { - Diag(MD->getLocStart(), diag::err_incorrect_defaulted_constexpr) << CSM; + bool Constexpr = defaultedSpecialMemberIsConstexpr( + *this, RD, CSM, HasConstParam, nullptr, nullptr, FD); + if ((getLangOpts().CPlusPlus14 ? !isa(FD) + : isa(FD)) && + FD->isConstexpr() && !Constexpr && + FD->getTemplatedKind() == FunctionDecl::TK_NonTemplate) { + Diag(FD->getLocStart(), diag::err_incorrect_defaulted_constexpr) << CSM; // FIXME: Explain why the special member can't be constexpr. HadError = true; } // and may have an explicit exception-specification only if it is compatible // with the exception-specification on the implicit declaration. + // FIXME(EricWF): Check exception specs? if (Type->hasExceptionSpec()) { // Delay the check if this is the first declaration of the special member, // since we may not have parsed some necessary in-class initializers yet. @@ -6521,46 +6753,57 @@ // If the exception specification needs to be instantiated, do so now, // before we clobber it with an EST_Unevaluated specification below. if (Type->getExceptionSpecType() == EST_Uninstantiated) { - InstantiateExceptionSpec(MD->getLocStart(), MD); - Type = MD->getType()->getAs(); + InstantiateExceptionSpec(FD->getLocStart(), FD); + Type = FD->getType()->getAs(); + } + DelayedDefaultedMemberExceptionSpecs.push_back(std::make_pair(FD, Type)); + } else if (CSM != CXXComparisonOperator) { + CheckExplicitlyDefaultedMemberExceptionSpec(FD, Type); + } else { + // This should be an explicitly-defaulted comparison operator which + // has been re-declared as defaulted outside, so it should already be + // ill formed. + assert(CSM == CXXComparisonOperator && FD->isInvalidDecl()); } - DelayedDefaultedMemberExceptionSpecs.push_back(std::make_pair(MD, Type)); - } else - CheckExplicitlyDefaultedMemberExceptionSpec(MD, Type); } // If a function is explicitly defaulted on its first declaration, if (First) { // -- it is implicitly considered to be constexpr if the implicit // definition would be, - MD->setConstexpr(Constexpr); - + FD->setConstexpr(Constexpr); + llvm::SmallVector Args; + Args.append(ExpectedParams, ArgType); // -- it is implicitly considered to have the same exception-specification // as if it had been implicitly declared, FunctionProtoType::ExtProtoInfo EPI = Type->getExtProtoInfo(); + // FIXME(EricWF) EPI.ExceptionSpec.Type = EST_Unevaluated; - EPI.ExceptionSpec.SourceDecl = MD; - MD->setType(Context.getFunctionType(ReturnType, - llvm::makeArrayRef(&ArgType, - ExpectedParams), - EPI)); + EPI.ExceptionSpec.SourceDecl = FD; + FD->setType(Context.getFunctionType(ReturnType, Args, EPI)); } - if (ShouldDeleteSpecialMember(MD, CSM)) { + bool ShouldDelete = + (CSM == CXXComparisonOperator && + Context.hasSameType(Context.VoidTy, FD->getReturnType())); + if (ShouldDelete || ShouldDeleteSpecialMember(FD, CSM)) { if (First) { - SetDeclDeleted(MD, MD->getLocation()); + SetDeclDeleted(FD, FD->getLocation()); } else { + assert(CSM != CXXComparisonOperator); // FIXME(EricWF) + assert(isa(FD)); + CXXMethodDecl *MD = cast(FD); // C++11 [dcl.fct.def.default]p4: // [For a] user-provided explicitly-defaulted function [...] if such a // function is implicitly defined as deleted, the program is ill-formed. - Diag(MD->getLocation(), diag::err_out_of_line_default_deletes) << CSM; + Diag(FD->getLocation(), diag::err_out_of_line_default_deletes) << CSM; ShouldDeleteSpecialMember(MD, CSM, nullptr, /*Diagnose*/ true); HadError = true; } } if (HadError) - MD->setInvalidDecl(); + FD->setInvalidDecl(); } /// Check whether the exception specification provided for an @@ -6568,18 +6811,19 @@ /// that would have been generated for an implicit special member, per /// C++11 [dcl.fct.def.default]p2. void Sema::CheckExplicitlyDefaultedMemberExceptionSpec( - CXXMethodDecl *MD, const FunctionProtoType *SpecifiedType) { + FunctionDecl *FD, const FunctionProtoType *SpecifiedType) { // If the exception specification was explicitly specified but hadn't been // parsed when the method was defaulted, grab it now. if (SpecifiedType->getExceptionSpecType() == EST_Unparsed) SpecifiedType = - MD->getTypeSourceInfo()->getType()->castAs(); + FD->getTypeSourceInfo()->getType()->castAs(); // Compute the implicit exception specification. - CallingConv CC = Context.getDefaultCallingConvention(/*IsVariadic=*/false, - /*IsCXXMethod=*/true); + CallingConv CC = Context.getDefaultCallingConvention( + /*IsVariadic=*/false, + /*IsCXXMethod=*/isa(FD)); FunctionProtoType::ExtProtoInfo EPI(CC); - auto IES = computeImplicitExceptionSpec(*this, MD->getLocation(), MD); + auto IES = computeImplicitExceptionSpec(*this, FD->getLocation(), FD); EPI.ExceptionSpec = IES.getExceptionSpec(); const FunctionProtoType *ImplicitType = cast( Context.getFunctionType(Context.VoidTy, None, EPI)); @@ -6587,9 +6831,9 @@ // Ensure that it matches. CheckEquivalentExceptionSpec( PDiag(diag::err_incorrect_defaulted_exception_spec) - << getSpecialMember(MD), PDiag(), - ImplicitType, SourceLocation(), - SpecifiedType, MD->getLocation()); + << getSpecialMember(FD), + PDiag(), ImplicitType, SourceLocation(), SpecifiedType, + FD->getLocation()); } void Sema::CheckDelayedMemberExceptionSpecs() { @@ -6613,19 +6857,24 @@ namespace { /// CRTP base class for visiting operations performed by a special member /// function (or inherited constructor). -template -struct SpecialMemberVisitor { +template struct SpecialMemberVisitor { Sema &S; - CXXMethodDecl *MD; + FunctionDecl *FD; Sema::CXXSpecialMember CSM; Sema::InheritedConstructorInfo *ICI; + CXXRecordDecl *Record; // Properties of the special member, computed for convenience. - bool IsConstructor = false, IsAssignment = false, ConstArg = false; + bool IsConstructor = false, IsAssignment = false, IsComparison = false, + ConstArg = false; - SpecialMemberVisitor(Sema &S, CXXMethodDecl *MD, Sema::CXXSpecialMember CSM, + SpecialMemberVisitor(Sema &S, FunctionDecl *FD, Sema::CXXSpecialMember CSM, Sema::InheritedConstructorInfo *ICI) - : S(S), MD(MD), CSM(CSM), ICI(ICI) { + : S(S), FD(FD), CSM(CSM), ICI(ICI) { + if (auto *MD = dyn_cast(FD)) + Record = MD->getParent(); + else + Record = cast(FD->getLexicalParent()); switch (CSM) { case Sema::CXXDefaultConstructor: case Sema::CXXCopyConstructor: @@ -6638,13 +6887,16 @@ break; case Sema::CXXDestructor: break; + case Sema::CXXComparisonOperator: + IsComparison = true; + break; case Sema::CXXInvalid: llvm_unreachable("invalid special member kind"); } - if (MD->getNumParams()) { + if (FD->getNumParams()) { if (const ReferenceType *RT = - MD->getParamDecl(0)->getType()->getAs()) + FD->getParamDecl(0)->getType()->getAs()) ConstArg = RT->getPointeeType().isConstQualified(); } } @@ -6659,7 +6911,7 @@ /// Look up the corresponding special member in the given class. Sema::SpecialMemberOverloadResult lookupIn(CXXRecordDecl *Class, unsigned Quals, bool IsMutable) { - return lookupCallFromSpecialMember(S, Class, CSM, Quals, + return lookupCallFromSpecialMember(S, Record, Class, CSM, Quals, ConstArg && !IsMutable); } @@ -6669,8 +6921,9 @@ if (!ICI) return {}; assert(CSM == Sema::CXXDefaultConstructor); - auto *BaseCtor = - cast(MD)->getInheritedConstructor().getConstructor(); + auto *BaseCtor = cast(FD) + ->getInheritedConstructor() + .getConstructor(); if (auto *MD = ICI->findConstructorForBase(Class, BaseCtor).first) return MD; return {}; @@ -6703,22 +6956,20 @@ // Visit the bases and members of the class. bool visit(BasesToVisit Bases) { - CXXRecordDecl *RD = MD->getParent(); - if (Bases == VisitPotentiallyConstructedBases) - Bases = RD->isAbstract() ? VisitNonVirtualBases : VisitAllBases; + Bases = Record->isAbstract() ? VisitNonVirtualBases : VisitAllBases; - for (auto &B : RD->bases()) + for (auto &B : Record->bases()) if ((Bases == VisitDirectBases || !B.isVirtual()) && getDerived().visitBase(&B)) return true; if (Bases == VisitAllBases) - for (auto &B : RD->vbases()) + for (auto &B : Record->vbases()) if (getDerived().visitBase(&B)) return true; - for (auto *F : RD->fields()) + for (auto *F : Record->fields()) if (!F->isInvalidDecl() && !F->isUnnamedBitfield() && getDerived().visitField(F)) return true; @@ -6737,13 +6988,13 @@ bool AllFieldsAreConst; - SpecialMemberDeletionInfo(Sema &S, CXXMethodDecl *MD, + SpecialMemberDeletionInfo(Sema &S, FunctionDecl *FD, Sema::CXXSpecialMember CSM, Sema::InheritedConstructorInfo *ICI, bool Diagnose) - : SpecialMemberVisitor(S, MD, CSM, ICI), Diagnose(Diagnose), - Loc(MD->getLocation()), AllFieldsAreConst(true) {} + : SpecialMemberVisitor(S, FD, CSM, ICI), Diagnose(Diagnose), + Loc(FD->getLocation()), AllFieldsAreConst(true) {} - bool inUnion() const { return MD->getParent()->isUnion(); } + bool inUnion() const { return Record->isUnion(); } Sema::CXXSpecialMember getEffectiveCSM() { return ICI ? Sema::CXXInvalid : CSM; @@ -6762,20 +7013,25 @@ Sema::SpecialMemberOverloadResult SMOR, bool IsDtorCallInCtor); - bool isAccessible(Subobject Subobj, CXXMethodDecl *D); + bool isAccessible(Subobject Subobj, FunctionDecl *D); }; } /// Is the given special member inaccessible when used on the given /// sub-object. bool SpecialMemberDeletionInfo::isAccessible(Subobject Subobj, - CXXMethodDecl *target) { + FunctionDecl *FnTarget) { + auto *target = dyn_cast_or_null(FnTarget); + // we're handling a friend declaration for an explicitly-defaulted + // comparison operator + if (!target) + return true; /// If we're operating on a base class, the object type is the /// type of this special member. QualType objectTy; AccessSpecifier access = target->getAccess(); if (CXXBaseSpecifier *base = Subobj.dyn_cast()) { - objectTy = S.Context.getTypeDeclType(MD->getParent()); + objectTy = S.Context.getTypeDeclType(Record); access = CXXRecordDecl::MergeAccess(base->getAccessSpecifier(), access); // If we're operating on a field, the object type is the type of the field. @@ -6786,24 +7042,42 @@ return S.isSpecialMemberAccessibleForDeletion(target, access, objectTy); } +static bool shouldDeleteForBuiltin(Sema &S, QualType ArgType, + SourceLocation Loc) { + Optional CompType = + ComparisonCategories::computeComparisonTypeForBuiltin(ArgType); + if (!CompType) + return true; + // Require that the header has already been included and that + // computed comparison category declaration has been found. + return S.CheckComparisonCategoryType(*CompType, Loc).isNull(); +} + /// Check whether we should delete a special member due to the implicit /// definition containing a call to a special member of a subobject. bool SpecialMemberDeletionInfo::shouldDeleteForSubobjectCall( Subobject Subobj, Sema::SpecialMemberOverloadResult SMOR, bool IsDtorCallInCtor) { - CXXMethodDecl *Decl = SMOR.getMethod(); + // FIXME(EricWF) This is wrong, it needs to handle functions soon + FunctionDecl *Decl = SMOR.getFunction(); FieldDecl *Field = Subobj.dyn_cast(); - + bool IsTrivial = false; + if (auto *MD = dyn_cast_or_null(Decl)) + IsTrivial = MD->isTrivial(); int DiagKind = -1; - - if (SMOR.getKind() == Sema::SpecialMemberOverloadResult::NoMemberOrDeleted) + if (SMOR.hasBuiltin() && + shouldDeleteForBuiltin(S, SMOR.getBuiltinArgType(), FD->getLocation())) { + assert(CSM == Sema::CXXComparisonOperator); + DiagKind = 0; + } else if (SMOR.getKind() == + Sema::SpecialMemberOverloadResult::NoMemberOrDeleted) DiagKind = !Decl ? 0 : 1; else if (SMOR.getKind() == Sema::SpecialMemberOverloadResult::Ambiguous) DiagKind = 2; else if (!isAccessible(Subobj, Decl)) DiagKind = 3; else if (!IsDtorCallInCtor && Field && Field->getParent()->isUnion() && - !Decl->isTrivial()) { + !IsTrivial && CSM != Sema::CXXComparisonOperator) { // A member of a union must have a trivial corresponding special member. // As a weird special case, a destructor call from a union's constructor // must be accessible and non-deleted, but need not be trivial. Such a @@ -6819,14 +7093,14 @@ if (Field) { S.Diag(Field->getLocation(), diag::note_deleted_special_member_class_subobject) - << getEffectiveCSM() << MD->getParent() << /*IsField*/true - << Field << DiagKind << IsDtorCallInCtor; + << getEffectiveCSM() << Record << /*IsField*/ true << Field + << DiagKind << IsDtorCallInCtor; } else { CXXBaseSpecifier *Base = Subobj.get(); S.Diag(Base->getLocStart(), diag::note_deleted_special_member_class_subobject) - << getEffectiveCSM() << MD->getParent() << /*IsField*/false - << Base->getType() << DiagKind << IsDtorCallInCtor; + << getEffectiveCSM() << Record << /*IsField*/ false << Base->getType() + << DiagKind << IsDtorCallInCtor; } if (DiagKind == 1) @@ -6858,8 +7132,8 @@ // C++11 [class.dtor]p5: // -- any direct or virtual base class [...] has a type with a destructor // that is deleted or inaccessible - if (!(CSM == Sema::CXXDefaultConstructor && - Field && Field->hasInClassInitializer()) && + if (!(CSM == Sema::CXXDefaultConstructor && Field && + Field->hasInClassInitializer()) && shouldDeleteForSubobjectCall(Subobj, lookupIn(Class, Quals, IsMutable), false)) return true; @@ -6868,9 +7142,8 @@ // -- any direct or virtual base class or non-static data member has a // type with a destructor that is deleted or inaccessible if (IsConstructor) { - Sema::SpecialMemberOverloadResult SMOR = - S.LookupSpecialMember(Class, Sema::CXXDestructor, - false, false, false, false, false); + Sema::SpecialMemberOverloadResult SMOR = S.LookupSpecialMember( + Class, Sema::CXXDestructor, false, false, false, false, false); if (shouldDeleteForSubobjectCall(Subobj, SMOR, true)) return true; } @@ -6889,7 +7162,7 @@ // If we have an inheriting constructor, check whether we're calling an // inherited constructor instead of a default constructor. Sema::SpecialMemberOverloadResult SMOR = lookupInheritedCtor(BaseClass); - if (auto *BaseCtor = SMOR.getMethod()) { + if (auto *BaseCtor = SMOR.getFunction()) { // Note that we do not check access along this path; other than that, // this is the same as shouldDeleteForSubobjectCall(Base, BaseCtor, false); // FIXME: Check that the base has a usable destructor! Sink this into @@ -6897,8 +7170,8 @@ if (BaseCtor->isDeleted() && Diagnose) { S.Diag(Base->getLocStart(), diag::note_deleted_special_member_class_subobject) - << getEffectiveCSM() << MD->getParent() << /*IsField*/false - << Base->getType() << /*Deleted*/1 << /*IsDtorCallInCtor*/false; + << getEffectiveCSM() << Record << /*IsField*/ false << Base->getType() + << /*Deleted*/ 1 << /*IsDtorCallInCtor*/ false; S.NoteDeletedFunction(BaseCtor); } return BaseCtor->isDeleted(); @@ -6918,7 +7191,7 @@ if (FieldType->isReferenceType() && !FD->hasInClassInitializer()) { if (Diagnose) S.Diag(FD->getLocation(), diag::note_deleted_default_ctor_uninit_field) - << !!ICI << MD->getParent() << FD << FieldType << /*Reference*/0; + << !!ICI << Record << FD << FieldType << /*Reference*/ 0; return true; } // C++11 [class.ctor]p5: any non-variant non-static data member of @@ -6930,7 +7203,7 @@ (!FieldRecord || !FieldRecord->hasUserProvidedDefaultConstructor())) { if (Diagnose) S.Diag(FD->getLocation(), diag::note_deleted_default_ctor_uninit_field) - << !!ICI << MD->getParent() << FD << FD->getType() << /*Const*/1; + << !!ICI << Record << FD << FD->getType() << /*Const*/ 1; return true; } @@ -6942,7 +7215,7 @@ if (FieldType->isRValueReferenceType()) { if (Diagnose) S.Diag(FD->getLocation(), diag::note_deleted_copy_ctor_rvalue_reference) - << MD->getParent() << FD << FieldType; + << Record << FD << FieldType; return true; } } else if (IsAssignment) { @@ -6950,7 +7223,7 @@ if (FieldType->isReferenceType()) { if (Diagnose) S.Diag(FD->getLocation(), diag::note_deleted_assign_field) - << isMove() << MD->getParent() << FD << FieldType << /*Reference*/0; + << isMove() << Record << FD << FieldType << /*Reference*/ 0; return true; } if (!FieldRecord && FieldType.isConstQualified()) { @@ -6958,10 +7231,12 @@ // -- a non-static data member of const non-class type (or array thereof) if (Diagnose) S.Diag(FD->getLocation(), diag::note_deleted_assign_field) - << isMove() << MD->getParent() << FD << FD->getType() << /*Const*/1; + << isMove() << Record << FD << FD->getType() << /*Const*/ 1; return true; } + } else if (IsComparison) { } + // FIXME(EricWF) if (FieldRecord) { // Some additional restrictions exist on the variant members. @@ -6989,7 +7264,7 @@ if (Diagnose) S.Diag(FieldRecord->getLocation(), diag::note_deleted_default_ctor_all_const) - << !!ICI << MD->getParent() << /*anonymous union*/1; + << !!ICI << Record << /*anonymous union*/ 1; return true; } @@ -7014,15 +7289,14 @@ // default constructor. Don't do that. if (CSM == Sema::CXXDefaultConstructor && inUnion() && AllFieldsAreConst) { bool AnyFields = false; - for (auto *F : MD->getParent()->fields()) + for (auto *F : Record->fields()) if ((AnyFields = !F->isUnnamedBitfield())) break; if (!AnyFields) return false; if (Diagnose) - S.Diag(MD->getParent()->getLocation(), - diag::note_deleted_default_ctor_all_const) - << !!ICI << MD->getParent() << /*not anonymous union*/0; + S.Diag(Record->getLocation(), diag::note_deleted_default_ctor_all_const) + << !!ICI << Record << /*not anonymous union*/ 0; return true; } return false; @@ -7031,12 +7305,17 @@ /// Determine whether a defaulted special member function should be defined as /// deleted, as specified in C++11 [class.ctor]p5, C++11 [class.copy]p11, /// C++11 [class.copy]p23, and C++11 [class.dtor]p5. -bool Sema::ShouldDeleteSpecialMember(CXXMethodDecl *MD, CXXSpecialMember CSM, +bool Sema::ShouldDeleteSpecialMember(FunctionDecl *FD, CXXSpecialMember CSM, InheritedConstructorInfo *ICI, bool Diagnose) { - if (MD->isInvalidDecl()) + if (FD->isInvalidDecl()) return false; - CXXRecordDecl *RD = MD->getParent(); + CXXRecordDecl *RD; + if (auto *MD = dyn_cast(FD)) + RD = MD->getParent(); + else + RD = cast(FD->getLexicalParent()); + assert(!RD->isDependentType() && "do deletion after instantiation"); if (!LangOpts.CPlusPlus11 || RD->isInvalidDecl()) return false; @@ -7063,7 +7342,7 @@ // If the class definition declares a move constructor or move assignment // operator, an implicitly declared copy constructor or copy assignment // operator is defined as deleted. - if (MD->isImplicit() && + if (FD->isImplicit() && (CSM == CXXCopyConstructor || CSM == CXXCopyAssignment)) { CXXMethodDecl *UserDeclaredMove = nullptr; @@ -7110,24 +7389,99 @@ } // Do access control from the special member function - ContextRAII MethodContext(*this, MD); + ContextRAII MethodContext(*this, FD); // C++11 [class.dtor]p5: // -- for a virtual destructor, lookup of the non-array deallocation function // results in an ambiguity or in a function that is deleted or inaccessible - if (CSM == CXXDestructor && MD->isVirtual()) { + if (CSM == CXXDestructor && cast(FD)->isVirtual()) { FunctionDecl *OperatorDelete = nullptr; DeclarationName Name = Context.DeclarationNames.getCXXOperatorName(OO_Delete); - if (FindDeallocationFunction(MD->getLocation(), MD->getParent(), Name, - OperatorDelete, /*Diagnose*/false)) { + if (FindDeallocationFunction(FD->getLocation(), RD, Name, OperatorDelete, + /*Diagnose*/ false)) { if (Diagnose) Diag(RD->getLocation(), diag::note_deleted_dtor_no_operator_delete); return true; } } - SpecialMemberDeletionInfo SMI(*this, MD, CSM, ICI, Diagnose); + // Handle defaulted relational and equality comparisons. These comparisons + // are simply rewritten in terms of operator<=>, so we don't need to visit + // each member to determine if it's valid. + if (CSM == CXXComparisonOperator && + FD->getOverloadedOperator() != OO_Spaceship) { + BinaryOperatorKind Opc = + BinaryOperator::getOverloadedOpcode(FD->getOverloadedOperator()); + bool IsRelational = BinaryOperator::isRelationalOp(Opc); + + QualType ArgTy(RD->getTypeForDecl(), Qualifiers::Const); + SpecialMemberOverloadResult SML = + LookupThreeWayComparison(RD, ArgTy, FD->getLocation()); + switch (SML.getKind()) { + case SpecialMemberOverloadResult::NoMemberOrDeleted: + case SpecialMemberOverloadResult::Ambiguous: + return true; + case SpecialMemberOverloadResult::Success: + break; + } + const ComparisonCategoryInfo *Info; + if (auto *FoundFD = SML.getFunction()) { + if (FoundFD->getReturnType()->isUndeducedType()) { + // FIXME(EricWF): Do this correctly? + // And should we be complaining during the initial pass? + if (DeduceReturnType(FoundFD, FD->getLocation(), /*Complain*/ true)) + return true; + } + if (auto *MD = dyn_cast(FoundFD)) { + if (!isSpecialMemberAccessibleForDeletion(MD, MD->getAccess(), + Context.getTypeDeclType(RD))) + return true; + } + Info = Context.CompCategories.computeCommonComparisonType( + FoundFD->getReturnType()); + } else { + // FIXME(EricWF): We've somehow selected a builtin candidate for a class + // type (probably via a conversion operator). We need to check the access + // control for whatever conversion function was selected. + // + // Similarly we'll need to check if the conversion is constexpr, and + // determine the exception specification for it. + Optional CompKind = + ComparisonCategories::computeComparisonTypeForBuiltin( + SML.getBuiltinArgType()); + + if (!CompKind) + return true; + Info = Context.CompCategories.lookupInfo(CompKind.getValue()); + if (!Info) + return true; + + DeclAccessPair ConvDecl = SML.getBuiltinConversionDecl(); + CXXMethodDecl *ConvFD = cast_or_null(ConvDecl.getDecl()); + if (ConvFD && + !isSpecialMemberAccessibleForDeletion(ConvFD, ConvDecl.getAccess(), + Context.getTypeDeclType(RD))) + return true; + } + if (!Info) + return true; + switch (Info->Kind) { + case ComparisonCategoryType::StrongOrdering: + case ComparisonCategoryType::WeakOrdering: + case ComparisonCategoryType::PartialOrdering: + break; + case ComparisonCategoryType::StrongEquality: + case ComparisonCategoryType::WeakEquality: + if (IsRelational) + return true; + break; + } + // FIXME(EricWF): Check access. Handle rewritten expressions? + return false; + } + + SpecialMemberDeletionInfo SMI(*this, FD, CSM, ICI, Diagnose); // Per DR1611, do not consider virtual bases of constructors of abstract // classes, since we are not going to construct them. @@ -7135,14 +7489,18 @@ // classes either. // Per DR2180, for assignment operators we only assign (and thus only // consider) direct bases. - if (SMI.visit(SMI.IsAssignment ? SMI.VisitDirectBases + // FIXME(EricWF): IS this correct? + if (SMI.visit((SMI.IsAssignment || SMI.IsComparison) + ? SMI.VisitDirectBases : SMI.VisitPotentiallyConstructedBases)) return true; if (SMI.shouldDeleteForAllConstMembers()) return true; - if (getLangOpts().CUDA) { + // FIXME(EricWF) + auto *MD = dyn_cast(FD); + if (getLangOpts().CUDA && MD) { // We should delete the special member in CUDA mode if target inference // failed. return inferCUDATargetForImplicitSpecialMember(RD, CSM, MD, SMI.ConstArg, @@ -7174,7 +7532,8 @@ switch (CSM) { case Sema::CXXInvalid: llvm_unreachable("not a special member"); - + case Sema::CXXComparisonOperator: + llvm_unreachable("handled elsewhere"); case Sema::CXXDefaultConstructor: // C++11 [class.ctor]p5: // A default constructor is trivial if: @@ -7260,8 +7619,8 @@ case Sema::CXXMoveConstructor: case Sema::CXXMoveAssignment: NeedOverloadResolution: - Sema::SpecialMemberOverloadResult SMOR = - lookupCallFromSpecialMember(S, RD, CSM, Quals, ConstRHS); + Sema::SpecialMemberOverloadResult SMOR = lookupCallFromSpecialMember( + S, /*EvaluatingClass*/ nullptr, RD, CSM, Quals, ConstRHS); // The standard doesn't describe how to behave if the lookup is ambiguous. // We treat it as not making the member non-trivial, just like the standard @@ -7270,7 +7629,7 @@ if (SMOR.getKind() == Sema::SpecialMemberOverloadResult::Ambiguous) return true; - if (!SMOR.getMethod()) { + if (!SMOR.getFunction()) { assert(SMOR.getKind() == Sema::SpecialMemberOverloadResult::NoMemberOrDeleted); return false; @@ -7278,13 +7637,14 @@ // We deliberately don't check if we found a deleted special member. We're // not supposed to! + CXXMethodDecl *MD = cast(SMOR.getFunction()); if (Selected) - *Selected = SMOR.getMethod(); + *Selected = MD; if (TAH == Sema::TAH_ConsiderTrivialABI && (CSM == Sema::CXXCopyConstructor || CSM == Sema::CXXMoveConstructor)) - return SMOR.getMethod()->isTrivialForCall(); - return SMOR.getMethod()->isTrivial(); + return MD->isTrivialForCall(); + return MD->isTrivial(); } llvm_unreachable("unknown special method kind"); @@ -7432,9 +7792,15 @@ /// Determine whether a defaulted or deleted special member function is trivial, /// as specified in C++11 [class.ctor]p5, C++11 [class.copy]p12, /// C++11 [class.copy]p25, and C++11 [class.dtor]p5. -bool Sema::SpecialMemberIsTrivial(CXXMethodDecl *MD, CXXSpecialMember CSM, +bool Sema::SpecialMemberIsTrivial(FunctionDecl *FD, CXXSpecialMember CSM, TrivialABIHandling TAH, bool Diagnose) { - assert(!MD->isUserProvided() && CSM != CXXInvalid && "not special enough"); + assert(CSM != CXXInvalid && "not special enough"); + + if (CSM == CXXComparisonOperator) + return false; + + auto *MD = cast(FD); + assert(MD && !MD->isUserProvided() && "not special enough"); CXXRecordDecl *RD = MD->getParent(); @@ -7481,7 +7847,8 @@ } break; } - + case Sema::CXXComparisonOperator: + llvm_unreachable("handled elsewhere"); case CXXInvalid: llvm_unreachable("not a special member"); } @@ -9020,6 +9387,26 @@ return Info->getType(); } +QualType Sema::ComputeCommonComparisonType(ArrayRef Types, + SourceLocation Loc) { + using CCT = ComparisonCategoryType; + // Check that has been included. We also might need to reference + // 'std::strong_ordering::equal' in a explicitly-defaulted comparison + // operator. + QualType SOType = CheckComparisonCategoryType(CCT::StrongOrdering, Loc); + if (SOType.isNull()) + return QualType(); + // OK, we *should* have all the comparison category types available. Try to + // compute the common type. Failure to do so should indicate one of the + // specified types is not a CCT. + const ComparisonCategoryInfo *Info = + Context.CompCategories.computeCommonComparisonType(Types); + if (!Info) + return Context.VoidTy; + // Success! + return Info->getType(); +} + /// \brief Retrieve the special "std" namespace, which may require us to /// implicitly define the namespace. NamespaceDecl *Sema::getOrCreateStdNamespace() { @@ -10583,11 +10970,11 @@ SourceLocation Loc; Sema::ImplicitExceptionSpecification ExceptSpec; - SpecialMemberExceptionSpecInfo(Sema &S, CXXMethodDecl *MD, + SpecialMemberExceptionSpecInfo(Sema &S, FunctionDecl *FD, Sema::CXXSpecialMember CSM, Sema::InheritedConstructorInfo *ICI, SourceLocation Loc) - : SpecialMemberVisitor(S, MD, CSM, ICI), Loc(Loc), ExceptSpec(S) {} + : SpecialMemberVisitor(S, FD, CSM, ICI), Loc(Loc), ExceptSpec(S) {} bool visitBase(CXXBaseSpecifier *Base); bool visitField(FieldDecl *FD); @@ -10597,6 +10984,9 @@ void visitSubobjectCall(Subobject Subobj, Sema::SpecialMemberOverloadResult SMOR); + + void visitLookupResult(SourceLocation Loc, + Sema::SpecialMemberOverloadResult SMOR); }; } @@ -10607,7 +10997,7 @@ auto *BaseClass = cast(RT->getDecl()); Sema::SpecialMemberOverloadResult SMOR = lookupInheritedCtor(BaseClass); - if (auto *BaseCtor = SMOR.getMethod()) { + if (auto *BaseCtor = cast_or_null(SMOR.getFunction())) { visitSubobjectCall(Base, BaseCtor); return false; } @@ -10646,24 +11036,43 @@ void SpecialMemberExceptionSpecInfo::visitSubobjectCall( Subobject Subobj, Sema::SpecialMemberOverloadResult SMOR) { + visitLookupResult(getSubobjectLoc(Subobj), SMOR); +} + +void SpecialMemberExceptionSpecInfo::visitLookupResult( + SourceLocation CallLoc, Sema::SpecialMemberOverloadResult SMOR) { // Note, if lookup fails, it doesn't matter what exception specification we // choose because the special member will be deleted. - if (CXXMethodDecl *MD = SMOR.getMethod()) - ExceptSpec.CalledDecl(getSubobjectLoc(Subobj), MD); + if (FunctionDecl *FD = SMOR.getFunction()) + ExceptSpec.CalledDecl(CallLoc, FD); + if (SMOR.hasBuiltin()) { + assert(CSM == Sema::CXXComparisonOperator); + DeclAccessPair ConvDecl = SMOR.getBuiltinConversionDecl(); + if (auto *FD = cast_or_null(ConvDecl.getDecl())) + ExceptSpec.CalledDecl(CallLoc, FD); + } } static Sema::ImplicitExceptionSpecification ComputeDefaultedSpecialMemberExceptionSpec( - Sema &S, SourceLocation Loc, CXXMethodDecl *MD, Sema::CXXSpecialMember CSM, + Sema &S, SourceLocation Loc, FunctionDecl *FD, Sema::CXXSpecialMember CSM, Sema::InheritedConstructorInfo *ICI) { - CXXRecordDecl *ClassDecl = MD->getParent(); // C++ [except.spec]p14: // An implicitly declared special member function (Clause 12) shall have an // exception-specification. [...] - SpecialMemberExceptionSpecInfo Info(S, MD, CSM, ICI, Loc); - if (ClassDecl->isInvalidDecl()) + SpecialMemberExceptionSpecInfo Info(S, FD, CSM, ICI, Loc); + if (Info.Record->isInvalidDecl()) + return Info.ExceptSpec; + + if (CSM == Sema::CXXComparisonOperator && + FD->getOverloadedOperator() != OO_Spaceship) { + QualType ArgTy(Info.Record->getTypeForDecl(), Qualifiers::Const); + Sema::SpecialMemberOverloadResult SMOR = + S.LookupThreeWayComparison(Info.Record, ArgTy, Loc); + Info.visitLookupResult(Loc, SMOR); return Info.ExceptSpec; + } // C++1z [except.spec]p7: // [Look for exceptions thrown by] a constructor selected [...] to @@ -11232,6 +11641,29 @@ } }; +class RefOrThisBuilder : public ExprBuilder { + VarDecl *Var = nullptr; + QualType VarType; + +public: + Expr *build(Sema &S, SourceLocation Loc) const override { + if (Var) + return assertNotNull( + S.BuildDeclRefExpr(Var, VarType, VK_LValue, Loc).get()); + else { + Expr *E = assertNotNull(S.ActOnCXXThis(Loc).getAs()); + return assertNotNull(S.CreateBuiltinUnaryOp(Loc, UO_Deref, E).get()); + } + } + + void makeRefBuilder(VarDecl *xVar, QualType VarTy) { + Var = xVar; + VarType = VarTy; + } + + bool isThisBuilder() const { return Var == nullptr; } +}; + class CastBuilder: public ExprBuilder { const ExprBuilder &Builder; QualType Type; @@ -12057,8 +12489,8 @@ /*ConstArg*/false, /*VolatileArg*/false, /*RValueThis*/true, /*ConstThis*/false, /*VolatileThis*/false); - if (!SMOR.getMethod() || SMOR.getMethod()->isTrivial() || - !SMOR.getMethod()->isMoveAssignmentOperator()) + CXXMethodDecl *MD = cast_or_null(SMOR.getFunction()); + if (!MD || MD->isTrivial() || !MD->isMoveAssignmentOperator()) continue; if (BaseSpec->isVirtual()) { @@ -12089,7 +12521,7 @@ // Only walk over bases that have defaulted move assignment operators. // We assume that any user-provided move assignment operator handles // the multiple-moves-of-vbase case itself somehow. - if (!SMOR.getMethod()->isDefaulted()) + if (!SMOR.getFunction()->isDefaulted()) continue; // We're going to move the base classes of Base. Add them to the list. @@ -12686,6 +13118,349 @@ } } +namespace { +class ComparisonBuilder { + Sema &S; + FunctionDecl *CompareOperator; + SmallVector Statements; + BinaryOperatorKind Opc; + + Expr *LastCmpResult = nullptr; + +public: + SourceLocation Loc; + + ComparisonBuilder(Sema &S, FunctionDecl *CompareOperator) + : S(S), CompareOperator(CompareOperator) { + // Our location for everything implicitly-generated. + Loc = CompareOperator->getLocEnd().isValid() + ? CompareOperator->getLocEnd() + : CompareOperator->getLocation(); + Opc = BinaryOperator::getOverloadedOpcode( + CompareOperator->getOverloadedOperator()); + } + + BinaryOperatorKind getOpcode() const { return Opc; } + bool isThreeWay() const { return Opc == BO_Cmp; } + + bool BuildCompare(const ExprBuilder &LHSB, const ExprBuilder &RHSB) { + // Build a call to the comparison function for the specified LHS and RHS + // expressions. + Expr *LHS = LHSB.build(S, Loc); + Expr *RHS = RHSB.build(S, Loc); + ExprResult Cmp = S.BuildBinOp(nullptr, Loc, BO_Cmp, LHS, RHS); + if (Cmp.isInvalid()) + return Error(); + + if (!isThreeWay()) { + assert(!LastCmpResult); + ExprResult Res; + if (auto *Rewritten = dyn_cast(Cmp.get())) { + assert(Rewritten->getRewrittenKind() == CXXRewrittenExpr::Comparison); + CXXRewrittenExpr::ComparisonBits CompareBits = + Rewritten->getRewrittenInfo()->CompareBits; + if (CompareBits.IsSynthesized) + Res = S.BuildBinOp(nullptr, Loc, Opc, BuildZeroLiteral(), Cmp.get()); + } + if (Res.isUnset()) + Res = S.BuildBinOp(nullptr, Loc, Opc, Cmp.get(), BuildZeroLiteral()); + if (Res.isInvalid()) + return Error(); + Cmp = Res; + } + + // Stash the result of that comparison in case it's the final comparison, + // and get the previously stashed comparison if it exists. + Expr *LastCmp = Cmp.get(); + std::swap(LastCmp, LastCmpResult); + + if (!LastCmp) + return false; + + // Only defaulted three-way comparisons should be building more than one + // comparison. + assert(isThreeWay() && + "non-three-way comparison should only build one compare"); + + // If there was a previously stashed comparison, build an expression of the + // form: + // if (auto result = ; result != 0) + // return result; + StmtResult BranchOnCmp = BuildCompareEqualOrReturn(LastCmp); + if (BranchOnCmp.isInvalid()) + return Error(); + + // Success! + Statements.push_back(BranchOnCmp.getAs()); + return false; + } + + bool Finalize() { + // If we don't have a previously stashed comparison (ie the class for this + // comparison operator is empty), build a result representing equality + // (or inequality when the comparison operator is !=, <, or >) + if (!LastCmpResult) { + assert(isThreeWay()); + ExprResult EqualExpr = BuildReturnValueForEquality(); + if (EqualExpr.isInvalid()) + return Error(); + LastCmpResult = EqualExpr.get(); + } + + // Add a final return statement which returns the result of the last + // comparison or the newly generated result if there was none. + StmtResult Return = S.BuildReturnStmt(Loc, LastCmpResult); + if (Return.isInvalid()) + return Error(); + Statements.push_back(Return.getAs()); + + StmtResult Body; + { + Sema::CompoundScopeRAII CompoundScope(S); + Body = S.ActOnCompoundStmt(Loc, Loc, Statements, + /*isStmtExpr=*/false); + assert(!Body.isInvalid() && "Compound statement creation cannot fail"); + } + CompareOperator->setBody(Body.getAs()); + CompareOperator->markUsed(S.Context); + return false; + } + +private: + bool Error() { + CompareOperator->setInvalidDecl(); + return true; + } + + /// FIXME(EricWF): Document this + StmtResult BuildCompareEqualOrReturn(Expr *LastResult) { + assert(LastResult && "should not be null"); + assert(isThreeWay()); + + VarDecl *VD = BuildInventedVarDecl(LastResult); + if (!VD) + return StmtError(); + Expr *DRE = BuildDeclRef(VD); + + // Now attempt to build the full expression '(LHS <=> RHS) != 0' using the + // evaluated operand and the literal 0. + ExprResult CmpRes = + S.BuildBinOp(nullptr, Loc, BO_NE, DRE, BuildZeroLiteral()); + if (CmpRes.isInvalid()) + return StmtError(); + + StmtResult VDDeclStmt = + S.ActOnDeclStmt(S.ConvertDeclToDeclGroup(VD), Loc, Loc); + if (VDDeclStmt.isInvalid()) + return false; + + Expr *ReturnExpr = DRE; + + // If the last comparison returned not equal, we'll return it's value. + StmtResult Return = S.BuildReturnStmt(Loc, ReturnExpr); + if (Return.isInvalid()) + return StmtError(); + + if (cast(Return.get())->getNRVOCandidate() == VD) + VD->setNRVOVariable(true); + + return S.ActOnIfStmt(Loc, false, VDDeclStmt.getAs(), + S.ActOnCondition(nullptr, Loc, CmpRes.get(), + Sema::ConditionKind::Boolean), + Return.get(), SourceLocation(), nullptr); + } + + /// Build an expression which will be returned for comparisons which + /// result in equality. For three-way comparisons this is + /// 'std::strong_ordering::equal', otherwise it's a boolean with the value + /// 'true' if the specified opcode checks for equality (including <= or >=) + /// and false otherwise. + /// + /// Note: This should only be used when the struct being compared is empty. + /// Otherwise the result of the previous comparison should be returned. + ExprResult BuildReturnValueForEquality() { + assert(Opc == BO_Cmp); + + // FIXME: we need to emit diagnostics here + QualType RetTy = S.CheckComparisonCategoryType( + ComparisonCategoryType::StrongOrdering, Loc); + if (RetTy.isNull()) + return ExprError(); + + VarDecl *VD = + S.Context.CompCategories.getInfoForType(RetTy).getValueInfo( + ComparisonCategoryResult::Equal)->VD; + return S.BuildDeclRefExpr(VD, RetTy, VK_LValue, Loc); + } + + /// Build a variable declaration to store the result of a comparison + /// expression. This declaration will be inserted into the initializer clause + /// of an IfStmt so it can be used as both the condition and return statement + /// inside the IfStmt body. + VarDecl *BuildInventedVarDecl(Expr *Init) { + // Build a dummy variable to hold the result of our last comparison. + auto *VD = VarDecl::Create( + S.Context, CompareOperator, Loc, Loc, + &S.PP.getIdentifierTable().get("__cmp_res"), Init->getType(), + S.Context.getTrivialTypeSourceInfo(Init->getType(), Loc), SC_Auto); + S.CheckVariableDeclarationType(VD); + if (VD->isInvalidDecl()) + return nullptr; + + // Initialize the dummy variable using the last comparison expression as + // the initializer. + InitializedEntity Entity = InitializedEntity::InitializeVariable(VD); + InitializationKind Kind = InitializationKind::CreateForInit( + VD->getLocation(), /*DirectInit=*/true, Init); + InitializationSequence InitSeq(S, Entity, Kind, Init, + /*TopLevelOfInitList=*/false, + /*TreatUnavailableAsInvalid=*/false); + if (!InitSeq) + return nullptr; + ExprResult InitResult = InitSeq.Perform(S, Entity, Kind, Init); + if (InitResult.isInvalid()) + return nullptr; + InitResult = S.ActOnFinishFullExpr(InitResult.get()); + if (InitResult.isInvalid()) + return nullptr; + + S.AddInitializerToDecl(VD, InitResult.get(), /*DirectInit=*/false); + + S.CheckCompleteVariableDeclaration(VD); + return VD; + } + + Expr *BuildDeclRef(VarDecl *VD) { + ExprResult DRE = + S.BuildDeclRefExpr(VD, VD->getType(), ExprValueKind::VK_LValue, Loc); + assert(!DRE.isInvalid()); + return DRE.get(); + } + + Expr *BuildZeroLiteral() { + llvm::APInt I = + llvm::APInt::getNullValue(S.Context.getIntWidth(S.Context.IntTy)); + return IntegerLiteral::Create(S.Context, I, S.Context.IntTy, + SourceLocation()); + } +}; +} + +void Sema::DefineDefaultedComparisonOperator(SourceLocation CurrentLocation, + FunctionDecl *CompareOperator) { + assert(CompareOperator->isDefaultComparisonOperator() && + !CompareOperator->isDeleted() && + !CompareOperator->doesThisDeclarationHaveABody()); + assert(CompareOperator->isInlined() && + "defaulted comparison operator should be marked as implicitly " + "inline"); + if (CompareOperator->isInvalidDecl()) + return; + + const bool IsMethod = isa(CompareOperator); + + CXXRecordDecl *ClassDecl = + cast(CompareOperator->getLexicalParent()); + SynthesizedFunctionScope Scope(*this, CompareOperator); + // FIXME(EricWF): What does the spec say about implicit exception specs for + // defaulted comparison operators? + + // Add a context note for diagnostics produced after this point. + Scope.addContextNote(CurrentLocation); + + auto CheckParam = [&](ParmVarDecl *PD) { + QualType RefTy = + PD->getType()->getAs()->getPointeeType(); + auto Quals = RefTy.getQualifiers(); + Quals.removeConst(); + assert(!Quals && "Bad argument type for defaulted comparison"); + return RefTy; + }; + + RefOrThisBuilder LHSRef; + if (!IsMethod) { + ParmVarDecl *VD = CompareOperator->getParamDecl(0); + LHSRef.makeRefBuilder(VD, CheckParam(VD)); + } + + // The parameter for the "other" object. + ParmVarDecl *RHSPD = CompareOperator->getParamDecl(IsMethod ? 0 : 1); + QualType RHSRefType = CheckParam(RHSPD); + // Builds a reference to the "other" object. + RefBuilder RHSRef(RHSPD, RHSRefType); + + auto CallQuals = + CompareOperator->getType()->getAs()->getTypeQuals(); + + ComparisonBuilder Builder(*this, CompareOperator); + bool Invalid = false; + if (Builder.isThreeWay()) { + // compare base classes + for (auto &Base : ClassDecl->bases()) { + // C++2a [class.spaceship]p1: + // It is unspecified whether virtual base class subobjects are compared + // more than once. + + QualType BaseType = Base.getType(); + if (!BaseType->isRecordType()) { + Invalid = true; + continue; + } + + CXXCastPath BasePath; + BasePath.push_back(&Base); + + // Implicitly cast "this" to the appropriately-qualified base type. + CastBuilder LHS(LHSRef, Context.getCVRQualifiedType(BaseType, CallQuals), + VK_LValue, BasePath); + + // Construct the "from" expression, which is an implicit cast to the + // appropriately-qualified base type. + CastBuilder RHS(RHSRef, BaseType, VK_LValue, BasePath); + + if (Builder.BuildCompare(LHS, RHS)) + return; + } + + // Assign non-static members. + for (auto *Field : ClassDecl->fields()) { + if (Field->isInvalidDecl()) { + Invalid = true; + continue; + } + + // Suppress assigning zero-width bitfields. + if (Field->isZeroLengthBitField(Context)) + continue; + + // Build references to the field in the object we're copying from and to. + LookupResult MemberLookup(*this, Field->getDeclName(), Builder.Loc, + LookupMemberName); + MemberLookup.addDecl(Field); + MemberLookup.resolveKind(); + MemberBuilder RHS(RHSRef, RHSRefType, /*IsArrow=*/false, MemberLookup); + MemberBuilder LHS(LHSRef, RHSRefType, /*IsArrow=*/false, MemberLookup); + + if (Builder.BuildCompare(LHS, RHS)) + return; + } + } else { + if (Builder.BuildCompare(LHSRef, RHSRef)) + return; + } + if (Invalid) { + CompareOperator->setInvalidDecl(); + return; + } + + if (Builder.Finalize()) + return; + + if (ASTMutationListener *L = getASTMutationListener()) { + L->CompletedImplicitDefinition(CompareOperator); + } +} + /// \brief Determine whether the given list arguments contains exactly one /// "real" (non-default) argument. static bool hasOneRealArgument(MultiExprArg Args) { @@ -14337,7 +15112,7 @@ FD = FTD->getTemplatedDecl(); else FD = cast(ND); - + assert(!FD->isExplicitlyDefaulted()); // C++11 [dcl.fct.default]p4: If a friend declaration specifies a // default argument expression, that declaration shall be a definition // and shall be the only declaration of the function or function @@ -14434,17 +15209,77 @@ Fn->setDeletedAsWritten(); } +bool Sema::checkDefaultedComparisonOperatorDecl(SourceLocation DefaultLoc, + Decl *Dcl) { + if (!Dcl) + return false; + + // If we don't have a defaulted comparison decl, return. + auto *FD = Dcl->getAsFunction(); + if (!FD || !FD->isDefaultComparisonOperator(true)) + return false; + + // Require defaulted comparison decls to be the first declaration for a + // given function. + Decl *CanonDcl = Dcl->getCanonicalDecl(); + if (CanonDcl != Dcl) { + // FIXME(EricWF): IDK if this is strictly required by the standard, + // but I don't want to handle it now. + // TODO: Handle this better? + Diag(FD->getLocation(), diag::err_defaulted_decl_not_first); + Diag(CanonDcl->getLocation(), diag::note_previous_declaration) + << CanonDcl->getSourceRange(); + Dcl->setInvalidDecl(); + return true; + } + + // Dig up the record decl in which this function was declared. If it's a + // defaulted non-method comparison operator, the canonical decls lexical + // context will provide this. + CXXRecordDecl *RD; + if (auto *MD = dyn_cast(FD)) + RD = MD->getParent(); + else + RD = dyn_cast(FD->getLexicalParent()); + if (!RD) { + Diag(FD->getLocation(), diag::err_defaulted_comparison_operator_decl_scope); + Dcl->setInvalidDecl(); + return true; + } + + // defaulted comparison operators are not allowed to be template functions. + if (isa(Dcl)) { + Diag(FD->getLocStart(), diag::err_defaulted_comparison_operator_template); + Dcl->setInvalidDecl(); + return true; + } + + // non-valid binary operators get diagnosed elsewhere. For now just ensure + // we skip further checking. + unsigned ExpectedParams = isa(FD) ? 1 : 2; + if (ExpectedParams != FD->getNumParams()) + return true; + + FD->setImplicitlyInline(); + return false; +} + void Sema::SetDeclDefaulted(Decl *Dcl, SourceLocation DefaultLoc) { - CXXMethodDecl *MD = dyn_cast_or_null(Dcl); + // If this is the first time we've seen a friend decl for a defaulted + // comparison operator, we need to do some initial checking on the + // declaration. + if (checkDefaultedComparisonOperatorDecl(DefaultLoc, Dcl)) + return; - if (MD) { + if (auto *MD = dyn_cast_or_null(Dcl)) { if (MD->getParent()->isDependentType()) { MD->setDefaulted(); MD->setExplicitlyDefaulted(); return; } - CXXSpecialMember Member = getSpecialMember(MD); + CXXSpecialMember Member = + getSpecialMember(MD, /*IsExplicitlyDefaulted*/ true); if (Member == CXXInvalid) { if (!MD->isInvalidDecl()) Diag(DefaultLoc, diag::err_default_special_members); @@ -14457,7 +15292,7 @@ // Unset that we will have a body for this function. We might not, // if it turns out to be trivial, and we don't need this marking now // that we've marked it as defaulted. - MD->setWillHaveBody(false); + MD->setWillHaveBody(Member == CXXComparisonOperator); // If this definition appears within the record, do the checking when // the record is complete. @@ -14473,10 +15308,31 @@ if (Primary->getCanonicalDecl()->isDefaulted()) return; - CheckExplicitlyDefaultedSpecialMember(MD); + CheckExplicitlyDefaultedMember(MD); if (!MD->isInvalidDecl()) DefineImplicitSpecialMember(*this, MD, DefaultLoc); + } else if (auto *FD = dyn_cast_or_null(Dcl)) { + if (FD->isDefaultComparisonOperator(true)) { + CXXRecordDecl *Parent = dyn_cast(FD->getLexicalParent()); + assert(!isa(Dcl)); + assert(Parent); + if (Parent->isDependentType()) { + FD->setDefaulted(); + FD->setExplicitlyDefaulted(); + return; + } + FD->setDefaulted(); + FD->setExplicitlyDefaulted(); + FD->setWillHaveBody(true); + + CheckExplicitlyDefaultedMember(FD); + + if (!FD->isInvalidDecl() && !FD->isDeleted()) + DefineImplicitSpecialMember(*this, FD, DefaultLoc); + } else { + Diag(DefaultLoc, diag::err_default_special_members); + } } else { Diag(DefaultLoc, diag::err_default_special_members); } Index: lib/Sema/SemaExceptionSpec.cpp =================================================================== --- lib/Sema/SemaExceptionSpec.cpp +++ lib/Sema/SemaExceptionSpec.cpp @@ -205,7 +205,7 @@ // Compute or instantiate the exception specification now. if (SourceFPT->getExceptionSpecType() == EST_Unevaluated) - EvaluateImplicitExceptionSpec(Loc, cast(SourceDecl)); + EvaluateImplicitExceptionSpec(Loc, cast(SourceDecl)); else InstantiateExceptionSpec(Loc, SourceDecl); @@ -419,10 +419,10 @@ FixItLoc = getLocForEndOfToken(FTLoc.getLocalRangeEnd()); } - if (FixItLoc.isInvalid()) + if (FixItLoc.isInvalid()) { Diag(New->getLocation(), DiagID) << New << OS.str(); - else { + } else { Diag(New->getLocation(), DiagID) << New << OS.str() << FixItHint::CreateInsertion(FixItLoc, " " + OS.str().str()); Index: lib/Sema/SemaExpr.cpp =================================================================== --- lib/Sema/SemaExpr.cpp +++ lib/Sema/SemaExpr.cpp @@ -103,7 +103,8 @@ // Try to diagnose why this special member function was implicitly // deleted. This might fail, if that reason no longer applies. CXXSpecialMember CSM = getSpecialMember(Method); - if (CSM != CXXInvalid) + // FIXME(EricWF): Handle CXXComparisonOperator + if (CSM != CXXInvalid && CSM != CXXComparisonOperator) ShouldDeleteSpecialMember(Method, CSM, nullptr, /*Diagnose=*/true); return; @@ -9799,8 +9800,6 @@ ExprResult &LHS, ExprResult &RHS, SourceLocation Loc) { - using CCT = ComparisonCategoryType; - QualType LHSType = LHS.get()->getType(); QualType RHSType = RHS.get()->getType(); // Dig out the original argument type and expression before implicit casts @@ -9869,20 +9868,9 @@ assert(!Type.isNull() && "composite type for <=> has not been set"); - auto TypeKind = [&]() { - if (const ComplexType *CT = Type->getAs()) { - if (CT->getElementType()->hasFloatingRepresentation()) - return CCT::WeakEquality; - return CCT::StrongEquality; - } - if (Type->isIntegralOrEnumerationType()) - return CCT::StrongOrdering; - if (Type->hasFloatingRepresentation()) - return CCT::PartialOrdering; - llvm_unreachable("other types are unimplemented"); - }(); - - return S.CheckComparisonCategoryType(TypeKind, Loc); + return S.CheckComparisonCategoryType( + ComparisonCategories::computeComparisonTypeForBuiltin(Type).getValue(), + Loc); } static QualType checkArithmeticOrEnumeralCompare(Sema &S, ExprResult &LHS, @@ -9974,38 +9962,16 @@ assert(getLangOpts().CPlusPlus); assert(Context.hasSameType(LHS.get()->getType(), RHS.get()->getType())); - QualType CompositeTy = LHS.get()->getType(); - assert(!CompositeTy->isReferenceType()); - - auto buildResultTy = [&](ComparisonCategoryType Kind) { - return CheckComparisonCategoryType(Kind, Loc); - }; - - // C++2a [expr.spaceship]p7: If the composite pointer type is a function - // pointer type, a pointer-to-member type, or std::nullptr_t, the - // result is of type std::strong_equality - if (CompositeTy->isFunctionPointerType() || - CompositeTy->isMemberPointerType() || CompositeTy->isNullPtrType()) - // FIXME: consider making the function pointer case produce - // strong_ordering not strong_equality, per P0946R0-Jax18 discussion - // and direction polls - return buildResultTy(ComparisonCategoryType::StrongEquality); - - // C++2a [expr.spaceship]p8: If the composite pointer type is an object - // pointer type, p <=> q is of type std::strong_ordering. - if (CompositeTy->isPointerType()) { - // P0946R0: Comparisons between a null pointer constant and an object - // pointer result in std::strong_equality - if (LHSIsNull != RHSIsNull) - return buildResultTy(ComparisonCategoryType::StrongEquality); - return buildResultTy(ComparisonCategoryType::StrongOrdering); - } + Optional CompType = + ComparisonCategories::computeComparisonTypeForBuiltin( + LHS.get()->getType(), LHSIsNull != RHSIsNull); // C++2a [expr.spaceship]p9: Otherwise, the program is ill-formed. // TODO: Extend support for operator<=> to ObjC types. + if (!CompType) return InvalidOperands(Loc, LHS, RHS); + return CheckComparisonCategoryType(CompType.getValue(), Loc); }; - if (!IsRelational && LHSIsNull != RHSIsNull) { bool IsEquality = Opc == BO_EQ; if (RHSIsNull) @@ -14197,7 +14163,8 @@ static bool isImplicitlyDefinableConstexprFunction(FunctionDecl *Func) { CXXMethodDecl *MD = dyn_cast(Func); return Func->isConstexpr() && - (Func->isImplicitlyInstantiable() || (MD && !MD->isUserProvided())); + (Func->isImplicitlyInstantiable() || (MD && !MD->isUserProvided()) || + Func->isDefaultComparisonOperator()); } /// \brief Mark a function referenced, and check whether it is odr-used @@ -14287,13 +14254,16 @@ MarkVTableUsed(Loc, Destructor->getParent()); } else if (CXXMethodDecl *MethodDecl = dyn_cast(Func)) { if (MethodDecl->isOverloadedOperator() && - MethodDecl->getOverloadedOperator() == OO_Equal) { + (MethodDecl->getOverloadedOperator() == OO_Equal || + MethodDecl->isDefaultComparisonOperator())) { MethodDecl = cast(MethodDecl->getFirstDecl()); if (MethodDecl->isDefaulted() && !MethodDecl->isDeleted()) { if (MethodDecl->isCopyAssignmentOperator()) DefineImplicitCopyAssignment(Loc, MethodDecl); else if (MethodDecl->isMoveAssignmentOperator()) DefineImplicitMoveAssignment(Loc, MethodDecl); + else if (MethodDecl->isDefaultComparisonOperator()) + DefineDefaultedComparisonOperator(Loc, MethodDecl); } } else if (isa(MethodDecl) && MethodDecl->getParent()->isLambda()) { @@ -14305,7 +14275,8 @@ DefineImplicitLambdaToFunctionPointerConversion(Loc, Conversion); } else if (MethodDecl->isVirtual() && getLangOpts().AppleKext) MarkVTableUsed(Loc, MethodDecl->getParent()); - } + } else if (Func->isDefaultComparisonOperator()) + DefineDefaultedComparisonOperator(Loc, Func); // Recursive functions should be marked when used from another function. // FIXME: Is this really right? Index: lib/Sema/SemaLookup.cpp =================================================================== --- lib/Sema/SemaLookup.cpp +++ lib/Sema/SemaLookup.cpp @@ -2857,6 +2857,8 @@ bool RValueThis, bool ConstThis, bool VolatileThis) { + assert(SM != CXXComparisonOperator && + "lookups for comparisons should be handled elsewhere"); assert(CanDeclareSpecialMemberFunction(RD) && "doing special member lookup into record that isn't fully complete"); RD = RD->getDefinition(); @@ -2896,7 +2898,7 @@ DeclareImplicitDestructor(RD); CXXDestructorDecl *DD = RD->getDestructor(); assert(DD && "record without a destructor"); - Result->setMethod(DD); + Result->setFunction(DD); Result->setKind(DD->isDeleted() ? SpecialMemberOverloadResult::NoMemberOrDeleted : SpecialMemberOverloadResult::Success); @@ -2981,7 +2983,7 @@ // destructor. assert(SM == CXXDefaultConstructor && "lookup for a constructor or assignment operator was empty"); - Result->setMethod(nullptr); + Result->setFunction(nullptr); Result->setKind(SpecialMemberOverloadResult::NoMemberOrDeleted); return *Result; } @@ -3028,22 +3030,22 @@ OverloadCandidateSet::iterator Best; switch (OCS.BestViableFunction(*this, LookupLoc, Best)) { case OR_Success: - Result->setMethod(cast(Best->Function)); + Result->setFunction(Best->Function); Result->setKind(SpecialMemberOverloadResult::Success); break; case OR_Deleted: - Result->setMethod(cast(Best->Function)); + Result->setFunction(Best->Function); Result->setKind(SpecialMemberOverloadResult::NoMemberOrDeleted); break; case OR_Ambiguous: - Result->setMethod(nullptr); + Result->setFunction(nullptr); Result->setKind(SpecialMemberOverloadResult::Ambiguous); break; case OR_No_Viable_Function: - Result->setMethod(nullptr); + Result->setFunction(nullptr); Result->setKind(SpecialMemberOverloadResult::NoMemberOrDeleted); break; } @@ -3057,7 +3059,7 @@ LookupSpecialMember(Class, CXXDefaultConstructor, false, false, false, false, false); - return cast_or_null(Result.getMethod()); + return cast_or_null(Result.getFunction()); } /// \brief Look up the copying constructor for the given class. @@ -3069,7 +3071,7 @@ LookupSpecialMember(Class, CXXCopyConstructor, Quals & Qualifiers::Const, Quals & Qualifiers::Volatile, false, false, false); - return cast_or_null(Result.getMethod()); + return cast_or_null(Result.getFunction()); } /// \brief Look up the moving constructor for the given class. @@ -3079,7 +3081,7 @@ LookupSpecialMember(Class, CXXMoveConstructor, Quals & Qualifiers::Const, Quals & Qualifiers::Volatile, false, false, false); - return cast_or_null(Result.getMethod()); + return cast_or_null(Result.getFunction()); } /// \brief Look up the constructors for the given class. @@ -3113,7 +3115,7 @@ ThisQuals & Qualifiers::Const, ThisQuals & Qualifiers::Volatile); - return Result.getMethod(); + return cast_or_null(Result.getFunction()); } /// \brief Look up the moving assignment operator for the given class. @@ -3129,7 +3131,7 @@ ThisQuals & Qualifiers::Const, ThisQuals & Qualifiers::Volatile); - return Result.getMethod(); + return cast_or_null(Result.getFunction()); } /// \brief Look for the destructor of the given class. @@ -3140,8 +3142,106 @@ /// \returns The destructor for this class. CXXDestructorDecl *Sema::LookupDestructor(CXXRecordDecl *Class) { return cast(LookupSpecialMember(Class, CXXDestructor, - false, false, false, - false, false).getMethod()); + false, false, false, false, + false) + .getFunction()); +} + +Sema::SpecialMemberOverloadResult +Sema::LookupThreeWayComparison(CXXRecordDecl *RD, QualType ArgType, + SourceLocation Loc) { + using ResultKind = SpecialMemberOverloadResultEntry::Kind; + assert(!ArgType->isReferenceType() && + "expression cannot have reference types"); + + // Add a const qualifier before constructing the node ID to prevent + // unnecessary duplicates. + ArgType.addConst(); + + // FIXME(EricWF): Before we do expensive lookup, check if we have + // non-overloadable types (ie arithmetic, pointer, member pointer, ect) + // In these cases we can deduce the builtin type without performing + // overload resolution. + + // FIXME(EricWF): It's not clear if we should even be caching lookups for + // defaulted comparison operators, since unlike special members, the lookup + // results can change. For now we add the record decl doing the lookup to + // avoid this. + llvm::FoldingSetNodeID ID; + ID.AddPointer(RD); + // FIXME(EricWF): Should we remove the quals before canonicalizing the type? + ID.AddPointer(ArgType.getAsOpaquePtr()); + ID.AddInteger(CXXComparisonOperator); + + void *InsertPoint; + SpecialMemberOverloadResultEntry *Result = + SpecialMemberCache.FindNodeOrInsertPos(ID, InsertPoint); + + // This was already cached + if (Result) + return *Result; + + Result = BumpAlloc.Allocate(); + Result = new (Result) SpecialMemberOverloadResultEntry(ID); + SpecialMemberCache.InsertNode(Result, InsertPoint); + + DeclarationName Name = + Context.DeclarationNames.getCXXOperatorName(OO_Spaceship); + + OpaqueValueExpr FakeArg(Loc, ArgType, VK_LValue); + Expr *Args[2] = {&FakeArg, &FakeArg}; + + // FIXME(EricWF): Do this earlier, and pass in the result. + UnresolvedSet<16> Functions; + LookupOverloadedOperatorName(OO_Spaceship, getCurScope(), ArgType, ArgType, + Functions); + + OverloadCandidateSet CandidateSet(Loc, OverloadCandidateSet::CSK_Operator); + // Add the candidates from the given function set. + AddFunctionCandidates(Functions, Args, CandidateSet); + + // Add operator candidates that are member functions. + AddMemberOperatorCandidates(OO_Spaceship, Loc, Args, CandidateSet); + + // FIXME(EricWF): Should we be performing ADL here? Seems likely. + AddArgumentDependentLookupCandidates(Name, Loc, Args, + /*ExplicitTemplateArgs*/ nullptr, + CandidateSet); + // Add builtin operator candidates. + AddBuiltinOperatorCandidates(OO_Spaceship, Loc, Args, CandidateSet); + + // FIXME(EricWF): Ignore rewritten candidates for now. + + OverloadCandidateSet::iterator Best; + switch (CandidateSet.BestViableFunction(*this, Loc, Best)) { + case OR_Success: { + OverloadCandidate &Ovl = *Best; + Result->setKind(ResultKind::Success); + if (Ovl.Function) + Result->setFunction(Ovl.Function); + else { + assert(Ovl.getNumParams() == 2 && + Context.hasSameType(Ovl.BuiltinParamTypes[0], + Ovl.BuiltinParamTypes[1])); + DeclAccessPair ConvDecl; + ConvDecl.set(nullptr, AS_none); + + ImplicitConversionSequence ICS = Ovl.Conversions[0]; + if (ICS.isUserDefined()) + ConvDecl = ICS.UserDefined.FoundConversionFunction; + Result->setBuiltinInfo(Ovl.BuiltinParamTypes[0], ConvDecl); + } + break; + } + case OR_Ambiguous: + Result->setKind(ResultKind::Ambiguous); + break; + case OR_Deleted: + case OR_No_Viable_Function: + Result->setKind(ResultKind::NoMemberOrDeleted); + break; + } + return *Result; } /// LookupLiteralOperator - Determine which literal operator should be used for Index: lib/Sema/SemaOverload.cpp =================================================================== --- lib/Sema/SemaOverload.cpp +++ lib/Sema/SemaOverload.cpp @@ -12570,8 +12570,6 @@ Expr *Rewritten = RewrittenRes.get(); // Create a dummy expression representing the original expression as written. - // FIXME(EricWF): This doesn't actually really represent the expression as - // written, because it may not result in a to a builtin operator. Expr *Original = new (S.Context) BinaryOperator(OpaqueValueExpr::Create(S.Context, Args[0]), OpaqueValueExpr::Create(S.Context, Args[1]), Opc, Index: lib/Sema/SemaTemplateInstantiateDecl.cpp =================================================================== --- lib/Sema/SemaTemplateInstantiateDecl.cpp +++ lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -1853,7 +1853,11 @@ PrincipalDecl->isInIdentifierNamespace(Decl::IDNS_Ordinary)) PrincipalDecl->setNonMemberOperator(); - assert(!D->isDefaulted() && "only methods should be defaulted"); + if (D->isExplicitlyDefaulted()) { + assert(D->isDefaultComparisonOperator() && + "Only default comparison operators may be defaulted"); + SemaRef.SetDeclDefaulted(Function, Function->getLocation()); + } return Function; } Index: test/CodeGenCXX/cxx2a-compare.cpp =================================================================== --- test/CodeGenCXX/cxx2a-compare.cpp +++ test/CodeGenCXX/cxx2a-compare.cpp @@ -196,5 +196,3 @@ } } // namespace RewrittenTest - - Index: test/CodeGenCXX/cxx2a-defaulted-compare.cpp =================================================================== --- /dev/null +++ test/CodeGenCXX/cxx2a-defaulted-compare.cpp @@ -0,0 +1,98 @@ +// RUN: %clang_cc1 -std=c++2a -emit-llvm %s -o - -triple %itanium_abi_triple | \ +// RUN: FileCheck %s \ +// RUN: '-DWE="class.std::__1::weak_equality"' \ +// RUN: '-DSO="class.std::__1::strong_ordering"' \ +// RUN: '-DSE="class.std::__1::strong_equality"' \ +// RUN: '-DPO="class.std::__1::partial_ordering"' \ +// RUN: -DEQ=0 -DLT=-1 -DGT=1 -DUNORD=-127 -DNE=1 \ +// RUN: -DOpaqueBaseSS=_ZNK10OpaqueBasessERKS_ \ +// RUN: -DOpaqueMemSS=_ZNK9OpaqueMemssERKS_ \ +// RUN: -DOpaqueFnSS=_ZssRK8OpaqueFnS1_ \ +// RUN: -DSO_NE=_ZNSt3__1neENS_15strong_orderingEMNS_19_CmpUnspecifiedTypeEFvvE \ +// RUN: -DSO_LT=_ZNSt3__1ltENS_15strong_orderingEMNS_19_CmpUnspecifiedTypeEFvvE + +#include "Inputs/std-compare.h" + +// CHECK-DAG: $_ZNK2T1ssERKS_ = comdat any +// CHECK-DAG: $_ZNK2T1ltERKS_ = comdat any + +template +void use_cmp(T const &LHS, T const &RHS) { + (void)(LHS <=> RHS); + (void)(LHS < RHS); +} +struct OpaqueBase { + std::strong_ordering operator<=>(OpaqueBase const &) const; +}; +struct OpaqueMem { + std::strong_ordering operator<=>(OpaqueMem const &) const; +}; +struct OpaqueFn { +}; +std::strong_ordering operator<=>(OpaqueFn const &, OpaqueFn const &); + +struct T1 : OpaqueBase { + OpaqueMem first; + OpaqueFn second; + // CHECK-LABEL: define linkonce_odr i8 @_ZNK2T1ssERKS_( + auto operator<=>(T1 const &other) const = default; + // CHECK: call i8 @[[OpaqueBaseSS]] + // CHECK: %[[BASE_NE_ZERO:.*]] = call zeroext i1 @[[SO_NE]] + // CHECK: br i1 %[[BASE_NE_ZERO]], label %if.then, label %if.end + // CHECK: if.then: + // CHECK-NEXT: br label %return + // CHECK: if.end: + // CHECK: call i8 @[[OpaqueMemSS]] + // CHECK: %[[MEM_NE_ZERO:.*]] = call zeroext i1 @[[SO_NE]] + // CHECK: br i1 %[[MEM_NE_ZERO]], label %[[THEN2:.*]], label %[[END2:.*]] + // CHECK: [[THEN2]]: + // CHECK-NEXT: br label %return + // CHECK: [[END2]]: + // CHECK: call i8 @[[OpaqueFnSS]] + // CHECK-NOT: [[SO_NE]] + // CHECK: br label %return + // CHECK: return: + // CHECK-NEXT: %[[TMP:.*]] = getelementptr inbounds %[[SO]], %[[SO]]* %retval + // CHECK-NEXT: %[[TMP2:.*]] = load i8, i8* %[[TMP]] + // CHECK-NEXT: ret i8 %[[TMP2]] + + // CHECK-LABEL: define linkonce_odr zeroext i1 @_ZNK2T1ltERKS_( + bool operator<(T1 const &other) const = default; + // CHECK: call i8 @_ZNK2T1ssERKS_( + // CHECK: %[[RES:.*]] = call zeroext i1 @[[SO_LT]] + // CHECK-NEXT: ret i1 %[[RES]] +}; +template void use_cmp(T1 const &, T1 const &); + + +struct Empty { + // CHECK-LABEL: define linkonce_odr i8 @_ZNK5EmptyssERKS_ + auto operator<=>(Empty const&) const = default; + // CHECK: %1 = bitcast %[[SO]]* %retval to i8* + // CHECK-NEXT: call void @llvm.memcpy + // CHECK-SAME: %1 + // CHECK-SAME: @_ZNSt3__115strong_ordering5equalE + // CHECK: %[[TMP:.*]] = getelementptr inbounds %[[SO]], %[[SO]]* %retval + // CHECK-NEXT: %2 = load i8, i8* %[[TMP]] + // CHECK-NEXT: ret i8 %2 + + // CHECK-LABEL: define linkonce_odr zeroext i1 @_ZNK5EmptyltERKS_ + bool operator<(Empty const&) const = default; + // CHECK: call i8 @_ZNK5EmptyssERKS_( + // CHECK: %[[RES:.*]] = call zeroext i1 @[[SO_LT]] + // CHECK-NEXT: ret i1 %[[RES]] +}; +template void use_cmp(Empty const&, Empty const&); + +struct OneMem { + OpaqueMem first; + + // CHECK-LABEL: define linkonce_odr i8 @_ZssRK6OneMemS1_( + friend auto operator<=>(OneMem const&, OneMem const&) = default; + // CHECK: %call = call i8 @[[OpaqueMemSS]] + // CHECK-NEXT: %[[TMP:.*]] = getelementptr inbounds %[[SO]], %[[SO]]* %retval + // CHECK-NEXT: store i8 %call, i8* %[[TMP]] + // CHECK-NOT: br + // CHECK: ret +}; +template void use_cmp(OneMem const&, OneMem const&); Index: test/SemaCXX/compare-cxx2a.cpp =================================================================== --- test/SemaCXX/compare-cxx2a.cpp +++ test/SemaCXX/compare-cxx2a.cpp @@ -210,12 +210,14 @@ using MemFnTy2 = void (Class::*)(); using MemFnTy3 = void (Class2::*)() const; using MemDataTy = long(Class::*); - +template +struct Printer; void test_nullptr(int *x, FnTy *fp, MemFnTy memp, MemDataTy memdp) { auto r1 = (nullptr <=> nullptr); ASSERT_EXPR_TYPE(r1, std::strong_equality); auto r2 = (nullptr <=> x); + ASSERT_EXPR_TYPE(r2, std::strong_equality); auto r3 = (fp <=> nullptr); @@ -536,3 +538,408 @@ template auto test(ThreeWay const &, ThreeWay const &); } // namespace TestRewrittenTemplate + +namespace IllFormedSpecialMemberTest { + +struct T1 { + // expected-error@+1 {{the parameter for this explicitly-defaulted comparison operator must have type 'const IllFormedSpecialMemberTest::T1 &', but parameter has type 'IllFormedSpecialMemberTest::T1'}} + void operator<=>(T1) const = default; + // expected-error@+1 {{explicitly-defaulted comparison operator must have type}} + void operator<=>(T1 &) const = default; + // expected-error@+1 {{explicitly-defaulted comparison operator must have type}} + void operator<=>(int) const = default; + // expected-error@+1 {{overloaded 'operator<=>' must be a binary operator (has 1 parameter)}} + void operator<=>() const = default; + // expected-error@+1 {{overloaded 'operator<=>' must be a binary operator (has 3 parameters)}} + void operator<=>(T1 const &, T1 const &) = default; + + // expected-error@+2 {{an explicitly-defaulted comparison operator cannot be a template}} + template + void operator<=>(U const &) = default; + + // FIXME(EricWF): This should delete itself. + void operator<=>(T1 const &) const = default; // OK +}; +// expected-error@+1 {{definition of explicitly defaulted comparison operator}} +void T1::operator<=>(T1 const &) const = default; + +struct T2 { + // expected-error@+1 {{overloaded 'operator<=>' must be a binary operator (has 1 parameter)}} + friend void operator<=>(T2 const &) = default; + // expected-error@+1 {{overloaded 'operator<=>' must be a binary operator (has 3 parameters)}} + friend void operator<=>(T2 const &, T2 const &, T2 const &) = default; + + // FIXME(EricWF): Improve this diagnostic to point to the parameter. + // expected-error@+2 {{explicitly-defaulted comparison operator must have type}} + // expected-error@+1 {{explicitly-defaulted comparison operator must have type}} + friend void operator<=>(T2, T2) = default; + // expected-error@+1 {{explicitly-defaulted comparison operator must have type}} + friend void operator<=>(T2 &, T2 const &) = default; + // expected-error@+1 {{explicitly-defaulted comparison operator must have type}} + friend void operator<=>(T2 const &, T2 const volatile &) = default; + + friend auto operator<=>(T2 const &, T2 const &) = default; //OK + + // expected-error@+2 {{an explicitly-defaulted comparison operator cannot be a template}} + template + friend void operator<=>(Tp const &, Tp const &) = default; +}; + +struct T3; +// expected-note@+1 {{previous declaration is here}} +auto operator<=>(T3 const &, T3 const &); +struct T3 { + // expected-error@+1 {{defaulted definition must be first declaration}} + friend auto operator<=>(T3 const &, T3 const &) = default; + + // expected-error@+1 {{explicitly-defaulted comparison operator must return 'bool'}} + int operator<=(T3 const &) const = default; + // expected-error@+1 {{explicitly-defaulted comparison operator must return 'bool'}} + bool &operator>=(T3 const &) const = default; + // expected-error@+1 {{explicitly-defaulted comparison operator must return 'bool'}} + friend void operator<=(T3 const &, T3 const &) = default; +}; + +} // namespace IllFormedSpecialMemberTest + +namespace DeletedMemberTest { +struct T1 { + bool operator<(T1 const &) const = default; // expected-note {{explicitly defaulted function was implicitly deleted here}} + bool operator>(T1 const &) const = default; + bool operator==(T1 const &) const = default; + bool operator!=(T1 const &) const = default; + + friend bool operator<(T1 const &, T1 const &) = default; + friend bool operator!=(T1 const &, T1 const &) = default; +}; +auto R1 = &T1::operator<; // expected-error {{attempt to use a deleted function}} + +struct T2 { + bool operator<(T2 const &) const = default; // expected-note {{deleted here}} + bool operator==(T2 const &) const = default; + friend std::strong_equality operator<=>(T2 const &, T2 const &) = default; +}; +auto R3 = &T2::operator<; // expected-error {{attempt to use a deleted function}} +auto R4 = &T2::operator==; // OK + +// expected-error@+1 {{definition of explicitly defaulted comparison operator}} +bool T2::operator<(T2 const &) const { + return false; +} + +struct T3 { +private: + auto operator<=>(T3 const &) const = default; +}; +struct T4 : T3 { + bool operator<(T4 const &) const = default; // expected-note {{deleted here}} +}; +auto R5 = &T4::operator<; // expected-error {{attempt to use a deleted function}} + +struct T5 { +private: + auto operator<=>(T5 const &) const = default; + +public: + bool operator<(T5 const &) const = default; +}; +auto R6 = &T5::operator<; // OK + +// FIXME(EricWF): Should T7's defaulted comparison operator be deleted here? +// We use the user defined conversion to 'int', but it's not accessible. This +// results in diagnostics when synthesizing the definition. +struct T6 { + friend struct T8; +private: + operator int() const; +}; +struct T7 : T6 { + bool operator<(T7 const &) const = default; // expected-note {{deleted here}} +}; + +auto R7 = &T7::operator<; // expected-error {{attempt to use a deleted function}} +struct T8 : T6 { + bool operator<(T8 const &) const = default; +}; +auto R8 = &T8::operator<; // OK + +} // namespace DeletedMemberTest + +namespace TestCommonCategoryType { +using SO = std::strong_ordering; +using WO = std::weak_ordering; +using PO = std::partial_ordering; +using SE = std::strong_equality; +using WE = std::weak_equality; + +template +struct Tag { using type = T; }; +template +struct CmpResult { + friend Tp operator<=>(CmpResult const &, CmpResult const &); +}; +template +struct CmpResult> { + friend T operator<=>(CmpResult const &, CmpResult const &); +}; +template <> +struct CmpResult> { + friend auto operator<=>(CmpResult const &, CmpResult const &) = delete; +}; + +template +struct Cmp : CmpResult... { + friend auto operator<=>(Cmp const &, Cmp const &) = default; +}; + +template +struct Cmp2 : CmpResult... { + auto operator<=>(Cmp2 const &) const = default; +}; + +void test() { + { + using C = Cmp<>; + C c1, c2; + ASSERT_EXPR_TYPE(c1 <=> c2, SO); + } + { + using C = Cmp; + C c1, c2; + (void)(c1 <=> c2); + ASSERT_EXPR_TYPE(c1 <=> c2, SO); + } + { + using C = Cmp2; + C c1, c2; + (void)(c1 <=> c2); + ASSERT_EXPR_TYPE(c1 <=> c2, SO); + } +} + +} // namespace TestCommonCategoryType + +namespace ConstexprDeductionTest { +struct T0 { + int x; + operator int() const { return x; } +}; +struct T1 : T0 { + // expected-error@+1 {{defaulted definition of comparison operator is not constexpr}} + friend constexpr auto operator<=>(T1 const &, T1 const &) = default; +}; +struct T2 { + operator int() const; + // expected-error@+1 {{defaulted definition of comparison operator is not constexpr}} + constexpr bool operator<(T2 const &) const = default; +}; +struct T3 { + constexpr operator int() const; + constexpr bool operator<(T3 const &) const = default; // OK +}; +struct T4 { + constexpr std::strong_ordering operator<=>(T4 const &) const { return std::strong_ordering::equal; } +}; +struct T5 : T4 { + auto operator<=>(T5 const &) const = default; +}; +static_assert(T5{} == T5{}); + +struct T6 { + std::strong_ordering operator<=>(T6 const &) const { return std::strong_ordering::equal; } +}; +struct T7 : T6 { + // expected-note@+1 {{declared here}} + auto operator<=>(T7 const &) const = default; +}; +// expected-error@+2 {{static_assert expression is not an integral constant expression}} +// expected-note@+1 {{non-constexpr function 'operator<=>' cannot be used in a constant expression}} +static_assert(T7{} == T7{}); +} // namespace ConstexprDeductionTest + +namespace NoexceptDeductionTest { +template +struct T0 { + operator int() const noexcept(IsNoexcept); +}; +struct T1 : T0<> { + // expected-error@+1 {{exception specification of explicitly defaulted comparison operator does not match the calculated one}} + friend auto operator<=>(T1 const &, T1 const &) noexcept = default; +}; +struct T2 : T0<> { + // expected-error@+1 {{exception specification of explicitly defaulted comparison operator does not match the calculated one}} + bool operator<(T2 const &) const noexcept = default; +}; +struct T3 { + operator int() const noexcept; + bool operator<(T3 const &) const noexcept = default; // OK +}; +template +struct T4 : T0 { + auto operator<=>(T4 const &) const = default; +}; +static_assert(!noexcept(T4{} <=> T4{})); +static_assert(noexcept(T4{} <=> T4{})); + +template +struct T5 { + friend std::strong_ordering operator<=>(T5 const &, T5 const &) noexcept(IsNoexcept); +}; +template +struct T6 { + T5 val; + auto operator<=>(T6 const &) const = default; +}; +static_assert(!noexcept(T6{} <=> T6{})); +static_assert(noexcept(T6{} <=> T6{})); + +} // namespace NoexceptDeductionTest + + +namespace TestRecursion { + +template +struct U { + Tp &t; + auto operator<=>(U const &) const = default; +}; + +struct T { + U u; + // FIXME(EricWF): Is this diagnostic helpful? + // expected-note@+1 {{explicitly defaulted function was implicitly deleted here}} + auto operator<=>(T const &) const = default; +}; + +void test(T &t1, T &t2) { + // expected-error@+1 {{object of type 'TestRecursion::T' cannot be compared because its comparison operator is implicitly deleted}} + using R = decltype(t1 <=> t2); +} + +} // namespace TestRecursion + +namespace AutoDeductionTest { +template +struct U { + Tp &t; + auto operator<=>(U const &u) const { + return t <=> u.t; + } +}; + +struct T { + U u; + auto operator<=>(T const &) const = default; +}; + +void test(T &t1, T &t2) { + using R = decltype(t1 <=> t2); + ASSERT_TYPE(R, std::partial_ordering); +} +} // namespace AutoDeductionTest + +namespace RecursionTest3 { +template +struct U { + bool b; + Tp &t; + float f; + auto operator<=>(U const &u) const { + if (b) + return t <=> u.t; + // expected-error@+1 {{'auto' in return type deduced as 'std::__1::partial_ordering' here but deduced as 'std::__1::strong_ordering' in earlier return statement}} + return f <=> u.f; + } +}; + +struct T { + U u; + // expected-note@+1 {{in instantiation of member function}} + auto operator<=>(T const &) const = default; +}; + +// FIXME(EricWF): Why isn't this working? +void test(T &t1, T &t2) { + using R = decltype(t1 <=> t2); +} +} // namespace RecursionTest3 + +namespace AccessTest { +struct T { + T() : x(0), y(0), z(0) {} + T(int x, int y, int z) : x(x), y(y), z(z) {} + int x; + +protected: + int y; + +private: + int z; + +public: + auto operator<=>(T const &) const = default; +}; +struct U { + U() : x(0), y(0), z(0) {} + U(int x, int y, int z) : x(x), y(y), z(z) {} + int x; + +protected: + int y; + +private: + float z; + +public: + auto operator<=>(U const &) const = default; +}; + +void test() { + T t1, t2; + (void)(t1 <=> t2); + { + U u1, u2; + auto r = (u1 <=> u2); + ASSERT_EXPR_TYPE(r, std::partial_ordering); + } +} + +struct P { + float x; + +private: + auto operator<=>(P const &) const = default; +}; + +struct D : P { + // expected-note@+1 {{explicitly defaulted function was implicitly deleted here}} + auto operator<=>(D const &) const = default; +}; +void test2() { + D d1, d2; + // expected-error@+1 {{object of type 'AccessTest::D' cannot be compared because its comparison operator is implicitly deleted}} + auto r = (d1 <=> d2); +} + +} // namespace AccessTest + + +namespace CachedLookupTest { +struct T {}; + +struct U { + T t; + // expected-note@+1 {{implicitly deleted here}} + auto operator<=>(U const &) const = default; +}; +// expected-error@+1 {{comparison operator is implicitly deleted}} +auto r1 = U{} <=> U{}; + +std::strong_ordering operator<=>(T const &, T const &); + +struct V { + T t; + auto operator<=>(V const &) const = default; +}; +auto r2 = V{} <=> V{}; +} // namespace CachedLookupTest Index: test/SemaCXX/constant-expression-cxx2a.cpp =================================================================== --- test/SemaCXX/constant-expression-cxx2a.cpp +++ test/SemaCXX/constant-expression-cxx2a.cpp @@ -202,5 +202,21 @@ static_assert(complex_test(makeComplex<_Complex int>(0, 0), makeComplex<_Complex int>(1, 0), std::strong_equality::nonequal)); -// TODO: defaulted operator <=> } // namespace ThreeWayComparison + +namespace DefaultTest { +struct T { + int x, y, z; + constexpr std::strong_ordering operator<=>(T const &other) const = default; +}; + +constexpr T t1{0, 0, 0}; +constexpr T t2{1, 1, 1}; +static_assert(t1 < t2); +static_assert(t1 != t2); +static_assert((t1 <=> t2) < 0); + +// expected-error@+1 {{static_assert failed due to requirement 't1 != (const DefaultTest::T &)t1'}} +static_assert(t1 != (T const &)t1); + +} // namespace DefaultTest