Tyker created this revision. Tyker added a reviewer: rsmith. Herald added a project: clang. Herald added a subscriber: cfe-commits.
this patch improves diagnostic for invalid constexpr defaulted special members by adding notes explaining why the special member cannot be constexpr. example input: 01 struct B {}; 02 03 struct C : virtual B { 04 }; 05 06 struct D { 07 C c; 08 }; 09 10 struct A : D { 11 constexpr A() = default; 12 }; 13 14 union U { 15 A a; 16 int b; 17 constexpr U() = default; 18 }; 19 20 struct E { 21 ~E() {} 22 }; 23 24 struct F { 25 E e; 26 constexpr F& operator=(const F&) =default; 27 }; output with patch: test.cpp:11:3: error: defaulted definition of default constructor is not constexpr because: constexpr A() = default; ^ test.cpp:10:12: note: base class 'D' of 'A' has a non-constexpr implicit default constructor struct A : D { ^ test.cpp:7:5: note: non-static data member 'c' of 'D' has a non-constexpr implicit default constructor C c; ^ test.cpp:3:12: note: 'C' inherits virtually from 'B' struct C : virtual B { ^ test.cpp:17:3: error: defaulted definition of default constructor is not constexpr because: constexpr U() = default; ^ note: unions require exactly one non-static data member initializer to have a constexpr default constructor test.cpp:26:3: error: defaulted definition of copy assignment operator is not constexpr because: constexpr F& operator=(const F&) =default; ^ note: 'F' is not a literal type 3 errors generated. I didn't adapt exitsing tests yet because the diagnostics emitted are likely be adapted during review. Repository: rC Clang https://reviews.llvm.org/D63134 Files: clang/include/clang/Basic/DiagnosticSemaKinds.td clang/lib/Sema/SemaDeclCXX.cpp
Index: clang/lib/Sema/SemaDeclCXX.cpp =================================================================== --- clang/lib/Sema/SemaDeclCXX.cpp +++ clang/lib/Sema/SemaDeclCXX.cpp @@ -6366,19 +6366,26 @@ Sema::CXXSpecialMember CSM, unsigned Quals, bool ConstRHS, CXXConstructorDecl *InheritedCtor = nullptr, - Sema::InheritedConstructorInfo *Inherited = nullptr) { + Sema::InheritedConstructorInfo *Inherited = nullptr, + SourceLocation* SMLoc = nullptr) { // If we're inheriting a constructor, see if we need to call it for this base // class. if (InheritedCtor) { assert(CSM == Sema::CXXDefaultConstructor); auto BaseCtor = Inherited->findConstructorForBase(ClassDecl, InheritedCtor).first; - if (BaseCtor) + if (BaseCtor) { + if (SMLoc && !BaseCtor->isImplicit()) + *SMLoc = BaseCtor->getLocation(); return BaseCtor->isConstexpr(); + } } - if (CSM == Sema::CXXDefaultConstructor) - return ClassDecl->hasConstexprDefaultConstructor(); + if (CSM == Sema::CXXDefaultConstructor) { + bool IsConstexprCtor = ClassDecl->hasConstexprDefaultConstructor(); + if (IsConstexprCtor || !SMLoc) + return IsConstexprCtor; + } Sema::SpecialMemberOverloadResult SMOR = lookupCallFromSpecialMember(S, ClassDecl, CSM, Quals, ConstRHS); @@ -6386,21 +6393,96 @@ // A constructor we wouldn't select can't be "involved in initializing" // anything. return true; + if (SMLoc && !SMOR.getMethod()->isImplicit()) + *SMLoc = SMOR.getMethod()->getLocation(); return SMOR.getMethod()->isConstexpr(); } /// Determine whether the specified special member function would be constexpr /// if it were implicitly defined. static bool defaultedSpecialMemberIsConstexpr( - Sema &S, CXXRecordDecl *ClassDecl, Sema::CXXSpecialMember CSM, - bool ConstArg, CXXConstructorDecl *InheritedCtor = nullptr, - Sema::InheritedConstructorInfo *Inherited = nullptr) { + Sema &S, const CXXRecordDecl *ClassDecl, Sema::CXXSpecialMember CSM, + bool ConstArg, + CXXConstructorDecl *InheritedCtor = nullptr, + Sema::InheritedConstructorInfo *Inherited = nullptr, + SmallVectorImpl<PartialDiagnosticAt>* DiagNotes = nullptr) { if (!S.getLangOpts().CPlusPlus11) return false; + SourceLocation SMLoc; + SourceLocation DiagLoc; + enum class FailKind { + Subobject, + Union, + VirtualBase, + NonLiteral, + }; + /// This lambda will gathering all information and add notes for diagnostics. + auto Fail = [&](FailKind FailureKind, const Decl *FaillingDecl = nullptr) { + if (DiagNotes) { + switch (FailureKind) { + case FailKind::Subobject: { + PartialDiagnostic PDiag(diag::note_constexpr_defaulted_special_member, S.Context.getDiagAllocator()); + auto *Field = dyn_cast<FieldDecl>(FaillingDecl); + auto *Record = dyn_cast<CXXRecordDecl>(FaillingDecl); + SourceLocation Loc = FaillingDecl->getLocation(); + if (!Record) + Record = Field->getType()->getAsCXXRecordDecl(); + if (DiagLoc.isValid()) + Loc = DiagLoc; + PDiag << CSM << isa<CXXRecordDecl>(FaillingDecl); + if (Field) + PDiag << Field; + else + PDiag << Record; + PDiag << ClassDecl << SMLoc.isInvalid() + << (Field ? Field->getDeclName().isEmpty() : false); + DiagNotes->emplace_back(Loc, PDiag); + if (SMLoc.isValid()) { + DiagNotes->emplace_back( + SMLoc, PartialDiagnostic(diag::note_special_member_declared_here, + S.Context.getDiagAllocator()) + << CSM); + return false; + } + if (!Record) + return false; + defaultedSpecialMemberIsConstexpr(S, Record, CSM, ConstArg, nullptr, + nullptr, DiagNotes); + return false; + } + case FailKind::VirtualBase: { + PartialDiagnostic PDiag(diag::note_constexpr_defaulted_virtual_base, + S.Context.getDiagAllocator()); + const CXXBaseSpecifier& Base = *ClassDecl->bases_begin(); + PDiag << ClassDecl << Base.getType(); + DiagNotes->emplace_back(Base.getBeginLoc(), PDiag); + return false; + } + case FailKind::Union: + DiagNotes->emplace_back( + SourceLocation(), + PartialDiagnostic(diag::note_constexpr_defaulted_union, + S.Context.getDiagAllocator()) + << CSM); + return false; + case FailKind::NonLiteral: + // FIXME: Explain why it is not literal. + DiagNotes->emplace_back( + SourceLocation(), + PartialDiagnostic(diag::note_constexpr_defaulted_non_literal, + S.Context.getDiagAllocator()) + << ClassDecl); + return false; + } + } + return false; + }; + // C++11 [dcl.constexpr]p4: // In the definition of a constexpr constructor [...] bool Ctor = true; + bool IsCtorConstexpr = false; switch (CSM) { case Sema::CXXDefaultConstructor: if (Inherited) @@ -6411,7 +6493,10 @@ // // This is important for performance; we need to know whether the default // constructor is constexpr to determine whether the type is a literal type. - return ClassDecl->defaultedDefaultConstructorIsConstexpr(); + IsCtorConstexpr = ClassDecl->defaultedDefaultConstructorIsConstexpr(); + if (DiagNotes && !IsCtorConstexpr) + break; + return IsCtorConstexpr; case Sema::CXXCopyConstructor: case Sema::CXXMoveConstructor: @@ -6438,20 +6523,23 @@ // If we squint, this is guaranteed, since exactly one non-static data member // will be initialized (if the constructor isn't deleted), we just don't know // which one. - if (Ctor && ClassDecl->isUnion()) - return CSM == Sema::CXXDefaultConstructor - ? ClassDecl->hasInClassInitializer() || - !ClassDecl->hasVariantMembers() - : true; + if (Ctor && ClassDecl->isUnion()) { + if (CSM == Sema::CXXDefaultConstructor && + !(ClassDecl->hasInClassInitializer() || + !ClassDecl->hasVariantMembers())) + return Fail(FailKind::Union); + else + return true; + } // -- the class shall not have any virtual base classes; if (Ctor && ClassDecl->getNumVBases()) - return false; + return Fail(FailKind::VirtualBase); // C++1y [class.copy]p26: // -- [the class] is a literal type, and if (!Ctor && !ClassDecl->isLiteral()) - return false; + return Fail(FailKind::NonLiteral); // -- every constructor involved in initializing [...] base class // sub-objects shall be a constexpr constructor; @@ -6461,10 +6549,11 @@ const RecordType *BaseType = B.getType()->getAs<RecordType>(); if (!BaseType) continue; + DiagLoc = B.getBeginLoc(); CXXRecordDecl *BaseClassDecl = cast<CXXRecordDecl>(BaseType->getDecl()); if (!specialMemberIsConstexpr(S, BaseClassDecl, CSM, 0, ConstArg, - InheritedCtor, Inherited)) - return false; + InheritedCtor, Inherited, DiagNotes ? &SMLoc : nullptr)) + return Fail(FailKind::Subobject, BaseClassDecl); } // -- every constructor involved in initializing non-static data members @@ -6484,10 +6573,11 @@ CXXRecordDecl *FieldRecDecl = cast<CXXRecordDecl>(RecordTy->getDecl()); if (!specialMemberIsConstexpr(S, FieldRecDecl, CSM, BaseType.getCVRQualifiers(), - ConstArg && !F->isMutable())) - return false; + ConstArg && !F->isMutable(), + nullptr, nullptr, DiagNotes ? &SMLoc : nullptr)) + return Fail(FailKind::Subobject, F); } else if (CSM == Sema::CXXDefaultConstructor) { - return false; + return Fail(FailKind::Subobject, F); } } @@ -6682,14 +6772,20 @@ // destructors in C++1y), this is checked elsewhere. // // FIXME: This should not apply if the member is deleted. - bool Constexpr = defaultedSpecialMemberIsConstexpr(*this, RD, CSM, - HasConstParam); - if ((getLangOpts().CPlusPlus14 ? !isa<CXXDestructorDecl>(MD) - : isa<CXXConstructorDecl>(MD)) && - MD->isConstexpr() && !Constexpr && - MD->getTemplatedKind() == FunctionDecl::TK_NonTemplate) { - Diag(MD->getBeginLoc(), diag::err_incorrect_defaulted_constexpr) << CSM; - // FIXME: Explain why the special member can't be constexpr. + bool InvalidIfNotConstexpr = + ((getLangOpts().CPlusPlus14 ? !isa<CXXDestructorDecl>(MD) + : isa<CXXConstructorDecl>(MD)) && + MD->isConstexpr() && + MD->getTemplatedKind() == FunctionDecl::TK_NonTemplate); + llvm::SmallVector<PartialDiagnosticAt, 8> DiagNotes; + bool Constexpr = defaultedSpecialMemberIsConstexpr( + *this, RD, CSM, HasConstParam, /*InheritedCtor=*/nullptr, + /*Inherited=*/nullptr, InvalidIfNotConstexpr ? &DiagNotes : nullptr); + if (InvalidIfNotConstexpr && !Constexpr) { + Diag(MD->getBeginLoc(), diag::err_incorrect_defaulted_constexpr) + << CSM << (DiagNotes.size() != 0); + for (auto Note : DiagNotes) + Diag(Note.first, Note.second); HadError = true; } Index: clang/include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- clang/include/clang/Basic/DiagnosticSemaKinds.td +++ clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -7846,7 +7846,7 @@ "lvalue reference type">; def err_incorrect_defaulted_constexpr : Error< "defaulted definition of %sub{select_special_member_kind}0 " - "is not constexpr">; + "is not constexpr%select{| because:}1">; def warn_defaulted_method_deleted : Warning< "explicitly defaulted %sub{select_special_member_kind}0 is implicitly " "deleted">, InGroup<DiagGroup<"defaulted-function-deleted">>; @@ -7866,7 +7866,19 @@ 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">; - +def note_special_member_declared_here : Note< + "%sub{select_special_member_kind}0 declared here">; +def note_constexpr_defaulted_special_member : Note< + "%select{|anonymous }5%select{non-static data member|base class}1" + "%select{ %2|}5 of %3 has a non-constexpr %select{|implicit }4" + "%sub{select_special_member_kind}0">; +def note_constexpr_defaulted_virtual_base : Note< + "%0 inherits virtually from %1">; +def note_constexpr_defaulted_union : Note< + "unions require exactly one non-static data member initializer" + " to have a constexpr %sub{select_special_member_kind}0">; +def note_constexpr_defaulted_non_literal : Note< + "%0 is not a literal type">; 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 "
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits