From d052a578de58cbbb638cbe2dba05242d1ff443b9 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Tue, 22 Oct 2019 17:44:08 -0700 Subject: [PATCH] [c++2a] Allow comparison functions to be explicitly defaulted. This adds some initial syntactic checking that only the appropriate function signatures can be defaulted. No implicit definitions are generated yet. --- clang/include/clang/AST/Decl.h | 7 + .../clang/Basic/DiagnosticCommonKinds.td | 3 +- .../clang/Basic/DiagnosticSemaKinds.td | 25 ++ clang/include/clang/Sema/Sema.h | 73 ++++- clang/lib/AST/Decl.cpp | 23 +- clang/lib/Parse/ParseDecl.cpp | 3 +- clang/lib/Parse/ParseDeclCXX.cpp | 3 +- clang/lib/Sema/SemaDecl.cpp | 22 -- clang/lib/Sema/SemaDeclCXX.cpp | 295 +++++++++++++++--- .../lib/Sema/SemaTemplateInstantiateDecl.cpp | 9 +- .../class.compare.default/p1.cpp | 46 +++ .../CXX/class/class.compare/class.eq/p1.cpp | 25 ++ .../CXX/class/class.compare/class.rel/p1.cpp | 25 ++ .../dcl.fct.def/dcl.fct.def.default/p1.cpp | 26 +- clang/test/Parser/cxx0x-decl.cpp | 2 +- .../SemaCXX/cxx0x-defaulted-functions.cpp | 2 +- clang/test/SemaCXX/cxx17-compat.cpp | 33 ++ 17 files changed, 522 insertions(+), 100 deletions(-) create mode 100644 clang/test/CXX/class/class.compare/class.compare.default/p1.cpp create mode 100644 clang/test/CXX/class/class.compare/class.eq/p1.cpp create mode 100644 clang/test/CXX/class/class.compare/class.rel/p1.cpp diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h index ce674e09c44d..b3e7a570fd6d 100644 --- a/clang/include/clang/AST/Decl.h +++ b/clang/include/clang/AST/Decl.h @@ -59,6 +59,7 @@ class EnumDecl; class Expr; class FunctionTemplateDecl; class FunctionTemplateSpecializationInfo; +class FunctionTypeLoc; class LabelStmt; class MemberSpecializationInfo; class Module; @@ -2362,6 +2363,12 @@ public: /// parameters have default arguments (in C++). unsigned getMinRequiredArguments() const; + /// Find the source location information for how the type of this function + /// was written. May be absent (for example if the function was declared via + /// a typedef) and may contain a different type from that of the function + /// (for example if the function type was adjusted by an attribute). + FunctionTypeLoc getFunctionTypeLoc() const; + QualType getReturnType() const { return getType()->castAs()->getReturnType(); } diff --git a/clang/include/clang/Basic/DiagnosticCommonKinds.td b/clang/include/clang/Basic/DiagnosticCommonKinds.td index 6018c1417789..7a416c282e3d 100644 --- a/clang/include/clang/Basic/DiagnosticCommonKinds.td +++ b/clang/include/clang/Basic/DiagnosticCommonKinds.td @@ -87,7 +87,8 @@ def warn_cxx98_compat_variadic_templates : Warning<"variadic templates are incompatible with C++98">, InGroup, DefaultIgnore; def err_default_special_members : Error< - "only special member functions may be defaulted">; + "only special member functions %select{|and comparison operators }0" + "may be defaulted">; def err_deleted_non_function : Error< "only functions can have deleted definitions">; def err_module_not_found : Error<"module '%0' not found">, DefaultFatal; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index d802a92c42c0..f7b98bb9ea86 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -8099,6 +8099,31 @@ def note_vbase_moved_here : Note< "%select{%1 is a virtual base class of base class %2 declared here|" "virtual base class %1 declared here}0">; +// C++20 defaulted comparisons +// This corresponds to values of Sema::DefaultedComparisonKind. +def select_defaulted_comparison_kind : TextSubstitution< + "%select{|equality|three-way|equality|relational}0 comparison " + "operator">; +def ext_defaulted_comparison : ExtWarn< + "defaulted comparison operators are a C++20 extension">, InGroup; +def warn_cxx17_compat_defaulted_comparison : Warning< + "defaulted comparison operators are incompatible with C++ standards " + "before C++20">, InGroup, DefaultIgnore; +def err_defaulted_comparison_template : Error< + "comparison operator template cannot be defaulted">; +def err_defaulted_comparison_out_of_class : Error< + "%sub{select_defaulted_comparison_kind}0 can only be defaulted in a class " + "definition">; +def err_defaulted_comparison_param : Error< + "invalid parameter type for defaulted %sub{select_defaulted_comparison_kind}0" + "%diff{; found $, expected $|}1,2">; +def err_defaulted_comparison_non_const : Error< + "defaulted member %sub{select_defaulted_comparison_kind}0 must be " + "const-qualified">; +def err_defaulted_comparison_return_type_not_bool : Error< + "return type for defaulted %sub{select_defaulted_comparison_kind}0 " + "must be 'bool', not %1">; + def ext_implicit_exception_spec_mismatch : ExtWarn< "function previously declared with an %select{explicit|implicit}0 exception " "specification redeclared with an %select{implicit|explicit}0 exception " diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index a911c61a07f8..3058f862c6ec 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -1237,6 +1237,24 @@ public: /// same special member, we should act as if it is not yet declared. llvm::SmallPtrSet SpecialMembersBeingDeclared; + /// Kinds of defaulted comparison operator functions. + enum class DefaultedComparisonKind { + /// This is not a defaultable comparison operator. + None, + /// This is an operator== that should be implemented as a series of + /// subobject comparisons. + Equal, + /// This is an operator<=> that should be implemented as a series of + /// subobject comparisons. + ThreeWay, + /// This is an operator!= that should be implemented as a rewrite in terms + /// of a == comparison. + NotEqual, + /// This is an <, <=, >, or >= that should be implemented as a rewrite in + /// terms of a <=> comparison. + Relational, + }; + /// The function definitions which were renamed as part of typo-correction /// to match their respective declarations. We want to keep track of them /// to ensure that we don't emit a "redefinition" error if we encounter a @@ -2541,7 +2559,52 @@ public: bool SpecialMemberIsTrivial(CXXMethodDecl *MD, CXXSpecialMember CSM, TrivialABIHandling TAH = TAH_IgnoreTrivialABI, bool Diagnose = false); - CXXSpecialMember getSpecialMember(const CXXMethodDecl *MD); + + /// For a defaulted function, the kind of defaulted function that it is. + class DefaultedFunctionKind { + CXXSpecialMember SpecialMember : 8; + DefaultedComparisonKind Comparison : 8; + + public: + DefaultedFunctionKind() + : SpecialMember(CXXInvalid), Comparison(DefaultedComparisonKind::None) { + } + DefaultedFunctionKind(CXXSpecialMember CSM) + : SpecialMember(CSM), Comparison(DefaultedComparisonKind::None) {} + DefaultedFunctionKind(DefaultedComparisonKind Comp) + : SpecialMember(CXXInvalid), Comparison(Comp) {} + + bool isSpecialMember() const { return SpecialMember != CXXInvalid; } + bool isComparison() const { + return Comparison != DefaultedComparisonKind::None; + } + + explicit operator bool() const { + return isSpecialMember() || isComparison(); + } + + CXXSpecialMember asSpecialMember() const { return SpecialMember; } + DefaultedComparisonKind asComparison() const { return Comparison; } + + /// Get the index of this function kind for use in diagnostics. + unsigned getDiagnosticIndex() const { + static_assert(CXXInvalid > CXXDestructor, + "invalid should have highest index"); + static_assert((unsigned)DefaultedComparisonKind::None == 0, + "none should be equal to zero"); + return SpecialMember + (unsigned)Comparison; + } + }; + + DefaultedFunctionKind getDefaultedFunctionKind(const FunctionDecl *FD); + + CXXSpecialMember getSpecialMember(const CXXMethodDecl *MD) { + return getDefaultedFunctionKind(MD).asSpecialMember(); + } + DefaultedComparisonKind getDefaultedComparisonKind(const FunctionDecl *FD) { + return getDefaultedFunctionKind(FD).asComparison(); + } + void ActOnLastBitfield(SourceLocation DeclStart, SmallVectorImpl &AllIvarDecls); Decl *ActOnIvar(Scope *S, SourceLocation DeclStart, @@ -6361,9 +6424,15 @@ public: StorageClass &SC); void CheckDeductionGuideTemplate(FunctionTemplateDecl *TD); - void CheckExplicitlyDefaultedSpecialMember(CXXMethodDecl *MD); + void CheckExplicitlyDefaultedFunction(FunctionDecl *MD); + + bool CheckExplicitlyDefaultedSpecialMember(CXXMethodDecl *MD, + CXXSpecialMember CSM); void CheckDelayedMemberExceptionSpecs(); + bool CheckExplicitlyDefaultedComparison(FunctionDecl *MD, + DefaultedComparisonKind DCK); + //===--------------------------------------------------------------------===// // C++ Derived Classes // diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp index 80235d8496d2..dae4af8bb249 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -3322,12 +3322,14 @@ bool FunctionDecl::doesDeclarationForceExternallyVisibleDefinition() const { return FoundBody; } -SourceRange FunctionDecl::getReturnTypeSourceRange() const { +FunctionTypeLoc FunctionDecl::getFunctionTypeLoc() const { const TypeSourceInfo *TSI = getTypeSourceInfo(); - if (!TSI) - return SourceRange(); - FunctionTypeLoc FTL = - TSI->getTypeLoc().IgnoreParens().getAs(); + return TSI ? TSI->getTypeLoc().IgnoreParens().getAs() + : FunctionTypeLoc(); +} + +SourceRange FunctionDecl::getReturnTypeSourceRange() const { + FunctionTypeLoc FTL = getFunctionTypeLoc(); if (!FTL) return SourceRange(); @@ -3343,15 +3345,8 @@ SourceRange FunctionDecl::getReturnTypeSourceRange() const { } SourceRange FunctionDecl::getExceptionSpecSourceRange() const { - const TypeSourceInfo *TSI = getTypeSourceInfo(); - if (!TSI) - return SourceRange(); - FunctionTypeLoc FTL = - TSI->getTypeLoc().IgnoreParens().getAs(); - if (!FTL) - return SourceRange(); - - return FTL.getExceptionSpecRange(); + FunctionTypeLoc FTL = getFunctionTypeLoc(); + return FTL ? FTL.getExceptionSpecRange() : SourceRange(); } /// For an inline function definition in C, or for a gnu_inline function diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp index b248d7582d84..c41eb74a9cf3 100644 --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -2349,7 +2349,8 @@ Decl *Parser::ParseDeclarationAfterDeclaratorAndAttributes( Diag(ConsumeToken(), diag::err_default_delete_in_multiple_declaration) << 0 /* default */; else - Diag(ConsumeToken(), diag::err_default_special_members); + Diag(ConsumeToken(), diag::err_default_special_members) + << getLangOpts().CPlusPlus2a; } else { InitializerScopeRAII InitScope(*this, D, ThisDecl); diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp index b98ce3e66292..6d4a1a4a4e87 100644 --- a/clang/lib/Parse/ParseDeclCXX.cpp +++ b/clang/lib/Parse/ParseDeclCXX.cpp @@ -2978,7 +2978,8 @@ ExprResult Parser::ParseCXXMemberInitializer(Decl *D, bool IsFunction, Diag(Tok, diag::err_default_delete_in_multiple_declaration) << 0 /* default */; else - Diag(ConsumeToken(), diag::err_default_special_members); + Diag(ConsumeToken(), diag::err_default_special_members) + << getLangOpts().CPlusPlus2a; return ExprError(); } } diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 62ec83967bff..6202391ee0b8 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -2993,28 +2993,6 @@ struct GNUCompatibleParamWarning { } // end anonymous namespace -/// getSpecialMember - get the special member enum for a method. -Sema::CXXSpecialMember Sema::getSpecialMember(const CXXMethodDecl *MD) { - 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)) { - return Sema::CXXDestructor; - } else if (MD->isCopyAssignmentOperator()) { - return Sema::CXXCopyAssignment; - } else if (MD->isMoveAssignmentOperator()) { - return Sema::CXXMoveAssignment; - } - - return Sema::CXXInvalid; -} - // Determine whether the previous declaration was a definition, implicit // declaration, or a declaration. template diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index ff90b9548e29..0201d014e6f2 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -6084,6 +6084,67 @@ void Sema::propagateDLLAttrToBaseClassTemplate( } } +/// Determine the kind of defaulting that would be done for a given function. +/// +/// If the function is both a default constructor and a copy / move constructor +/// (due to having a default argument for the first parameter), this picks +/// CXXDefaultConstructor. +/// +/// FIXME: Check that case is properly handled by all callers. +Sema::DefaultedFunctionKind +Sema::getDefaultedFunctionKind(const FunctionDecl *FD) { + if (auto *MD = dyn_cast(FD)) { + if (const CXXConstructorDecl *Ctor = dyn_cast(FD)) { + if (Ctor->isDefaultConstructor()) + return Sema::CXXDefaultConstructor; + + if (Ctor->isCopyConstructor()) + return Sema::CXXCopyConstructor; + + if (Ctor->isMoveConstructor()) + return Sema::CXXMoveConstructor; + } + + if (MD->isCopyAssignmentOperator()) + return Sema::CXXCopyAssignment; + + if (MD->isMoveAssignmentOperator()) + return Sema::CXXMoveAssignment; + + if (isa(FD)) + return Sema::CXXDestructor; + } + + switch (FD->getDeclName().getCXXOverloadedOperator()) { + case OO_EqualEqual: + return DefaultedComparisonKind::Equal; + + case OO_ExclaimEqual: + return DefaultedComparisonKind::NotEqual; + + case OO_Spaceship: + // No point allowing this if <=> doesn't exist in the current language mode. + if (!getLangOpts().CPlusPlus2a) + break; + return DefaultedComparisonKind::ThreeWay; + + case OO_Less: + case OO_LessEqual: + case OO_Greater: + case OO_GreaterEqual: + // No point allowing this if <=> doesn't exist in the current language mode. + if (!getLangOpts().CPlusPlus2a) + break; + return DefaultedComparisonKind::Relational; + + default: + break; + } + + // Not defaultable. + return DefaultedFunctionKind(); +} + static void DefineImplicitSpecialMember(Sema &S, CXXMethodDecl *MD, SourceLocation DefaultLoc) { switch (S.getSpecialMember(MD)) { @@ -6331,9 +6392,9 @@ void Sema::CheckCompletedCXXClass(CXXRecordDecl *Record) { Record->setHasTrivialSpecialMemberForCall(); auto CompleteMemberFunction = [&](CXXMethodDecl *M) { - // Check whether the explicitly-defaulted special members are valid. + // Check whether the explicitly-defaulted members are valid. if (!M->isInvalidDecl() && M->isExplicitlyDefaulted()) - CheckExplicitlyDefaultedSpecialMember(M); + CheckExplicitlyDefaultedFunction(M); // For an explicitly defaulted or deleted special member, we defer // determining triviality until the class is complete. That time is now! @@ -6413,6 +6474,15 @@ void Sema::CheckCompletedCXXClass(CXXRecordDecl *Record) { DiagnoseAbsenceOfOverrideControl(M); } + // Process any defaulted friends in the member-specification. + if (!Record->isDependentType()) { + for (FriendDecl *D : Record->friends()) { + auto *FD = dyn_cast_or_null(D->getFriendDecl()); + if (FD && !FD->isInvalidDecl() && FD->isExplicitlyDefaulted()) + CheckExplicitlyDefaultedFunction(FD); + } + } + // ms_struct is a request to use the same ABI rules as MSVC. Check // whether this class uses any C++ features that are implemented // completely differently in MSVC, and if so, emit a diagnostic. @@ -6766,9 +6836,22 @@ void Sema::EvaluateImplicitExceptionSpec(SourceLocation Loc, CXXMethodDecl *MD) UpdateExceptionSpec(MD->getCanonicalDecl(), ESI); } -void Sema::CheckExplicitlyDefaultedSpecialMember(CXXMethodDecl *MD) { +void Sema::CheckExplicitlyDefaultedFunction(FunctionDecl *FD) { + assert(FD->isExplicitlyDefaulted() && "not explicitly-defaulted"); + + DefaultedFunctionKind DefKind = getDefaultedFunctionKind(FD); + assert(DefKind && "not a defaultable function"); + + if (DefKind.isSpecialMember() + ? CheckExplicitlyDefaultedSpecialMember(cast(FD), + DefKind.asSpecialMember()) + : CheckExplicitlyDefaultedComparison(FD, DefKind.asComparison())) + FD->setInvalidDecl(); +} + +bool Sema::CheckExplicitlyDefaultedSpecialMember(CXXMethodDecl *MD, + CXXSpecialMember CSM) { CXXRecordDecl *RD = MD->getParent(); - CXXSpecialMember CSM = getSpecialMember(MD); assert(MD->isExplicitlyDefaulted() && CSM != CXXInvalid && "not an explicitly-defaulted special member"); @@ -6781,7 +6864,7 @@ void Sema::CheckExplicitlyDefaultedSpecialMember(CXXMethodDecl *MD) { // C++11 [dcl.fct.def.default]p1: // A function that is explicitly defaulted shall - // -- be a special member function (checked elsewhere), + // -- be a special member function [...] (checked elsewhere), // -- have the same type (except for ref-qualifiers, and except that a // copy operation can take a non-const reference) as an implicit // declaration, and @@ -6960,8 +7043,87 @@ void Sema::CheckExplicitlyDefaultedSpecialMember(CXXMethodDecl *MD) { } } - if (HadError) - MD->setInvalidDecl(); + return HadError; +} + +bool Sema::CheckExplicitlyDefaultedComparison(FunctionDecl *FD, + DefaultedComparisonKind DCK) { + assert(DCK != DefaultedComparisonKind::None && "not a defaulted comparison"); + + // C++2a [class.compare.default]p1: + // A defaulted comparison operator function for some class C shall be a + // non-template function declared in the member-specification of C that is + // -- a non-static const member of C having one parameter of type + // const C&, or + // -- a friend of C having two parameters of type const C&. + CXXRecordDecl *RD = dyn_cast(FD->getLexicalDeclContext()); + assert(RD && "defaulted comparison is not defaulted in a class"); + + QualType ExpectedParmType = + Context.getLValueReferenceType(Context.getRecordType(RD).withConst()); + for (const ParmVarDecl *Param : FD->parameters()) { + if (!Context.hasSameType(Param->getType(), ExpectedParmType)) { + Diag(FD->getLocation(), diag::err_defaulted_comparison_param) + << (int)DCK << Param->getType() << ExpectedParmType + << Param->getSourceRange(); + return true; + } + } + + // ... non-static const member ... + if (auto *MD = dyn_cast(FD)) { + assert(!MD->isStatic() && "comparison function cannot be a static member"); + if (!MD->isConst()) { + SourceLocation InsertLoc; + if (FunctionTypeLoc Loc = MD->getFunctionTypeLoc()) + InsertLoc = getLocForEndOfToken(Loc.getRParenLoc()); + Diag(MD->getLocation(), diag::err_defaulted_comparison_non_const) + << (int)DCK << FixItHint::CreateInsertion(InsertLoc, " const"); + + // Add the 'const' to the type to recover. + const auto *FPT = MD->getType()->castAs(); + FunctionProtoType::ExtProtoInfo EPI = FPT->getExtProtoInfo(); + EPI.TypeQuals.addConst(); + MD->setType(Context.getFunctionType(FPT->getReturnType(), + FPT->getParamTypes(), EPI)); + } + } else { + // A non-member function declared in a class must be a friend. + assert(FD->getFriendObjectKind() && "expected a friend declaration"); + } + + // C++2a [class.compare.default]p2: + // A defaulted comparison operator function for class C is defined as + // deleted if any non-static data member of C is of reference type or C is + // a union-like class. + // FIXME: Applying this to cases other than == and <=> is unreasonable. + // FIXME: Implement. + + // C++2a [class.eq]p1, [class.rel]p1: + // A [defaulted comparison other than <=>] shall have a declared return + // type bool. + if (DCK != DefaultedComparisonKind::ThreeWay && + !Context.hasSameType(FD->getDeclaredReturnType(), Context.BoolTy)) { + Diag(FD->getLocation(), diag::err_defaulted_comparison_return_type_not_bool) + << (int)DCK << FD->getDeclaredReturnType() << Context.BoolTy + << FD->getReturnTypeSourceRange(); + return true; + } + + // FIXME: Determine whether the function should be defined as deleted. + + // C++2a [dcl.fct.def.default]p3: + // An explicitly-defaulted function [..] may be declared constexpr or + // consteval only if it would have been implicitly declared constexpr. + // FIXME: There are no rules governing when these should be constexpr, + // except for the special case of the injected operator==, for which + // C++2a [class.compare.default]p3 says: + // The operator is a constexpr function if its definition would satisfy + // the requirements for a constexpr function. + // FIXME: Apply this rule to all defaulted comparisons. The only way this + // can fail is if the return type of a defaulted operator<=> is not a literal + // type. + return false; } void Sema::CheckDelayedMemberExceptionSpecs() { @@ -15006,51 +15168,88 @@ void Sema::SetDeclDeleted(Decl *Dcl, SourceLocation DelLoc) { } void Sema::SetDeclDefaulted(Decl *Dcl, SourceLocation DefaultLoc) { - CXXMethodDecl *MD = dyn_cast_or_null(Dcl); + if (!Dcl || Dcl->isInvalidDecl()) + return; - if (MD) { - if (MD->getParent()->isDependentType()) { - MD->setDefaulted(); - MD->setExplicitlyDefaulted(); - return; + auto *FD = dyn_cast(Dcl); + if (!FD) { + if (auto *FTD = dyn_cast(Dcl)) { + if (getDefaultedFunctionKind(FTD->getTemplatedDecl()).isComparison()) { + Diag(DefaultLoc, diag::err_defaulted_comparison_template); + return; + } } - CXXSpecialMember Member = getSpecialMember(MD); - if (Member == CXXInvalid) { - if (!MD->isInvalidDecl()) - Diag(DefaultLoc, diag::err_default_special_members); - return; - } - - MD->setDefaulted(); - MD->setExplicitlyDefaulted(); - - // 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); - - // If this definition appears within the record, do the checking when - // the record is complete. - const FunctionDecl *Primary = MD; - if (const FunctionDecl *Pattern = MD->getTemplateInstantiationPattern()) - // Ask the template instantiation pattern that actually had the - // '= default' on it. - Primary = Pattern; - - // If the method was defaulted on its first declaration, we will have - // already performed the checking in CheckCompletedCXXClass. Such a - // declaration doesn't trigger an implicit definition. - if (Primary->getCanonicalDecl()->isDefaulted()) - return; - - CheckExplicitlyDefaultedSpecialMember(MD); - - if (!MD->isInvalidDecl()) - DefineImplicitSpecialMember(*this, MD, DefaultLoc); - } else { - Diag(DefaultLoc, diag::err_default_special_members); + Diag(DefaultLoc, diag::err_default_special_members) + << getLangOpts().CPlusPlus2a; + return; } + + // Reject if this can't possibly be a defaultable function. + DefaultedFunctionKind DefKind = getDefaultedFunctionKind(FD); + if (!DefKind && + // A dependent function that doesn't locally look defaultable can + // still instantiate to a defaultable function if it's a constructor + // or assignment operator. + (!FD->isDependentContext() || + (!isa(FD) && + FD->getDeclName().getCXXOverloadedOperator() != OO_Equal))) { + Diag(DefaultLoc, diag::err_default_special_members) + << getLangOpts().CPlusPlus2a; + return; + } + + if (DefKind.isComparison() && + !isa(FD->getLexicalDeclContext())) { + Diag(FD->getLocation(), diag::err_defaulted_comparison_out_of_class) + << (int)DefKind.asComparison(); + return; + } + + // Issue compatibility warning. We already warned if the operator is + // 'operator<=>' when parsing the '<=>' token. + if (DefKind.isComparison() && + DefKind.asComparison() != DefaultedComparisonKind::ThreeWay) { + Diag(DefaultLoc, getLangOpts().CPlusPlus2a + ? diag::warn_cxx17_compat_defaulted_comparison + : diag::ext_defaulted_comparison); + } + + FD->setDefaulted(); + FD->setExplicitlyDefaulted(); + + // Defer checking functions that are defaulted in a dependent context. + if (FD->isDependentContext()) + return; + + // 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. + FD->setWillHaveBody(false); + + // If this definition appears within the record, do the checking when + // the record is complete. This is always the case for a defaulted + // comparison. + if (DefKind.isComparison()) + return; + auto *MD = cast(FD); + + const FunctionDecl *Primary = FD; + if (const FunctionDecl *Pattern = FD->getTemplateInstantiationPattern()) + // Ask the template instantiation pattern that actually had the + // '= default' on it. + Primary = Pattern; + + // If the method was defaulted on its first declaration, we will have + // already performed the checking in CheckCompletedCXXClass. Such a + // declaration doesn't trigger an implicit definition. + if (Primary->getCanonicalDecl()->isDefaulted()) + return; + + if (CheckExplicitlyDefaultedSpecialMember(MD, DefKind.asSpecialMember())) + MD->setInvalidDecl(); + else + DefineImplicitSpecialMember(*this, MD, DefaultLoc); } static void SearchForReturnInStmt(Sema &Self, Stmt *S) { diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp index d1ad304e62e4..31a4302ba826 100644 --- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -2049,6 +2049,11 @@ Decl *TemplateDeclInstantiator::VisitFunctionDecl(FunctionDecl *D, } } + if (D->isExplicitlyDefaulted()) + SemaRef.SetDeclDefaulted(Function, D->getLocation()); + if (D->isDeleted()) + SemaRef.SetDeclDeleted(Function, D->getLocation()); + if (Function->isLocalExternDecl() && !Function->getPreviousDecl()) DC->makeDeclVisibleInContext(PrincipalDecl); @@ -2056,7 +2061,6 @@ Decl *TemplateDeclInstantiator::VisitFunctionDecl(FunctionDecl *D, PrincipalDecl->isInIdentifierNamespace(Decl::IDNS_Ordinary)) PrincipalDecl->setNonMemberOperator(); - assert(!D->isDefaulted() && "only methods should be defaulted"); return Function; } @@ -4016,9 +4020,6 @@ void Sema::InstantiateExceptionSpec(SourceLocation PointOfInstantiation, bool TemplateDeclInstantiator::InitFunctionInstantiation(FunctionDecl *New, FunctionDecl *Tmpl) { - if (Tmpl->isDeleted()) - New->setDeletedAsWritten(); - New->setImplicit(Tmpl->isImplicit()); // Forward the mangling number from the template to the instantiated decl. diff --git a/clang/test/CXX/class/class.compare/class.compare.default/p1.cpp b/clang/test/CXX/class/class.compare/class.compare.default/p1.cpp new file mode 100644 index 000000000000..1f8d6a2a7cff --- /dev/null +++ b/clang/test/CXX/class/class.compare/class.compare.default/p1.cpp @@ -0,0 +1,46 @@ +// RUN: %clang_cc1 -std=c++2a -verify %s + +struct B {}; +bool operator==(const B&, const B&) = default; // expected-error {{equality comparison operator can only be defaulted in a class definition}} +bool operator<=>(const B&, const B&) = default; // expected-error {{three-way comparison operator can only be defaulted in a class definition}} + +template + bool operator<(const B&, const B&) = default; // expected-error {{comparison operator template cannot be defaulted}} + +struct A { + friend bool operator==(const A&, const A&) = default; + friend bool operator!=(const A&, const B&) = default; // expected-error {{invalid parameter type for defaulted equality comparison}} + friend bool operator!=(const B&, const B&) = default; // expected-error {{invalid parameter type for defaulted equality comparison}} + friend bool operator<(const A&, const A&); + friend bool operator<(const B&, const B&) = default; // expected-error {{invalid parameter type for defaulted relational comparison}} + friend bool operator>(A, A) = default; // expected-error {{invalid parameter type for defaulted relational comparison}} + + bool operator<(const A&) const; + bool operator<=(const A&) const = default; + bool operator==(const A&) const volatile && = default; // surprisingly, OK + bool operator<=>(const A&) = default; // expected-error {{defaulted member three-way comparison operator must be const-qualified}} + bool operator>=(const B&) const = default; // expected-error {{invalid parameter type for defaulted relational comparison}} + static bool operator>(const B&) = default; // expected-error {{overloaded 'operator>' cannot be a static member function}} + + template + friend bool operator==(const A&, const A&) = default; // expected-error {{comparison operator template cannot be defaulted}} + template + bool operator==(const A&) const = default; // expected-error {{comparison operator template cannot be defaulted}} +}; + +// FIXME: The wording is not clear as to whether these are valid, but the +// intention is that they are not. +bool operator<(const A&, const A&) = default; // expected-error {{relational comparison operator can only be defaulted in a class definition}} +bool A::operator<(const A&) const = default; // expected-error {{can only be defaulted in a class definition}} + +template struct Dependent { + using U = typename T::type; + bool operator==(U) const = default; // expected-error {{found 'Dependent::U'}} + friend bool operator==(U, U) = default; // expected-error {{found 'Dependent::U'}} +}; + +struct Good { using type = const Dependent&; }; +template struct Dependent; + +struct Bad { using type = Dependent&; }; +template struct Dependent; // expected-note {{in instantiation of}} diff --git a/clang/test/CXX/class/class.compare/class.eq/p1.cpp b/clang/test/CXX/class/class.compare/class.eq/p1.cpp new file mode 100644 index 000000000000..622f66cf9281 --- /dev/null +++ b/clang/test/CXX/class/class.compare/class.eq/p1.cpp @@ -0,0 +1,25 @@ +// RUN: %clang_cc1 -std=c++2a -verify %s + +struct Good { + bool operator==(const Good&) const = default; + bool operator!=(const Good&) const = default; + friend bool operator==(const Good&, const Good&) = default; + friend bool operator!=(const Good&, const Good&) = default; +}; + +enum Bool : bool {}; +struct Bad { + bool &operator==(const Bad&) const = default; // expected-error {{return type for defaulted equality comparison operator must be 'bool', not 'bool &'}} + const bool operator!=(const Bad&) const = default; // expected-error {{return type for defaulted equality comparison operator must be 'bool', not 'const bool'}} + friend Bool operator==(const Bad&, const Bad&) = default; // expected-error {{return type for defaulted equality comparison operator must be 'bool', not 'Bool'}} + friend int operator!=(const Bad&, const Bad&) = default; // expected-error {{return type for defaulted equality comparison operator must be 'bool', not 'int'}} +}; + +template struct Ugly { + T operator==(const Ugly&) const = default; // expected-error {{return type}} + T operator!=(const Ugly&) const = default; // expected-error {{return type}} + friend T operator==(const Ugly&, const Ugly&) = default; // expected-error {{return type}} + friend T operator!=(const Ugly&, const Ugly&) = default; // expected-error {{return type}} +}; +template struct Ugly; +template struct Ugly; // expected-note {{in instantiation of}} diff --git a/clang/test/CXX/class/class.compare/class.rel/p1.cpp b/clang/test/CXX/class/class.compare/class.rel/p1.cpp new file mode 100644 index 000000000000..3797d5f81f56 --- /dev/null +++ b/clang/test/CXX/class/class.compare/class.rel/p1.cpp @@ -0,0 +1,25 @@ +// RUN: %clang_cc1 -std=c++2a -verify %s + +struct Good { + bool operator<(const Good&) const = default; + bool operator>(const Good&) const = default; + friend bool operator<=(const Good&, const Good&) = default; + friend bool operator>=(const Good&, const Good&) = default; +}; + +enum Bool : bool {}; +struct Bad { + bool &operator<(const Bad&) const = default; // expected-error {{return type for defaulted relational comparison operator must be 'bool', not 'bool &'}} + const bool operator>(const Bad&) const = default; // expected-error {{return type for defaulted relational comparison operator must be 'bool', not 'const bool'}} + friend Bool operator<=(const Bad&, const Bad&) = default; // expected-error {{return type for defaulted relational comparison operator must be 'bool', not 'Bool'}} + friend int operator>=(const Bad&, const Bad&) = default; // expected-error {{return type for defaulted relational comparison operator must be 'bool', not 'int'}} +}; + +template struct Ugly { + T operator<(const Ugly&) const = default; // expected-error {{return type}} + T operator>(const Ugly&) const = default; // expected-error {{return type}} + friend T operator<=(const Ugly&, const Ugly&) = default; // expected-error {{return type}} + friend T operator>=(const Ugly&, const Ugly&) = default; // expected-error {{return type}} +}; +template struct Ugly; +template struct Ugly; // expected-note {{in instantiation of}} diff --git a/clang/test/CXX/dcl.decl/dcl.fct.def/dcl.fct.def.default/p1.cpp b/clang/test/CXX/dcl.decl/dcl.fct.def/dcl.fct.def.default/p1.cpp index 3f2bc569edf6..6e9b45903d39 100644 --- a/clang/test/CXX/dcl.decl/dcl.fct.def/dcl.fct.def.default/p1.cpp +++ b/clang/test/CXX/dcl.decl/dcl.fct.def/dcl.fct.def.default/p1.cpp @@ -1,12 +1,28 @@ -// RUN: %clang_cc1 -verify %s -std=c++11 -// RUN: %clang_cc1 -verify %s -std=c++17 -// RUN: %clang_cc1 -verify %s -std=c++2a +// RUN: %clang_cc1 -verify=expected,pre2a %s -std=c++11 +// RUN: %clang_cc1 -verify=expected,pre2a %s -std=c++17 +// RUN: %clang_cc1 -verify=expected %s -std=c++2a // A function that is explicitly defaulted shall struct A { - // -- be a special member function, - A(int) = default; // expected-error {{only special member functions may be defaulted}} + // -- be a special member function [C++2a: or a comparison operator function], + A(int) = default; +#if __cplusplus <= 201703L + // expected-error@-2 {{only special member functions may be defaulted}} +#else + // expected-error@-4 {{only special member functions and comparison operators may be defaulted}} +#endif A(A) = default; // expected-error {{must pass its first argument by reference}} + void f(A) = default; // expected-error-re {{only special member functions{{( and comparison operators)?}} may be defaulted}} + + bool operator==(const A&) const = default; // pre2a-warning {{defaulted comparison operators are a C++20 extension}} + bool operator!=(const A&) const = default; // pre2a-warning {{defaulted comparison operators are a C++20 extension}} + bool operator<(const A&) const = default; // pre2a-error {{only special member functions may be defaulted}} + bool operator>(const A&) const = default; // pre2a-error {{only special member functions may be defaulted}} + bool operator<=(const A&) const = default; // pre2a-error {{only special member functions may be defaulted}} + bool operator>=(const A&) const = default; // pre2a-error {{only special member functions may be defaulted}} + bool operator<=>(const A&) const = default; // pre2a-error 1+{{}} pre2a-warning {{'<=>' is a single token in C++2a}} + + A operator+(const A&) const = default; // expected-error-re {{only special member functions{{( and comparison operators)?}} may be defaulted}} // -- have the same declared function type as if it had been implicitly // declared diff --git a/clang/test/Parser/cxx0x-decl.cpp b/clang/test/Parser/cxx0x-decl.cpp index 2f219ac87fb8..3c1c3602691b 100644 --- a/clang/test/Parser/cxx0x-decl.cpp +++ b/clang/test/Parser/cxx0x-decl.cpp @@ -39,7 +39,7 @@ static_assert(something, ""); // expected-error {{undeclared identifier}} // PR9903 struct SS { - typedef void d() = default; // expected-error {{function definition declared 'typedef'}} expected-error {{only special member functions may be defaulted}} + typedef void d() = default; // expected-error {{function definition declared 'typedef'}} expected-error {{only special member functions and comparison operators may be defaulted}} }; using PR14855 = int S::; // expected-error {{expected ';' after alias declaration}} diff --git a/clang/test/SemaCXX/cxx0x-defaulted-functions.cpp b/clang/test/SemaCXX/cxx0x-defaulted-functions.cpp index 45a65440d599..c68b7d67932e 100644 --- a/clang/test/SemaCXX/cxx0x-defaulted-functions.cpp +++ b/clang/test/SemaCXX/cxx0x-defaulted-functions.cpp @@ -175,7 +175,7 @@ namespace PR14577 { Outer::Inner1::~Inner1() = delete; // expected-error {{nested name specifier 'Outer::Inner1::' for declaration does not refer into a class, class template or class template partial specialization}} expected-error {{only functions can have deleted definitions}} template - Outer::Inner2::~Inner2() = default; // expected-error {{nested name specifier 'Outer::Inner2::' for declaration does not refer into a class, class template or class template partial specialization}} expected-error {{only special member functions may be defaulted}} + Outer::Inner2::~Inner2() = default; // expected-error {{nested name specifier 'Outer::Inner2::' for declaration does not refer into a class, class template or class template partial specialization}} } extern "C" { // expected-note {{extern "C" language linkage specification begins here}} diff --git a/clang/test/SemaCXX/cxx17-compat.cpp b/clang/test/SemaCXX/cxx17-compat.cpp index 3d5420fa0637..e063b1fc1807 100644 --- a/clang/test/SemaCXX/cxx17-compat.cpp +++ b/clang/test/SemaCXX/cxx17-compat.cpp @@ -88,3 +88,36 @@ void f() { // expected-warning@-4 {{decomposition declaration declared with 'static thread_local' specifiers is incompatible with C++ standards before C++2a}} #endif } + +struct DefaultedComparisons { + bool operator==(const DefaultedComparisons&) const = default; + bool operator!=(const DefaultedComparisons&) const = default; +#if __cplusplus <= 201703L + // expected-warning@-3 {{defaulted comparison operators are a C++20 extension}} + // expected-warning@-3 {{defaulted comparison operators are a C++20 extension}} +#else + // expected-warning@-6 {{defaulted comparison operators are incompatible with C++ standards before C++20}} + // expected-warning@-6 {{defaulted comparison operators are incompatible with C++ standards before C++20}} +#endif + bool operator<=>(const DefaultedComparisons&) const = default; +#if __cplusplus <= 201703L + // expected-error@-2 {{'operator<=' cannot be the name of a variable or data member}} expected-error@-2 0+{{}} expected-warning@-2 {{}} +#else + // expected-warning@-4 {{'<=>' operator is incompatible with C++ standards before C++2a}} +#endif + bool operator<(const DefaultedComparisons&) const = default; + bool operator<=(const DefaultedComparisons&) const = default; + bool operator>(const DefaultedComparisons&) const = default; + bool operator>=(const DefaultedComparisons&) const = default; +#if __cplusplus <= 201703L + // expected-error@-5 {{only special member functions}} + // expected-error@-5 {{only special member functions}} + // expected-error@-5 {{only special member functions}} + // expected-error@-5 {{only special member functions}} +#else + // expected-warning@-10 {{defaulted comparison operators are incompatible with C++ standards before C++20}} + // expected-warning@-10 {{defaulted comparison operators are incompatible with C++ standards before C++20}} + // expected-warning@-10 {{defaulted comparison operators are incompatible with C++ standards before C++20}} + // expected-warning@-10 {{defaulted comparison operators are incompatible with C++ standards before C++20}} +#endif +};