Index: include/clang/Basic/Diagnostic.td =================================================================== --- include/clang/Basic/Diagnostic.td +++ include/clang/Basic/Diagnostic.td @@ -39,6 +39,15 @@ def SFINAE_Report : SFINAEResponse; def SFINAE_AccessControl : SFINAEResponse; +// Textual substitutions which may be performed on the text of diagnostics +class TextSubstitution { + string Substitution = Text; + // TODO: These are only here to allow substitutions to be declared inline with + // diagnostics + string Component = ""; + string CategoryName = ""; +} + // Diagnostic Categories. These can be applied to groups or individual // diagnostics to specify a category. class DiagCategory { Index: include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- include/clang/Basic/DiagnosticSemaKinds.td +++ include/clang/Basic/DiagnosticSemaKinds.td @@ -1622,13 +1622,16 @@ "overrides of %0 in subclasses are not available in the " "%select{constructor|destructor}1 of %2">; +def select_special_member_kind : TextSubstitution< + "%select{default constructor|copy constructor|move constructor|" + "copy assignment operator|move assignment operator|destructor}0">; + def note_member_declared_at : Note<"member is declared here">; def note_ivar_decl : Note<"instance variable is declared here">; def note_bitfield_decl : Note<"bit-field is declared here">; def note_implicit_param_decl : Note<"%0 is an implicit parameter">; def note_member_synthesized_at : Note< - "in implicit %select{default constructor|copy constructor|move constructor|" - "copy assignment operator|move assignment operator|destructor}0 for %1 " + "in implicit %sub{select_special_member_kind}0 for %1 " "first required here">; def err_missing_default_ctor : Error< "%select{constructor for %1 must explicitly initialize the|" @@ -1641,12 +1644,10 @@ def err_illegal_union_or_anon_struct_member : Error< "%select{anonymous struct|union}0 member %1 has a non-trivial " - "%select{constructor|copy constructor|move constructor|copy assignment " - "operator|move assignment operator|destructor}2">; + "%sub{select_special_member_kind}2">; def warn_cxx98_compat_nontrivial_union_or_anon_struct_member : Warning< "%select{anonymous struct|union}0 member %1 with a non-trivial " - "%select{constructor|copy constructor|move constructor|copy assignment " - "operator|move assignment operator|destructor}2 is incompatible with C++98">, + "%sub{select_special_member_kind}2 is incompatible with C++98">, InGroup, DefaultIgnore; def note_nontrivial_virtual_dtor : Note< @@ -1665,8 +1666,7 @@ "%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 " - "%select{default constructor|copy constructor|move constructor|" - "copy assignment operator|move assignment operator|destructor}2">; + "%sub{select_special_member_kind}2">; def note_nontrivial_in_class_init : Note< "because field %0 has an initializer">; def note_nontrivial_param_type : Note< @@ -1733,9 +1733,7 @@ // C++ implicit special member functions def note_in_declaration_of_implicit_special_member : Note< - "while declaring the implicit " - "%select{default constructor|copy constructor|move constructor|" - "copy assignment operator|move assignment operator|destructor}1" + "while declaring the implicit %sub{select_special_member_kind}1" " for %0">; // C++ constructors @@ -3527,25 +3525,23 @@ "pass -fshow-overloads=all to show them">; def note_ovl_candidate : Note<"candidate " "%select{function|function|constructor|" - "function |function |constructor |" "is the implicit default constructor|" "is the implicit copy constructor|" "is the implicit move constructor|" "is the implicit copy assignment operator|" "is the implicit move assignment operator|" - "inherited constructor|" - "inherited constructor }0%2" - "%select{| has different class%diff{ (expected $ but has $)|}4,5" - "| has different number of parameters (expected %4 but has %5)" - "| has type mismatch at %ordinal4 parameter" - "%diff{ (expected $ but has $)|}5,6" - "| has different return type%diff{ ($ expected but has $)|}4,5" + "inherited constructor}0%select{| template| %3}1" + "%select{| has different class%diff{ (expected $ but has $)|}5,6" + "| has different number of parameters (expected %5 but has %6)" + "| has type mismatch at %ordinal5 parameter" + "%diff{ (expected $ but has $)|}6,7" + "| has different return type%diff{ ($ expected but has $)|}5,6" "| has different qualifiers (expected " "%select{none|const|restrict|const and restrict|volatile|const and volatile" - "|volatile and restrict|const, volatile, and restrict}4 but found " + "|volatile and restrict|const, volatile, and restrict}5 but found " "%select{none|const|restrict|const and restrict|volatile|const and volatile" - "|volatile and restrict|const, volatile, and restrict}5)" - "| has different exception specification}3">; + "|volatile and restrict|const, volatile, and restrict}6)" + "| has different exception specification}4">; def note_ovl_candidate_inherited_constructor : Note< "constructor from base class %0 inherited here">; @@ -3613,235 +3609,110 @@ def note_ovl_candidate_non_deduced_mismatch_qualified : Note< "candidate template ignored: could not match %q0 against %q1">; -// Note that we don't treat templates differently for this diagnostic. -def note_ovl_candidate_arity : Note<"candidate " - "%select{function|function|constructor|function|function|constructor|" +def select_ovl_candidate_kind : TextSubstitution< + "%select{function|function|constructor|" "constructor (the implicit default constructor)|" "constructor (the implicit copy constructor)|" "constructor (the implicit move constructor)|" "function (the implicit copy assignment operator)|" "function (the implicit move assignment operator)|" - "inherited constructor|" - "inherited constructor}0 %select{|template }1" - "not viable: requires%select{ at least| at most|}2 %3 argument%s3, but %4 " - "%plural{1:was|:were}4 provided">; + "inherited constructor}0%select{| template| %2}1">; + +// Note that we don't treat templates differently for this diagnostic. +def note_ovl_candidate_arity : Note<"candidate " + "%sub{select_ovl_candidate_kind}0,1,2 not viable: " + "requires%select{ at least| at most|}3 %4 argument%s4, but %5 " + "%plural{1:was|:were}5 provided">; def note_ovl_candidate_arity_one : Note<"candidate " - "%select{function|function|constructor|function|function|constructor|" - "constructor (the implicit default constructor)|" - "constructor (the implicit copy constructor)|" - "constructor (the implicit move constructor)|" - "function (the implicit copy assignment operator)|" - "function (the implicit move assignment operator)|" - "inherited constructor|" - "inherited constructor}0 %select{|template }1not viable: " - "%select{requires at least|allows at most single|requires single}2 " - "argument %3, but %plural{0:no|:%4}4 arguments were provided">; + "%sub{select_ovl_candidate_kind}0,1,2 not viable: " + "%select{requires at least|allows at most single|requires single}3 " + "argument %4, but %plural{0:no|:%5}5 arguments were provided">; def note_ovl_candidate_deleted : Note< - "candidate %select{function|function|constructor|" - "function |function |constructor |" - "constructor (the implicit default constructor)|" - "constructor (the implicit copy constructor)|" - "constructor (the implicit move constructor)|" - "function (the implicit copy assignment operator)|" - "function (the implicit move assignment operator)|" - "inherited constructor|" - "inherited constructor }0%1 has been " + "candidate %sub{select_ovl_candidate_kind}0,1,2 has been " "%select{explicitly made unavailable|explicitly deleted|" - "implicitly deleted}2">; + "implicitly deleted}3">; // Giving the index of the bad argument really clutters this message, and // it's relatively unimportant because 1) it's generally obvious which // argument(s) are of the given object type and 2) the fix is usually // to complete the type, which doesn't involve changes to the call line // anyway. If people complain, we can change it. -def note_ovl_candidate_bad_conv_incomplete : Note<"candidate " - "%select{function|function|constructor|" - "function |function |constructor |" - "constructor (the implicit default constructor)|" - "constructor (the implicit copy constructor)|" - "constructor (the implicit move constructor)|" - "function (the implicit copy assignment operator)|" - "function (the implicit move assignment operator)|" - "inherited constructor|" - "inherited constructor }0%1 " - "not viable: cannot convert argument of incomplete type " - "%diff{$ to $|to parameter type}2,3 for " - "%select{%ordinal5 argument|object argument}4" +def note_ovl_candidate_bad_conv_incomplete : Note< + "candidate %sub{select_ovl_candidate_kind}0,1,2 not viable: " + "cannot convert argument of incomplete type " + "%diff{$ to $|to parameter type}3,4 for " + "%select{%ordinal6 argument|object argument}5" "%select{|; dereference the argument with *|" "; take the address of the argument with &|" "; remove *|" - "; remove &}6">; -def note_ovl_candidate_bad_list_argument : Note<"candidate " - "%select{function|function|constructor|" - "function |function |constructor |" - "constructor (the implicit default constructor)|" - "constructor (the implicit copy constructor)|" - "constructor (the implicit move constructor)|" - "function (the implicit copy assignment operator)|" - "function (the implicit move assignment operator)|" - "inherited constructor|" - "inherited constructor }0%1 " - "not viable: cannot convert initializer list argument to %3">; -def note_ovl_candidate_bad_overload : Note<"candidate " - "%select{function|function|constructor|" - "function |function |constructor |" - "constructor (the implicit default constructor)|" - "constructor (the implicit copy constructor)|" - "constructor (the implicit move constructor)|" - "function (the implicit copy assignment operator)|" - "function (the implicit move assignment operator)|" - "inherited constructor|" - "inherited constructor }0%1" - " not viable: no overload of %3 matching %2 for %ordinal4 argument">; -def note_ovl_candidate_bad_conv : Note<"candidate " - "%select{function|function|constructor|" - "function |function |constructor |" - "constructor (the implicit default constructor)|" - "constructor (the implicit copy constructor)|" - "constructor (the implicit move constructor)|" - "function (the implicit copy assignment operator)|" - "function (the implicit move assignment operator)|" - "inherited constructor|" - "inherited constructor }0%1" - " not viable: no known conversion " - "%diff{from $ to $|from argument type to parameter type}2,3 for " - "%select{%ordinal5 argument|object argument}4" + "; remove &}7">; +def note_ovl_candidate_bad_list_argument : Note< + "candidate %sub{select_ovl_candidate_kind}0,1,2 not viable: " + "cannot convert initializer list argument to %4">; +def note_ovl_candidate_bad_overload : Note< + "candidate %sub{select_ovl_candidate_kind}0,1,2 not viable: " + "no overload of %4 matching %3 for %ordinal5 argument">; +def note_ovl_candidate_bad_conv : Note< + "candidate %sub{select_ovl_candidate_kind}0,1,2 not viable: " + "no known conversion " + "%diff{from $ to $|from argument type to parameter type}3,4 for " + "%select{%ordinal6 argument|object argument}5" "%select{|; dereference the argument with *|" "; take the address of the argument with &|" "; remove *|" - "; remove &}6">; -def note_ovl_candidate_bad_arc_conv : Note<"candidate " - "%select{function|function|constructor|" - "function |function |constructor |" - "constructor (the implicit default constructor)|" - "constructor (the implicit copy constructor)|" - "constructor (the implicit move constructor)|" - "function (the implicit copy assignment operator)|" - "function (the implicit move assignment operator)|" - "inherited constructor|" - "inherited constructor }0%1" - " not viable: cannot implicitly convert argument " - "%diff{of type $ to $|type to parameter type}2,3 for " - "%select{%ordinal5 argument|object argument}4 under ARC">; -def note_ovl_candidate_bad_lvalue : Note<"candidate " - "%select{function|function|constructor|" - "function |function |constructor |" - "constructor (the implicit default constructor)|" - "constructor (the implicit copy constructor)|" - "constructor (the implicit move constructor)|" - "function (the implicit copy assignment operator)|" - "function (the implicit move assignment operator)|" - "inherited constructor|" - "inherited constructor }0%1" - " not viable: expects an l-value for " - "%select{%ordinal3 argument|object argument}2">; -def note_ovl_candidate_bad_addrspace : Note<"candidate " - "%select{function|function|constructor|" - "function |function |constructor |" - "constructor (the implicit default constructor)|" - "constructor (the implicit copy constructor)|" - "constructor (the implicit move constructor)|" - "function (the implicit copy assignment operator)|" - "function (the implicit move assignment operator)|" - "inherited constructor|" - "inherited constructor }0%1 not viable: " - "%select{%ordinal6|'this'}5 argument (%2) is in " - "address space %3, but parameter must be in address space %4">; -def note_ovl_candidate_bad_gc : Note<"candidate " - "%select{function|function|constructor|" - "function |function |constructor |" - "constructor (the implicit default constructor)|" - "constructor (the implicit copy constructor)|" - "constructor (the implicit move constructor)|" - "function (the implicit copy assignment operator)|" - "function (the implicit move assignment operator)|" - "inherited constructor|" - "inherited constructor }0%1 not viable: " - "%select{%ordinal6|'this'}5 argument (%2) has %select{no|__weak|__strong}3 " - "ownership, but parameter has %select{no|__weak|__strong}4 ownership">; -def note_ovl_candidate_bad_ownership : Note<"candidate " - "%select{function|function|constructor|" - "function |function |constructor |" - "constructor (the implicit default constructor)|" - "constructor (the implicit copy constructor)|" - "constructor (the implicit move constructor)|" - "function (the implicit copy assignment operator)|" - "function (the implicit move assignment operator)|" - "inherited constructor|" - "inherited constructor }0%1 not viable: " - "%select{%ordinal6|'this'}5 argument (%2) has " - "%select{no|__unsafe_unretained|__strong|__weak|__autoreleasing}3 ownership," + "; remove &}7">; +def note_ovl_candidate_bad_arc_conv : Note< + "candidate %sub{select_ovl_candidate_kind}0,1,2 not viable: " + "cannot implicitly convert argument " + "%diff{of type $ to $|type to parameter type}3,4 for " + "%select{%ordinal6 argument|object argument}5 under ARC">; +def note_ovl_candidate_bad_lvalue : Note< + "candidate %sub{select_ovl_candidate_kind}0,1,2 not viable: " + "expects an l-value for " + "%select{%ordinal4 argument|object argument}3">; +def note_ovl_candidate_bad_addrspace : Note< + "candidate %sub{select_ovl_candidate_kind}0,1,2 not viable: " + "%select{%ordinal7|'this'}6 argument (%3) is in " + "address space %4, but parameter must be in address space %5">; +def note_ovl_candidate_bad_gc : Note< + "candidate %sub{select_ovl_candidate_kind}0,1,2 not viable: " + "%select{%ordinal7|'this'}6 argument (%3) has %select{no|__weak|__strong}4 " + "ownership, but parameter has %select{no|__weak|__strong}5 ownership">; +def note_ovl_candidate_bad_ownership : Note< + "candidate %sub{select_ovl_candidate_kind}0,1,2 not viable: " + "%select{%ordinal7|'this'}6 argument (%3) has " + "%select{no|__unsafe_unretained|__strong|__weak|__autoreleasing}4 ownership," " but parameter has %select{no|__unsafe_unretained|__strong|__weak|" - "__autoreleasing}4 ownership">; -def note_ovl_candidate_bad_cvr_this : Note<"candidate " - "%select{|function|||function|||||" - "function (the implicit copy assignment operator)|" - "function (the implicit move assignment operator)||}0 not viable: " - "'this' argument has type %2, but method is not marked " + "__autoreleasing}5 ownership">; +def note_ovl_candidate_bad_cvr_this : Note< + "candidate %sub{select_ovl_candidate_kind}0,1,2 not viable: " + "'this' argument has type %3, but method is not marked " "%select{const|restrict|const or restrict|volatile|const or volatile|" - "volatile or restrict|const, volatile, or restrict}3">; -def note_ovl_candidate_bad_cvr : Note<"candidate " - "%select{function|function|constructor|" - "function |function |constructor |" - "constructor (the implicit default constructor)|" - "constructor (the implicit copy constructor)|" - "constructor (the implicit move constructor)|" - "function (the implicit copy assignment operator)|" - "function (the implicit move assignment operator)|" - "inherited constructor|" - "inherited constructor }0%1 not viable: " - "%ordinal4 argument (%2) would lose " + "volatile or restrict|const, volatile, or restrict}4">; +def note_ovl_candidate_bad_cvr : Note< + "candidate %sub{select_ovl_candidate_kind}0,1,2 not viable: " + "%ordinal5 argument (%3) would lose " "%select{const|restrict|const and restrict|volatile|const and volatile|" - "volatile and restrict|const, volatile, and restrict}3 qualifier" - "%select{||s||s|s|s}3">; -def note_ovl_candidate_bad_unaligned : Note<"candidate " - "%select{function|function|constructor|" - "function |function |constructor |" - "constructor (the implicit default constructor)|" - "constructor (the implicit copy constructor)|" - "constructor (the implicit move constructor)|" - "function (the implicit copy assignment operator)|" - "function (the implicit move assignment operator)|" - "inherited constructor|" - "inherited constructor }0%1 not viable: " - "%ordinal4 argument (%2) would lose __unaligned qualifier">; -def note_ovl_candidate_bad_base_to_derived_conv : Note<"candidate " - "%select{function|function|constructor|" - "function |function |constructor |" - "constructor (the implicit default constructor)|" - "constructor (the implicit copy constructor)|" - "constructor (the implicit move constructor)|" - "function (the implicit copy assignment operator)|" - "function (the implicit move assignment operator)|" - "inherited constructor|" - "inherited constructor }0%1 not viable: " - "cannot %select{convert from|convert from|bind}2 " - "%select{base class pointer|superclass|base class object of type}2 %3 to " - "%select{derived class pointer|subclass|derived class reference}2 %4 for " - "%ordinal5 argument">; + "volatile and restrict|const, volatile, and restrict}4 qualifier" + "%select{||s||s|s|s}4">; +def note_ovl_candidate_bad_unaligned : Note< + "candidate %sub{select_ovl_candidate_kind}0,1,2 not viable: " + "%ordinal5 argument (%3) would lose __unaligned qualifier">; +def note_ovl_candidate_bad_base_to_derived_conv : Note< + "candidate %sub{select_ovl_candidate_kind}0,1,2 not viable: " + "cannot %select{convert from|convert from|bind}3 " + "%select{base class pointer|superclass|base class object of type}3 %4 to " + "%select{derived class pointer|subclass|derived class reference}3 %5 for " + "%ordinal6 argument">; def note_ovl_candidate_bad_target : Note< - "candidate %select{function|function|constructor|" - "function|function|constructor|" - "constructor (the implicit default constructor)|" - "constructor (the implicit copy constructor)|" - "constructor (the implicit move constructor)|" - "function (the implicit copy assignment operator)|" - "function (the implicit move assignment operator)|" - "inherited constructor|" - "inherited constructor}0 not viable: " + "candidate %sub{select_ovl_candidate_kind}0,1,2 not viable: " "call to " - "%select{__device__|__global__|__host__|__host__ __device__|invalid}1 function from" - " %select{__device__|__global__|__host__|__host__ __device__|invalid}2 function">; + "%select{__device__|__global__|__host__|__host__ __device__|invalid}3 function from" + " %select{__device__|__global__|__host__|__host__ __device__|invalid}4 function">; def note_implicit_member_target_infer_collision : Note< - "implicit %select{" - "default constructor|" - "copy constructor|" - "move constructor|" - "copy assignment operator|" - "move assignment operator|" - "destructor}0 inferred target collision: call to both " + "implicit %sub{select_special_member_kind}0 inferred target collision: call to both " "%select{__device__|__global__|__host__|__host__ __device__}1 and " "%select{__device__|__global__|__host__|__host__ __device__}2 members">; @@ -3883,9 +3754,8 @@ "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|" - "copy constructor|move constructor|copy assignment operator|" - "move assignment operator|destructor}1 is implicitly deleted">; + "assigned|destroyed}1 because its %sub{select_special_member_kind}1 " + "is implicitly deleted">; def err_ovl_no_viable_subscript : Error<"no viable overloaded operator[] for type %0">; def err_ovl_no_oper : @@ -7763,9 +7633,8 @@ "an explicitly-defaulted %select{copy|move}0 assignment operator may not " "have 'const'%select{, 'constexpr'|}1 or 'volatile' qualifiers">; 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">; + "the parameter for an explicitly-defaulted %sub{select_special_member_kind}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">; @@ -7777,17 +7646,13 @@ "the parameter for an explicitly-defaulted copy assignment operator must be an " "lvalue reference type">; 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 " - "calculated one">; + "exception specification of explicitly defaulted " + "%sub{select_special_member_kind}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 " + "defaulted definition of %sub{select_special_member_kind}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 " + "defaulting this %sub{select_special_member_kind}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 " Index: lib/Basic/Diagnostic.cpp =================================================================== --- lib/Basic/Diagnostic.cpp +++ lib/Basic/Diagnostic.cpp @@ -580,6 +580,8 @@ // Skip over 'ValNo' |'s. while (ValNo) { const char *NextVal = ScanFormat(Argument, ArgumentEnd, '|'); + if (NextVal == ArgumentEnd) + llvm::errs() << "WITH ARGUMENT: " << Argument; assert(NextVal != ArgumentEnd && "Value for integer select modifier was" " larger than the number of options in the diagnostic string!"); Argument = NextVal+1; // Skip this string. Index: lib/Sema/SemaOverload.cpp =================================================================== --- lib/Sema/SemaOverload.cpp +++ lib/Sema/SemaOverload.cpp @@ -9326,36 +9326,43 @@ oc_function, oc_method, oc_constructor, - oc_function_template, - oc_method_template, - oc_constructor_template, oc_implicit_default_constructor, oc_implicit_copy_constructor, oc_implicit_move_constructor, oc_implicit_copy_assignment, oc_implicit_move_assignment, - oc_inherited_constructor, - oc_inherited_constructor_template + oc_inherited_constructor }; -static OverloadCandidateKind +enum OverloadCandidateSelect { + ocs_non_template, + ocs_template, + ocs_described_template, +}; + +static std::pair ClassifyOverloadCandidate(Sema &S, NamedDecl *Found, FunctionDecl *Fn, std::string &Description) { bool isTemplate = false; - if (FunctionTemplateDecl *FunTmpl = Fn->getPrimaryTemplate()) { isTemplate = true; Description = S.getTemplateArgumentBindingsText( FunTmpl->getTemplateParameters(), *Fn->getTemplateSpecializationArgs()); } + OverloadCandidateSelect Select = [&]() { + if (!Description.empty()) + return ocs_described_template; + return isTemplate ? ocs_template : ocs_non_template; + }(); + + OverloadCandidateKind Kind = [&]() { if (CXXConstructorDecl *Ctor = dyn_cast(Fn)) { if (!Ctor->isImplicit()) { if (isa(Found)) - return isTemplate ? oc_inherited_constructor_template - : oc_inherited_constructor; + return oc_inherited_constructor; else - return isTemplate ? oc_constructor_template : oc_constructor; + return oc_constructor; } if (Ctor->isDefaultConstructor()) @@ -9373,7 +9380,7 @@ // This actually gets spelled 'candidate function' for now, but // it doesn't hurt to split it out. if (!Meth->isImplicit()) - return isTemplate ? oc_method_template : oc_method; + return oc_method; if (Meth->isMoveAssignmentOperator()) return oc_implicit_move_assignment; @@ -9385,7 +9392,10 @@ return oc_method; } - return isTemplate ? oc_function_template : oc_function; + return oc_function; + }(); + + return std::make_pair(Kind, Select); } void MaybeEmitInheritedConstructorNote(Sema &S, Decl *FoundDecl) { @@ -9477,9 +9487,11 @@ return; std::string FnDesc; - OverloadCandidateKind K = ClassifyOverloadCandidate(*this, Found, Fn, FnDesc); + std::pair KSPair = + ClassifyOverloadCandidate(*this, Found, Fn, FnDesc); PartialDiagnostic PD = PDiag(diag::note_ovl_candidate) - << (unsigned) K << Fn << FnDesc; + << (unsigned)KSPair.first << (unsigned)KSPair.second + << Fn << FnDesc; HandleFunctionTypeMismatch(PD, Fn->getType(), DestType); Diag(Fn->getLocation(), PD); @@ -9553,7 +9565,7 @@ } std::string FnDesc; - OverloadCandidateKind FnKind = + std::pair FnKindPair = ClassifyOverloadCandidate(S, Cand->FoundDecl, Fn, FnDesc); Expr *FromExpr = Conv.Bad.FromExpr; @@ -9568,9 +9580,9 @@ DeclarationName Name = cast(E)->getName(); S.Diag(Fn->getLocation(), diag::note_ovl_candidate_bad_overload) - << (unsigned) FnKind << FnDesc - << (FromExpr ? FromExpr->getSourceRange() : SourceRange()) - << ToTy << Name << I+1; + << (unsigned)FnKindPair.first << (unsigned)FnKindPair.second << FnDesc + << (FromExpr ? FromExpr->getSourceRange() : SourceRange()) << ToTy + << Name << I + 1; MaybeEmitInheritedConstructorNote(S, Cand->FoundDecl); return; } @@ -9597,9 +9609,8 @@ if (FromQs.getAddressSpace() != ToQs.getAddressSpace()) { S.Diag(Fn->getLocation(), diag::note_ovl_candidate_bad_addrspace) - << (unsigned) FnKind << FnDesc - << (FromExpr ? FromExpr->getSourceRange() : SourceRange()) - << FromTy + << (unsigned)FnKindPair.first << (unsigned)FnKindPair.second << FnDesc + << (FromExpr ? FromExpr->getSourceRange() : SourceRange()) << FromTy << FromQs.getAddressSpaceAttributePrintValue() << ToQs.getAddressSpaceAttributePrintValue() << (unsigned)isObjectArgument << I + 1; @@ -9609,9 +9620,8 @@ if (FromQs.getObjCLifetime() != ToQs.getObjCLifetime()) { S.Diag(Fn->getLocation(), diag::note_ovl_candidate_bad_ownership) - << (unsigned) FnKind << FnDesc - << (FromExpr ? FromExpr->getSourceRange() : SourceRange()) - << FromTy + << (unsigned)FnKindPair.first << (unsigned)FnKindPair.second << FnDesc + << (FromExpr ? FromExpr->getSourceRange() : SourceRange()) << FromTy << FromQs.getObjCLifetime() << ToQs.getObjCLifetime() << (unsigned)isObjectArgument << I + 1; MaybeEmitInheritedConstructorNote(S, Cand->FoundDecl); @@ -9620,9 +9630,8 @@ if (FromQs.getObjCGCAttr() != ToQs.getObjCGCAttr()) { S.Diag(Fn->getLocation(), diag::note_ovl_candidate_bad_gc) - << (unsigned) FnKind << FnDesc - << (FromExpr ? FromExpr->getSourceRange() : SourceRange()) - << FromTy + << (unsigned)FnKindPair.first << (unsigned)FnKindPair.second << FnDesc + << (FromExpr ? FromExpr->getSourceRange() : SourceRange()) << FromTy << FromQs.getObjCGCAttr() << ToQs.getObjCGCAttr() << (unsigned)isObjectArgument << I + 1; MaybeEmitInheritedConstructorNote(S, Cand->FoundDecl); @@ -9631,9 +9640,9 @@ if (FromQs.hasUnaligned() != ToQs.hasUnaligned()) { S.Diag(Fn->getLocation(), diag::note_ovl_candidate_bad_unaligned) - << (unsigned) FnKind << FnDesc - << (FromExpr ? FromExpr->getSourceRange() : SourceRange()) - << FromTy << FromQs.hasUnaligned() << I+1; + << (unsigned)FnKindPair.first << (unsigned)FnKindPair.second << FnDesc + << (FromExpr ? FromExpr->getSourceRange() : SourceRange()) << FromTy + << FromQs.hasUnaligned() << I + 1; MaybeEmitInheritedConstructorNote(S, Cand->FoundDecl); return; } @@ -9643,14 +9652,14 @@ if (isObjectArgument) { S.Diag(Fn->getLocation(), diag::note_ovl_candidate_bad_cvr_this) - << (unsigned) FnKind << FnDesc - << (FromExpr ? FromExpr->getSourceRange() : SourceRange()) - << FromTy << (CVR - 1); + << (unsigned)FnKindPair.first << (unsigned)FnKindPair.second << FnDesc + << (FromExpr ? FromExpr->getSourceRange() : SourceRange()) << FromTy + << (CVR - 1); } else { S.Diag(Fn->getLocation(), diag::note_ovl_candidate_bad_cvr) - << (unsigned) FnKind << FnDesc - << (FromExpr ? FromExpr->getSourceRange() : SourceRange()) - << FromTy << (CVR - 1) << I+1; + << (unsigned)FnKindPair.first << (unsigned)FnKindPair.second << FnDesc + << (FromExpr ? FromExpr->getSourceRange() : SourceRange()) << FromTy + << (CVR - 1) << I + 1; } MaybeEmitInheritedConstructorNote(S, Cand->FoundDecl); return; @@ -9660,9 +9669,9 @@ // telling the user that it has type void is not useful. if (FromExpr && isa(FromExpr)) { S.Diag(Fn->getLocation(), diag::note_ovl_candidate_bad_list_argument) - << (unsigned) FnKind << FnDesc - << (FromExpr ? FromExpr->getSourceRange() : SourceRange()) - << FromTy << ToTy << (unsigned) isObjectArgument << I+1; + << (unsigned)FnKindPair.first << (unsigned)FnKindPair.second << FnDesc + << (FromExpr ? FromExpr->getSourceRange() : SourceRange()) << FromTy + << ToTy << (unsigned)isObjectArgument << I + 1; MaybeEmitInheritedConstructorNote(S, Cand->FoundDecl); return; } @@ -9676,9 +9685,9 @@ if (TempFromTy->isIncompleteType()) { // Emit the generic diagnostic and, optionally, add the hints to it. S.Diag(Fn->getLocation(), diag::note_ovl_candidate_bad_conv_incomplete) - << (unsigned) FnKind << FnDesc - << (FromExpr ? FromExpr->getSourceRange() : SourceRange()) - << FromTy << ToTy << (unsigned) isObjectArgument << I+1 + << (unsigned)FnKindPair.first << (unsigned)FnKindPair.second << FnDesc + << (FromExpr ? FromExpr->getSourceRange() : SourceRange()) << FromTy + << ToTy << (unsigned)isObjectArgument << I + 1 << (unsigned)(Cand->Fix.Kind); MaybeEmitInheritedConstructorNote(S, Cand->FoundDecl); @@ -9717,21 +9726,19 @@ ToTy.getNonReferenceType().getCanonicalType() == FromTy.getNonReferenceType().getCanonicalType()) { S.Diag(Fn->getLocation(), diag::note_ovl_candidate_bad_lvalue) - << (unsigned) FnKind << FnDesc - << (FromExpr ? FromExpr->getSourceRange() : SourceRange()) - << (unsigned) isObjectArgument << I + 1; + << (unsigned)FnKindPair.first << (unsigned)FnKindPair.second << FnDesc + << (unsigned)isObjectArgument << I + 1 + << (FromExpr ? FromExpr->getSourceRange() : SourceRange()); MaybeEmitInheritedConstructorNote(S, Cand->FoundDecl); return; } } if (BaseToDerivedConversion) { - S.Diag(Fn->getLocation(), - diag::note_ovl_candidate_bad_base_to_derived_conv) - << (unsigned) FnKind << FnDesc + S.Diag(Fn->getLocation(), diag::note_ovl_candidate_bad_base_to_derived_conv) + << (unsigned)FnKindPair.first << (unsigned)FnKindPair.second << FnDesc << (FromExpr ? FromExpr->getSourceRange() : SourceRange()) - << (BaseToDerivedConversion - 1) - << FromTy << ToTy << I+1; + << (BaseToDerivedConversion - 1) << FromTy << ToTy << I + 1; MaybeEmitInheritedConstructorNote(S, Cand->FoundDecl); return; } @@ -9742,8 +9749,8 @@ Qualifiers ToQs = CToTy.getQualifiers(); if (FromQs.getObjCLifetime() != ToQs.getObjCLifetime()) { S.Diag(Fn->getLocation(), diag::note_ovl_candidate_bad_arc_conv) - << (unsigned) FnKind << FnDesc - << (FromExpr ? FromExpr->getSourceRange() : SourceRange()) + << (unsigned)FnKindPair.first << (unsigned)FnKindPair.second + << FnDesc << (FromExpr ? FromExpr->getSourceRange() : SourceRange()) << FromTy << ToTy << (unsigned)isObjectArgument << I + 1; MaybeEmitInheritedConstructorNote(S, Cand->FoundDecl); return; @@ -9756,9 +9763,9 @@ // Emit the generic diagnostic and, optionally, add the hints to it. PartialDiagnostic FDiag = S.PDiag(diag::note_ovl_candidate_bad_conv); - FDiag << (unsigned) FnKind << FnDesc - << (FromExpr ? FromExpr->getSourceRange() : SourceRange()) - << FromTy << ToTy << (unsigned) isObjectArgument << I + 1 + FDiag << (unsigned)FnKindPair.first << (unsigned)FnKindPair.second << FnDesc + << (FromExpr ? FromExpr->getSourceRange() : SourceRange()) << FromTy + << ToTy << (unsigned)isObjectArgument << I + 1 << (unsigned)(Cand->Fix.Kind); // If we can fix the conversion, suggest the FixIts. @@ -9832,17 +9839,17 @@ } std::string Description; - OverloadCandidateKind FnKind = + std::pair FnKindPair = ClassifyOverloadCandidate(S, Found, Fn, Description); if (modeCount == 1 && Fn->getParamDecl(0)->getDeclName()) S.Diag(Fn->getLocation(), diag::note_ovl_candidate_arity_one) - << (unsigned) FnKind << (Fn->getDescribedFunctionTemplate() != nullptr) - << mode << Fn->getParamDecl(0) << NumFormalArgs; + << (unsigned)FnKindPair.first << (unsigned)FnKindPair.second + << Description << mode << Fn->getParamDecl(0) << NumFormalArgs; else S.Diag(Fn->getLocation(), diag::note_ovl_candidate_arity) - << (unsigned) FnKind << (Fn->getDescribedFunctionTemplate() != nullptr) - << mode << modeCount << NumFormalArgs; + << (unsigned)FnKindPair.first << (unsigned)FnKindPair.second + << Description << mode << modeCount << NumFormalArgs; MaybeEmitInheritedConstructorNote(S, Found); } @@ -10117,11 +10124,13 @@ CalleeTarget = S.IdentifyCUDATarget(Callee); std::string FnDesc; - OverloadCandidateKind FnKind = + std::pair FnKindPair = ClassifyOverloadCandidate(S, Cand->FoundDecl, Callee, FnDesc); S.Diag(Callee->getLocation(), diag::note_ovl_candidate_bad_target) - << (unsigned)FnKind << CalleeTarget << CallerTarget; + << (unsigned)FnKindPair.first << (unsigned)ocs_non_template + << FnDesc /* Ignored */ + << CalleeTarget << CallerTarget; // This could be an implicit constructor for which we could not infer the // target due to a collsion. Diagnose that case. @@ -10130,7 +10139,7 @@ CXXRecordDecl *ParentClass = Meth->getParent(); Sema::CXXSpecialMember CSM; - switch (FnKind) { + switch (FnKindPair.first) { default: return; case oc_implicit_default_constructor: @@ -10202,11 +10211,11 @@ if (Cand->Viable) { if (Fn->isDeleted() || S.isFunctionConsideredUnavailable(Fn)) { std::string FnDesc; - OverloadCandidateKind FnKind = + std::pair FnKindPair = ClassifyOverloadCandidate(S, Cand->FoundDecl, Fn, FnDesc); S.Diag(Fn->getLocation(), diag::note_ovl_candidate_deleted) - << FnKind << FnDesc + << (unsigned)FnKindPair.first << (unsigned)FnKindPair.second << FnDesc << (Fn->isDeleted() ? (Fn->isDeletedAsWritten() ? 1 : 2) : 0); MaybeEmitInheritedConstructorNote(S, Cand->FoundDecl); return; @@ -11103,7 +11112,7 @@ S.PDiag(diag::err_addr_ovl_ambiguous) << Matches[0].second->getDeclName(), S.PDiag(diag::note_ovl_candidate) - << (unsigned)oc_function_template, + << (unsigned)oc_function << (unsigned)ocs_described_template, Complain, TargetFunctionType); if (Result != MatchesCopy.end()) { Index: test/SemaCXX/anonymous-struct.cpp =================================================================== --- test/SemaCXX/anonymous-struct.cpp +++ test/SemaCXX/anonymous-struct.cpp @@ -16,7 +16,7 @@ struct { S x; #if __cplusplus <= 199711L - // expected-error@-2 {{anonymous struct member 'x' has a non-trivial constructor}} + // expected-error@-2 {{anonymous struct member 'x' has a non-trivial default constructor}} #endif }; static struct { Index: test/SemaCXX/cxx98-compat.cpp =================================================================== --- test/SemaCXX/cxx98-compat.cpp +++ test/SemaCXX/cxx98-compat.cpp @@ -239,13 +239,13 @@ ~NonTrivDtor(); // expected-note 2{{user-provided destructor}} }; union BadUnion { - NonTrivCtor ntc; // expected-warning {{union member 'ntc' with a non-trivial constructor is incompatible with C++98}} + NonTrivCtor ntc; // expected-warning {{union member 'ntc' with a non-trivial default constructor is incompatible with C++98}} NonTrivCopy ntcp; // expected-warning {{union member 'ntcp' with a non-trivial copy constructor is incompatible with C++98}} NonTrivDtor ntd; // expected-warning {{union member 'ntd' with a non-trivial destructor is incompatible with C++98}} }; struct Wrap { struct { - NonTrivCtor ntc; // expected-warning {{anonymous struct member 'ntc' with a non-trivial constructor is incompatible with C++98}} + NonTrivCtor ntc; // expected-warning {{anonymous struct member 'ntc' with a non-trivial default constructor is incompatible with C++98}} NonTrivCopy ntcp; // expected-warning {{anonymous struct member 'ntcp' with a non-trivial copy constructor is incompatible with C++98}} NonTrivDtor ntd; // expected-warning {{anonymous struct member 'ntd' with a non-trivial destructor is incompatible with C++98}} }; @@ -346,7 +346,7 @@ }; union S { - X x; // expected-warning{{union member 'x' with a non-trivial constructor is incompatible with C++98}} + X x; // expected-warning{{union member 'x' with a non-trivial default constructor is incompatible with C++98}} }; } Index: test/TableGen/DiagnosticBase.inc =================================================================== --- test/TableGen/DiagnosticBase.inc +++ test/TableGen/DiagnosticBase.inc @@ -1,35 +1,130 @@ -// Define the diagnostic mappings. -class DiagMapping; -def MAP_IGNORE : DiagMapping; -def MAP_WARNING : DiagMapping; -def MAP_ERROR : DiagMapping; -def MAP_FATAL : DiagMapping; +//===--- Diagnostic.td - C Language Family Diagnostic Handling ------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines the TableGen core definitions for the diagnostics +// and diagnostic control. +// +//===----------------------------------------------------------------------===// + +// See the Internals Manual, section The Diagnostics Subsystem for an overview. + +// Define the diagnostic severities. +class Severity { + string Name = N; +} +def SEV_Ignored : Severity<"Ignored">; +def SEV_Remark : Severity<"Remark">; +def SEV_Warning : Severity<"Warning">; +def SEV_Error : Severity<"Error">; +def SEV_Fatal : Severity<"Fatal">; // Define the diagnostic classes. class DiagClass; def CLASS_NOTE : DiagClass; +def CLASS_REMARK : DiagClass; def CLASS_WARNING : DiagClass; def CLASS_EXTENSION : DiagClass; def CLASS_ERROR : DiagClass; +// Responses to a diagnostic in a SFINAE context. +class SFINAEResponse; +def SFINAE_SubstitutionFailure : SFINAEResponse; +def SFINAE_Suppress : SFINAEResponse; +def SFINAE_Report : SFINAEResponse; +def SFINAE_AccessControl : SFINAEResponse; + +// Textual substitutions which may be performed on the text of diagnostics +class TextSubstitution { + string Substitution = Text; + // TODO: These are only here to allow substitutions to be declared inline with + // diagnostics + string Component = ""; + string CategoryName = ""; +} + +// Diagnostic Categories. These can be applied to groups or individual +// diagnostics to specify a category. +class DiagCategory { + string CategoryName = Name; +} + +// Diagnostic Groups. class DiagGroup subgroups = []> { string GroupName = Name; list SubGroups = subgroups; string CategoryName = ""; + code Documentation = [{}]; } class InGroup { DiagGroup Group = G; } +//class IsGroup { DiagGroup Group = DiagGroup; } + +include "DiagnosticDocs.inc" // All diagnostics emitted by the compiler are an indirect subclass of this. -class Diagnostic { +class Diagnostic { + /// Component is specified by the file with a big let directive. + string Component = ?; string Text = text; DiagClass Class = DC; - DiagMapping DefaultMapping = defaultmapping; + SFINAEResponse SFINAE = SFINAE_Suppress; + bit AccessControl = 0; + bit WarningNoWerror = 0; + bit ShowInSystemHeader = 0; + Severity DefaultSeverity = defaultmapping; DiagGroup Group; string CategoryName = ""; } -class Error : Diagnostic; -class Warning : Diagnostic; -class Extension : Diagnostic; -class ExtWarn : Diagnostic; -class Note : Diagnostic; +class SFINAEFailure { + SFINAEResponse SFINAE = SFINAE_SubstitutionFailure; +} +class NoSFINAE { + SFINAEResponse SFINAE = SFINAE_Report; +} +class AccessControl { + SFINAEResponse SFINAE = SFINAE_AccessControl; +} + +class ShowInSystemHeader { + bit ShowInSystemHeader = 1; +} + +class SuppressInSystemHeader { + bit ShowInSystemHeader = 0; +} + +// FIXME: ExtWarn and Extension should also be SFINAEFailure by default. +class Error : Diagnostic, SFINAEFailure { + bit ShowInSystemHeader = 1; +} +// Warnings default to on (but can be default-off'd with DefaultIgnore). +// This is used for warnings about questionable code; warnings about +// accepted language extensions should use Extension or ExtWarn below instead. +class Warning : Diagnostic; +// Remarks can be turned on with -R flags and provide commentary, e.g. on +// optimizer decisions. +class Remark : Diagnostic; +// Extensions are warnings about accepted language extensions. +// Extension warnings are default-off but enabled by -pedantic. +class Extension : Diagnostic; +// ExtWarns are warnings about accepted language extensions. +// ExtWarn warnings are default-on. +class ExtWarn : Diagnostic; +// Notes can provide supplementary information on errors, warnings, and remarks. +class Note : Diagnostic; + + +class DefaultIgnore { Severity DefaultSeverity = SEV_Ignored; } +class DefaultWarn { Severity DefaultSeverity = SEV_Warning; } +class DefaultError { Severity DefaultSeverity = SEV_Error; } +class DefaultFatal { Severity DefaultSeverity = SEV_Fatal; } +class DefaultWarnNoWerror { + bit WarningNoWerror = 1; +} +class DefaultRemark { Severity DefaultSeverity = SEV_Remark; } Index: test/TableGen/DiagnosticDocs.inc =================================================================== --- /dev/null +++ test/TableGen/DiagnosticDocs.inc @@ -0,0 +1,75 @@ + +def GlobalDocumentation { + code Intro =[{.. + ------------------------------------------------------------------- + NOTE: This file is automatically generated by running clang-tblgen + -gen-diag-docs. Do not edit this file by hand!! + ------------------------------------------------------------------- + +.. Add custom CSS to output. FIXME: This should be put into rather + than the start of . +.. raw:: html + + + +.. FIXME: rST doesn't support formatting this, so we format all elements + as monospace font face instead. +.. |nbsp| unicode:: 0xA0 + :trim: + +.. Roles generated by clang-tblgen. +.. role:: error +.. role:: warning +.. role:: remark +.. role:: diagtext +.. role:: placeholder(emphasis) + +========================= +Diagnostic flags in Clang +========================= +.. contents:: + :local: + +Introduction +============ + +This page lists the diagnostic flags currently supported by Clang. + +Diagnostic flags +================ +}]; +} Index: test/TableGen/emit-diag-docs.td =================================================================== --- /dev/null +++ test/TableGen/emit-diag-docs.td @@ -0,0 +1,78 @@ +// RUN: clang-tblgen -gen-diag-docs -I%S %s -o - 2>&1 | \ +// RUN: FileCheck --strict-whitespace %s +include "DiagnosticBase.inc" + +def MyGroup : DiagGroup<"MyGroupName">; + +def MyKinds : TextSubstitution<"%select{food|forests}0">; +def MyGoodBad : TextSubstitution<"%select{good|bad}0">; +def MySubNested : TextSubstitution<"%sub{MyGoodBad}1 %sub{MyKinds}2 are %sub{MyGoodBad}1 according to %0">; + +// CHECK: -WMyGroupName +// CHECK: **Diagnostic text:** + +let Group = MyGroup in { + +// CHECK: |:warning:`warning:` |nbsp| :diagtext:`this is my diff text`| +// CHECK-NEXT: +-----------------------------------------------------------+ +def CheckDiff : Warning<"%diff{$ is not $|this is my diff text}0,1">; + + +// CHECK: |:warning:`warning:` |nbsp| :placeholder:`A` |nbsp| :diagtext:`is my modifier test` |nbsp| :placeholder:`B`| +// CHECK-NEXT: +----------------------------------------------------------------------------------------------------------+ +def CheckModifier : Warning<"%0 is my modifier test %1">; + + +// CHECK: |:warning:`warning:` |nbsp| :diagtext:`This is the` |nbsp| :placeholder:`A` |nbsp| :diagtext:`test I've written`| +// CHECK-NEXT: +---------------------------------------------------------------------------------------------------------------+ +def CheckOrdinal : Warning<"This is the %ordinal0 test I've written">; + +// CHECK: |:warning:`warning:` |nbsp| :diagtext:`I wrote` |nbsp| |+----------------+| |nbsp| :diagtext:`tests`| +// CHECK-NEXT: | ||:diagtext:`no` || | +// CHECK-NEXT: | |+----------------+| | +// CHECK-NEXT: | ||:diagtext:`one` || | +// CHECK-NEXT: | |+----------------+| | +// CHECK-NEXT: | ||:placeholder:`A`|| | +// CHECK-NEXT: | |+----------------+| | +// CHECK-NEXT: +------------------------------------------------------+------------------+-------------------------+ +def CheckPlural : Warning<"I wrote %plural{0:no|1:one|:%0}0 tests">; + + +// CHECK: |:warning:`warning:` |nbsp| :diagtext:`bad type` |nbsp| :placeholder:`A`| +// CHECK-NEXT: +-----------------------------------------------------------------------+ +def CheckQ : Warning<"bad type %q0">; + + +// CHECK: |:warning:`warning:` |nbsp| :diagtext:`My test`|+-------------+| |nbsp| :diagtext:`are the best!`| +// CHECK-NEXT: | || || | +// CHECK-NEXT: | |+-------------+| | +// CHECK-NEXT: | ||:diagtext:`s`|| | +// CHECK-NEXT: | |+-------------+| | +// CHECK-NEXT: +----------------------------------------------+---------------+---------------------------------+ +def CheckS : Warning<"My test%s0 are the best!">; + + +// CHECK: |:warning:`warning:` |nbsp| :diagtext:`this is my select test:` |nbsp| |+---------------+| +// CHECK-NEXT: | ||:diagtext:`one`|| +// CHECK-NEXT: | |+---------------+| +// CHECK-NEXT: | ||:diagtext:`two`|| +// CHECK-NEXT: | |+---------------+| +// CHECK-NEXT: +----------------------------------------------------------------------+-----------------+ +def CheckSelect : Warning<"this is my select test: %select{one|two}0 and it is %select{good|bad}1">; + + +// CHECK: +-------------------------------------------------------+------------------+--------+---------------------+-------------------------------+------------------+--------------------------------------------------------+ +// CHECK-NEXT: |:warning:`warning:` |nbsp| :diagtext:`They say` |nbsp| |+----------------+| |nbsp| |+-------------------+| |nbsp| :diagtext:`are` |nbsp| |+----------------+| |nbsp| :diagtext:`according to` |nbsp| :placeholder:`D`| +// CHECK-NEXT: | ||:diagtext:`good`|| ||:diagtext:`food` || ||:diagtext:`good`|| | +// CHECK-NEXT: | |+----------------+| |+-------------------+| |+----------------+| | +// CHECK-NEXT: | ||:diagtext:`bad` || ||:diagtext:`forests`|| ||:diagtext:`bad` || | +// CHECK-NEXT: | |+----------------+| |+-------------------+| |+----------------+| | +// CHECK-NEXT: +-------------------------------------------------------+------------------+--------+---------------------+-------------------------------+------------------+--------------------------------------------------------+ +def CheckSubstitution : Warning<"They say %sub{MySubNested}3,1,0">; + + +// CHECK: |:warning:`warning:` |nbsp| :diagtext:`this is my warning text`| +// CHECK-NEXT: +--------------------------------------------------------------+ +def CheckText : Warning<"this is my warning text">; + +} Index: test/TableGen/text-substitution.td =================================================================== --- /dev/null +++ test/TableGen/text-substitution.td @@ -0,0 +1,40 @@ +// RUN: clang-tblgen -gen-clang-diags-defs -I%S %s -o - 2>&1 | \ +// RUN: FileCheck --strict-whitespace %s +include "DiagnosticBase.inc" + +def yes_no : TextSubstitution<"%select{yes|no}0">; +def says_yes : TextSubstitution<"%1 says %sub{yes_no}0">; + + +def sub_test_rewrite : TextSubstitution< + "SELECT! %select{one|two}3. " + "DIFF! %diff{$ is $|or not}0,1. " + "PLURAL! %plural{0:zero items|[1,2]:one or two item|:multiple items}2. " + "ORDINAL! %ordinal1. " + "S! item%s2. " + "Q! %q4. " + "PLACEHOLDER! %5." + "OBJCCLASS! %objcclass0. " + "OBJCINSTANCE! %objcinstance1. ">; + +// CHECK: DIAG(test_rewrite, +// CHECK-SAME: SELECT! %select{one|two}2. +// CHECK-SAME: DIFF! %diff{$ is $|or not}5,4. +// CHECK-SAME: PLURAL! %plural{0:zero items|[1,2]:one or two item|:multiple items}3. +// CHECK-SAME: ORDINAL! %ordinal4. +// CHECK-SAME: S! item%s3. +// CHECK-SAME: Q! %q1. +// CHECK-SAME: PLACEHOLDER! %0.OBJCCLASS! +// CHECK-SAME: %objcclass5. OBJCINSTANCE! +// CHECK-SAME: %objcinstance4. DONE!", +def test_rewrite: Error<"%sub{sub_test_rewrite}5,4,3,2,1,0 DONE!">; + +def test_sub_basic : Error<"%sub{yes_no}0">; +// CHECK: test_sub_basic +// CHECK-SAME: "%select{yes|no}0", + +def test_sub_nested : Error<"%sub{says_yes}2,4">; +// CHECK: test_sub_nested +// CHECK-SAME: "%4 says %select{yes|no}2", + + Index: test/lit.cfg.py =================================================================== --- test/lit.cfg.py +++ test/lit.cfg.py @@ -57,7 +57,8 @@ tool_dirs = [config.clang_tools_dir, config.llvm_tools_dir] tools = [ - 'c-index-test', 'clang-check', 'clang-diff', 'clang-format', 'opt', + 'c-index-test', 'clang-check', 'clang-diff', 'clang-format', 'clang-tblgen', + 'opt', ToolSubst('%clang_func_map', command=FindTool( 'clang-func-mapping'), unresolved='ignore'), ] Index: utils/TableGen/ClangDiagnosticsEmitter.cpp =================================================================== --- utils/TableGen/ClangDiagnosticsEmitter.cpp +++ utils/TableGen/ClangDiagnosticsEmitter.cpp @@ -14,12 +14,13 @@ #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/PointerUnion.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringMap.h" -#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/Twine.h" +#include "llvm/Support/Casting.h" #include "llvm/TableGen/Error.h" #include "llvm/TableGen/Record.h" #include "llvm/TableGen/StringToOffsetTable.h" @@ -441,6 +442,722 @@ } } +namespace { +enum PieceKind { + MultiPieceClass, + TextPieceClass, + PlaceholderPieceClass, + SelectPieceClass, + PluralPieceClass, + DiffPieceClass, + SubstitutionPieceClass, +}; + +enum ModifierType { + MT_Unknown, + MT_Placeholder, + MT_Select, + MT_Sub, + MT_Plural, + MT_Diff, + MT_Ordinal, + MT_S, + MT_Q, + MT_ObjCClass, + MT_ObjCInstance, +}; + +static StringRef getModifierName(ModifierType MT) { + switch (MT) { + case MT_Select: + return "select"; + case MT_Sub: + return "sub"; + case MT_Diff: + return "diff"; + case MT_Plural: + return "plural"; + case MT_Ordinal: + return "ordinal"; + case MT_S: + return "s"; + case MT_Q: + return "q"; + case MT_Placeholder: + return ""; + case MT_ObjCClass: + return "objcclass"; + case MT_ObjCInstance: + return "objcinstance"; + case MT_Unknown: + llvm_unreachable("invalid modifier type"); + } +} + +struct Piece { + // This type and its derived classes are move-only. + Piece(PieceKind Kind) : ClassKind(Kind) {} + Piece(Piece const &O) = delete; + Piece &operator=(Piece const &) = delete; + virtual ~Piece() {} + + PieceKind getPieceClass() const { return ClassKind; } + static bool classof(const Piece *) { return true; } + +private: + PieceKind ClassKind; +}; + +struct MultiPiece : Piece { + MultiPiece() : Piece(MultiPieceClass) {} + MultiPiece(std::vector Pieces) + : Piece(MultiPieceClass), Pieces(std::move(Pieces)) {} + + std::vector Pieces; + + static bool classof(const Piece *P) { + return P->getPieceClass() == MultiPieceClass; + } +}; + +struct TextPiece : Piece { + StringRef Role; + std::string Text; + TextPiece(StringRef Text, StringRef Role = "") + : Piece(TextPieceClass), Role(Role), Text(Text.str()) {} + + static bool classof(const Piece *P) { + return P->getPieceClass() == TextPieceClass; + } +}; + +struct PlaceholderPiece : Piece { + ModifierType Kind; + int Index; + PlaceholderPiece(ModifierType Kind, int Index) + : Piece(PlaceholderPieceClass), Kind(Kind), Index(Index) {} + + static bool classof(const Piece *P) { + return P->getPieceClass() == PlaceholderPieceClass; + } +}; + +struct SelectPiece : Piece { +protected: + SelectPiece(PieceKind Kind, ModifierType ModKind) + : Piece(Kind), ModKind(ModKind) {} + +public: + SelectPiece(ModifierType ModKind) : SelectPiece(SelectPieceClass, ModKind) {} + + ModifierType ModKind; + std::vector Options; + unsigned Index; + + static bool classof(const Piece *P) { + return P->getPieceClass() == SelectPieceClass || + P->getPieceClass() == PluralPieceClass; + } +}; + +struct PluralPiece : SelectPiece { + PluralPiece() : SelectPiece(PluralPieceClass, MT_Plural) {} + + std::vector OptionPrefixes; + unsigned Index; + + static bool classof(const Piece *P) { + return P->getPieceClass() == PluralPieceClass; + } +}; + +struct DiffPiece : Piece { + DiffPiece() : Piece(DiffPieceClass) {} + + Piece *Options[2] = {}; + unsigned Indexes[2] = {}; + + static bool classof(const Piece *P) { + return P->getPieceClass() == DiffPieceClass; + } +}; + +struct SubstitutionPiece : Piece { + SubstitutionPiece() : Piece(SubstitutionPieceClass) {} + + std::string Name; + std::vector Modifiers; + + static bool classof(const Piece *P) { + return P->getPieceClass() == SubstitutionPieceClass; + } +}; + +/// Diagnostic text, parsed into pieces. + + +struct DiagnosticTextBuilder { + DiagnosticTextBuilder(DiagnosticTextBuilder const &) = delete; + DiagnosticTextBuilder &operator=(DiagnosticTextBuilder const &) = delete; + + DiagnosticTextBuilder(RecordKeeper &Records) { + // Build up the list of substitution records. + for (auto *S : Records.getAllDerivedDefinitions("TextSubstitution")) { + EvaluatingRecordGuard Guard(&EvaluatingRecord, S); + Substitutions.try_emplace( + S->getName(), DiagText(*this, S->getValueAsString("Substitution"))); + } + } + + std::vector buildForDocumentation(StringRef Role, + const Record *R); + std::string buildForDefinition(const Record *R); + + Piece *getSubstitution(SubstitutionPiece *S) const { + auto It = Substitutions.find(S->Name); + if (It == Substitutions.end()) + PrintFatalError("Failed to find substitution with name: " + S->Name); + return It->second.Root; + } + + void PrintFatalError(llvm::Twine const &Msg) const { + assert(EvaluatingRecord && "not evaluating a record?"); + llvm::PrintFatalError(EvaluatingRecord->getLoc(), Msg); + } + +private: + struct DiagText { + DiagnosticTextBuilder &Builder; + std::vector AllocatedPieces; + Piece *Root = nullptr; + + template T *New(Args &&... args) { + static_assert(std::is_base_of::value, "must be piece"); + T *Mem = new T(std::forward(args)...); + AllocatedPieces.push_back(Mem); + return Mem; + } + + DiagText(DiagnosticTextBuilder &Builder, StringRef Text) + : Builder(Builder), Root(parseDiagText(Text)) {} + + Piece *parseDiagText(StringRef &Text, bool Nested = false); + unsigned parseModifier(StringRef &) const; + + public: + DiagText(DiagText &&O) noexcept + : Builder(O.Builder), AllocatedPieces(std::move(O.AllocatedPieces)), + Root(O.Root) { + O.Root = nullptr; + } + + ~DiagText() { + for (Piece *P : AllocatedPieces) + delete P; + } + }; + +private: + const Record *EvaluatingRecord = nullptr; + struct EvaluatingRecordGuard { + EvaluatingRecordGuard(const Record **Dest, const Record *New) + : Dest(Dest), Old(*Dest) { + *Dest = New; + } + ~EvaluatingRecordGuard() { *Dest = Old; } + const Record **Dest; + const Record *Old; + }; + + StringMap Substitutions; +}; + +template struct DiagTextVisitor { + using ModifierMappingsType = Optional>; + +private: + Derived &getDerived() { return static_cast(*this); } + +public: + std::vector + getSubstitutionMappings(SubstitutionPiece *P, + const ModifierMappingsType &Mappings) const { + std::vector NewMappings; + for (unsigned Idx : P->Modifiers) + NewMappings.push_back(mapIndex(Idx, Mappings)); + return NewMappings; + } + + struct SubstitutionContext { + SubstitutionContext(DiagTextVisitor &Visitor, SubstitutionPiece *P) + : Visitor(Visitor) { + Substitution = Visitor.Builder.getSubstitution(P); + OldMappings = std::move(Visitor.ModifierMappings); + std::vector NewMappings = + Visitor.getSubstitutionMappings(P, OldMappings); + Visitor.ModifierMappings = std::move(NewMappings); + } + + ~SubstitutionContext() { + Visitor.ModifierMappings = std::move(OldMappings); + } + + private: + DiagTextVisitor &Visitor; + Optional> OldMappings; + + public: + Piece *Substitution; + }; + +public: + DiagTextVisitor(DiagnosticTextBuilder &Builder) : Builder(Builder) {} + + void Visit(Piece *P) { + switch (P->getPieceClass()) { +#define CASE(T) \ + case T##PieceClass: \ + return getDerived().Visit##T(static_cast(P)) + CASE(Multi); + CASE(Text); + CASE(Placeholder); + CASE(Select); + CASE(Plural); + CASE(Diff); + CASE(Substitution); +#undef CASE + } + } + + void VisitSubstitution(SubstitutionPiece *P) { + SubstitutionContext Guard(*this, P); + Visit(Guard.Substitution); + } + + unsigned mapIndex(unsigned Idx, + ModifierMappingsType const &ModifierMappings) const { + if (!ModifierMappings) + return Idx; + if (ModifierMappings->size() <= Idx) + Builder.PrintFatalError("Modifier value '" + std::to_string(Idx) + + "' is not valid for this mapping (has " + + std::to_string(ModifierMappings->size()) + + " mappings)"); + return (*ModifierMappings)[Idx]; + } + + unsigned mapIndex(unsigned Idx) const { + return mapIndex(Idx, ModifierMappings); + } + +protected: + DiagnosticTextBuilder &Builder; + ModifierMappingsType ModifierMappings; +}; + +void escapeRST(StringRef Str, std::string &Out) { + for (auto K : Str) { + if (StringRef("`*|_[]\\").count(K)) + Out.push_back('\\'); + Out.push_back(K); + } +} + +template void padToSameLength(It Begin, It End) { + size_t Width = 0; + for (It I = Begin; I != End; ++I) + Width = std::max(Width, I->size()); + for (It I = Begin; I != End; ++I) + (*I) += std::string(Width - I->size(), ' '); +} + +template void makeTableRows(It Begin, It End) { + if (Begin == End) + return; + padToSameLength(Begin, End); + for (It I = Begin; I != End; ++I) + *I = "|" + *I + "|"; +} + +void makeRowSeparator(std::string &Str) { + for (char &K : Str) + K = (K == '|' ? '+' : '-'); +} + +struct DiagTextDocPrinter : DiagTextVisitor { + using BaseTy = DiagTextVisitor; + DiagTextDocPrinter(DiagnosticTextBuilder &Builder, + std::vector &RST) + : BaseTy(Builder), RST(RST) {} + + void gatherNodes( + Piece *OrigP, const ModifierMappingsType &CurrentMappings, + std::vector> &Pieces) const { + if (auto *Sub = dyn_cast(OrigP)) { + ModifierMappingsType NewMappings = + getSubstitutionMappings(Sub, CurrentMappings); + return gatherNodes(Builder.getSubstitution(Sub), NewMappings, Pieces); + } + if (auto *MD = dyn_cast(OrigP)) { + for (auto *Node : MD->Pieces) + gatherNodes(Node, CurrentMappings, Pieces); + return; + } + Pieces.push_back(std::make_pair(OrigP, CurrentMappings)); + } + + void VisitMulti(MultiPiece *P) { + if (P->Pieces.empty()) { + RST.push_back(""); + return; + } + + if (P->Pieces.size() == 1) + return Visit(P->Pieces[0]); + + // Flatten the list of nodes, replacing any substitution pieces with the + // recursively flattened substituted node. + std::vector> Pieces; + gatherNodes(P, ModifierMappings, Pieces); + + std::string EmptyLinePrefix; + size_t Start = RST.size(); + bool HasMultipleLines = true; + for (auto &NodePair : Pieces) { + std::vector Lines; + DiagTextDocPrinter Visitor{Builder, Lines}; + Visitor.ModifierMappings = NodePair.second; + Visitor.Visit(NodePair.first); + + if (Lines.empty()) + continue; + + // We need a vertical separator if either this or the previous piece is a + // multi-line piece, or this is the last piece. + const char *Separator = (Lines.size() > 1 || HasMultipleLines) ? "|" : ""; + HasMultipleLines = Lines.size() > 1; + + if (Start + Lines.size() > RST.size()) + RST.resize(Start + Lines.size(), EmptyLinePrefix); + + padToSameLength(Lines.begin(), Lines.end()); + for (size_t I = 0; I != Lines.size(); ++I) + RST[Start + I] += Separator + Lines[I]; + std::string Empty(Lines[0].size(), ' '); + for (size_t I = Start + Lines.size(); I != RST.size(); ++I) + RST[I] += Separator + Empty; + EmptyLinePrefix += Separator + Empty; + } + for (size_t I = Start; I != RST.size(); ++I) + RST[I] += "|"; + EmptyLinePrefix += "|"; + + makeRowSeparator(EmptyLinePrefix); + RST.insert(RST.begin() + Start, EmptyLinePrefix); + RST.insert(RST.end(), EmptyLinePrefix); + } + + void VisitText(TextPiece *P) { + RST.push_back(""); + auto &S = RST.back(); + + StringRef T = P->Text; + while (!T.empty() && T.front() == ' ') { + RST.back() += " |nbsp| "; + T = T.drop_front(); + } + + std::string Suffix; + while (!T.empty() && T.back() == ' ') { + Suffix += " |nbsp| "; + T = T.drop_back(); + } + + if (!T.empty()) { + S += ':'; + S += P->Role; + S += ":`"; + escapeRST(T, S); + S += '`'; + } + + S += Suffix; + } + + void VisitPlaceholder(PlaceholderPiece *P) { + RST.push_back(std::string(":placeholder:`") + + char('A' + mapIndex(P->Index)) + "`"); + } + + void VisitSelect(SelectPiece *P) { + std::vector SeparatorIndexes; + SeparatorIndexes.push_back(RST.size()); + RST.emplace_back(); + for (auto *O : P->Options) { + Visit(O); + SeparatorIndexes.push_back(RST.size()); + RST.emplace_back(); + } + + makeTableRows(RST.begin() + SeparatorIndexes.front(), + RST.begin() + SeparatorIndexes.back() + 1); + for (size_t I : SeparatorIndexes) + makeRowSeparator(RST[I]); + } + + void VisitPlural(PluralPiece *P) { VisitSelect(P); } + + void VisitDiff(DiffPiece *P) { Visit(P->Options[1]); } + + std::vector &RST; +}; + +struct DiagTextPrinter : DiagTextVisitor { +public: + using BaseTy = DiagTextVisitor; + DiagTextPrinter(DiagnosticTextBuilder &Builder, std::string &Result) + : BaseTy(Builder), Result(Result) {} + + void VisitMulti(MultiPiece *P) { + for (auto *Child : P->Pieces) + Visit(Child); + } + void VisitText(TextPiece *P) { Result += P->Text; } + void VisitPlaceholder(PlaceholderPiece *P) { + Result += "%"; + Result += getModifierName(P->Kind); + addInt(mapIndex(P->Index)); + } + void VisitSelect(SelectPiece *P) { + Result += "%"; + Result += getModifierName(P->ModKind); + if (P->ModKind == MT_Select) { + Result += "{"; + for (auto *D : P->Options) { + Visit(D); + Result += '|'; + } + if (!P->Options.empty()) + Result.erase(--Result.end()); + Result += '}'; + } + addInt(mapIndex(P->Index)); + } + + void VisitPlural(PluralPiece *P) { + Result += "%plural{"; + assert(P->Options.size() == P->OptionPrefixes.size()); + for (unsigned I = 0, End = P->Options.size(); I < End; ++I) { + if (P->OptionPrefixes[I]) + Visit(P->OptionPrefixes[I]); + Visit(P->Options[I]); + Result += "|"; + } + if (!P->Options.empty()) + Result.erase(--Result.end()); + Result += '}'; + addInt(mapIndex(P->Index)); + } + + void VisitDiff(DiffPiece *P) { + Result += "%diff{"; + Visit(P->Options[0]); + Result += "|"; + Visit(P->Options[1]); + Result += "}"; + addInt(mapIndex(P->Indexes[0])); + Result += ","; + addInt(mapIndex(P->Indexes[1])); + } + + void addInt(unsigned Val) { + assert(Val < 10); + Result += static_cast('0' + Val); + } + + std::string &Result; +}; + +unsigned DiagnosticTextBuilder::DiagText::parseModifier(StringRef &Text) const { + if (Text.empty() || !isdigit(Text[0])) + Builder.PrintFatalError("expected modifier in diagnostic"); + + unsigned Val = Text[0] - '0'; + Text = Text.drop_front(); + return Val; +} + +Piece *DiagnosticTextBuilder::DiagText::parseDiagText(StringRef &Text, + bool Nested) { + std::vector Parsed; + + while (!Text.empty()) { + size_t End = (size_t)-2; + do + End = Nested ? Text.find_first_of("%|}", End + 2) + : Text.find_first_of('%', End + 2); + while (End < Text.size() - 1 && Text[End] == '%' && + (Text[End + 1] == '%' || Text[End + 1] == '|')); + + if (End) { + Parsed.push_back(New(Text.slice(0, End), "diagtext")); + Text = Text.slice(End, StringRef::npos); + if (Text.empty()) + break; + } + + if (Text[0] == '|' || Text[0] == '}') + break; + + // Drop the '%'. + Text = Text.drop_front(); + + // Extract the (optional) modifier. + size_t ModLength = Text.find_first_of("0123456789{"); + StringRef Modifier = Text.slice(0, ModLength); + Text = Text.slice(ModLength, StringRef::npos); + ModifierType ModType = llvm::StringSwitch{Modifier} + .Case("select", MT_Select) + .Case("sub", MT_Sub) + .Case("diff", MT_Diff) + .Case("plural", MT_Plural) + .Case("s", MT_S) + .Case("ordinal", MT_Ordinal) + .Case("q", MT_Q) + .Case("objcclass", MT_ObjCClass) + .Case("objcinstance", MT_ObjCInstance) + .Case("", MT_Placeholder) + .Default(MT_Unknown); + + switch (ModType) { + case MT_Unknown: + Builder.PrintFatalError("Unknown modifier type: " + Modifier); + case MT_Select: { + SelectPiece *Select = New(MT_Select); + do { + Text = Text.drop_front(); // '{' or '|' + Select->Options.push_back(parseDiagText(Text, true)); + assert(!Text.empty() && "malformed %select"); + } while (Text.front() == '|'); + // Drop the trailing '}'. + Text = Text.drop_front(1); + Select->Index = parseModifier(Text); + Parsed.push_back(Select); + continue; + } + case MT_Plural: { + PluralPiece *Plural = New(); + do { + Text = Text.drop_front(); // '{' or '|' + size_t End = Text.find_first_of(":"); + if (End == StringRef::npos) + Builder.PrintFatalError("expected ':' while parsing %plural"); + ++End; + assert(!Text.empty()); + Plural->OptionPrefixes.push_back( + New(Text.slice(0, End), "diagtext")); + Text = Text.slice(End, StringRef::npos); + Plural->Options.push_back(parseDiagText(Text, true)); + assert(!Text.empty() && "malformed %select"); + } while (Text.front() == '|'); + // Drop the trailing '}'. + Text = Text.drop_front(1); + Plural->Index = parseModifier(Text); + Parsed.push_back(Plural); + continue; + } + case MT_Sub: { + SubstitutionPiece *Sub = New(); + Text = Text.drop_front(); // '{' + size_t NameSize = Text.find_first_of('}'); + assert(NameSize != size_t(-1) && "failed to find the end of the name"); + assert(NameSize != 0 && "empty name?"); + Sub->Name = Text.substr(0, NameSize).str(); + Text = Text.drop_front(NameSize); + Text = Text.drop_front(); // '}' + if (!Text.empty()) { + while (true) { + if (!isdigit(Text[0])) + break; + Sub->Modifiers.push_back(parseModifier(Text)); + if (Text.empty() || Text[0] != ',') + break; + Text = Text.drop_front(); // ',' + assert(!Text.empty() && isdigit(Text[0]) && + "expected another modifier"); + } + } + Parsed.push_back(Sub); + continue; + } + case MT_Diff: { + DiffPiece *Diff = New(); + Text = Text.drop_front(); // '{' + Diff->Options[0] = parseDiagText(Text, true); + Text = Text.drop_front(); // '|' + Diff->Options[1] = parseDiagText(Text, true); + + Text = Text.drop_front(); // '}' + Diff->Indexes[0] = parseModifier(Text); + Text = Text.drop_front(); // ',' + Diff->Indexes[1] = parseModifier(Text); + Parsed.push_back(Diff); + continue; + } + case MT_S: { + SelectPiece *Select = New(ModType); + Select->Options.push_back(New("")); + Select->Options.push_back(New("s", "diagtext")); + Select->Index = parseModifier(Text); + Parsed.push_back(Select); + continue; + } + case MT_Q: + case MT_Placeholder: + case MT_ObjCClass: + case MT_ObjCInstance: + case MT_Ordinal: { + Parsed.push_back(New(ModType, parseModifier(Text))); + continue; + } + } + } + + return New(Parsed); +} + +std::vector +DiagnosticTextBuilder::buildForDocumentation(StringRef Severity, + const Record *R) { + EvaluatingRecordGuard Guard(&EvaluatingRecord, R); + StringRef Text = R->getValueAsString("Text"); + + DiagText D(*this, Text); + TextPiece *Prefix = D.New(Severity, Severity); + Prefix->Text += ": "; + auto *MP = dyn_cast(D.Root); + if (!MP) { + MP = D.New(); + MP->Pieces.push_back(D.Root); + D.Root = MP; + } + MP->Pieces.insert(MP->Pieces.begin(), Prefix); + std::vector Result; + DiagTextDocPrinter{*this, Result}.Visit(D.Root); + return Result; +} + +std::string DiagnosticTextBuilder::buildForDefinition(const Record *R) { + EvaluatingRecordGuard Guard(&EvaluatingRecord, R); + StringRef Text = R->getValueAsString("Text"); + DiagText D(*this, Text); + std::string Result; + DiagTextPrinter{*this, Result}.Visit(D.Root); + return Result; +} + +} // namespace + //===----------------------------------------------------------------------===// // Warning Tables (.inc file) generation. //===----------------------------------------------------------------------===// @@ -455,6 +1172,7 @@ return ClsName == "CLASS_REMARK"; } + /// ClangDiagsDefsEmitter - The top-level class emits .def files containing /// declarations of Clang diagnostics. namespace clang { @@ -470,8 +1188,9 @@ OS << "#endif\n\n"; } - const std::vector &Diags = - Records.getAllDerivedDefinitions("Diagnostic"); + DiagnosticTextBuilder DiagTextBuilder(Records); + + std::vector Diags = Records.getAllDerivedDefinitions("Diagnostic"); std::vector DiagGroups = Records.getAllDerivedDefinitions("DiagGroup"); @@ -520,7 +1239,7 @@ // Description string. OS << ", \""; - OS.write_escaped(R.getValueAsString("Text")) << '"'; + OS.write_escaped(DiagTextBuilder.buildForDefinition(&R)) << '"'; // Warning associated with the diagnostic. This is stored as an index into // the alphabetically sorted warning table. @@ -882,261 +1601,6 @@ namespace docs { namespace { -/// Diagnostic text, parsed into pieces. -struct DiagText { - struct Piece { - // This type and its derived classes are move-only. - Piece() {} - Piece(Piece &&O) {} - Piece &operator=(Piece &&O) { return *this; } - - virtual void print(std::vector &RST) = 0; - virtual ~Piece() {} - }; - struct TextPiece : Piece { - StringRef Role; - std::string Text; - void print(std::vector &RST) override; - }; - struct PlaceholderPiece : Piece { - int Index; - void print(std::vector &RST) override; - }; - struct SelectPiece : Piece { - SelectPiece() {} - SelectPiece(SelectPiece &&O) noexcept : Options(std::move(O.Options)) {} - std::vector Options; - void print(std::vector &RST) override; - }; - - std::vector> Pieces; - - DiagText(); - DiagText(DiagText &&O) noexcept : Pieces(std::move(O.Pieces)) {} - - DiagText(StringRef Text); - DiagText(StringRef Kind, StringRef Text); - - template void add(P Piece) { - Pieces.push_back(llvm::make_unique

(std::move(Piece))); - } - void print(std::vector &RST); -}; - -DiagText parseDiagText(StringRef &Text, bool Nested = false) { - DiagText Parsed; - - while (!Text.empty()) { - size_t End = (size_t)-2; - do - End = Nested ? Text.find_first_of("%|}", End + 2) - : Text.find_first_of('%', End + 2); - while (End < Text.size() - 1 && Text[End] == '%' && Text[End + 1] == '%'); - - if (End) { - DiagText::TextPiece Piece; - Piece.Role = "diagtext"; - Piece.Text = Text.slice(0, End); - Parsed.add(std::move(Piece)); - Text = Text.slice(End, StringRef::npos); - if (Text.empty()) break; - } - - if (Text[0] == '|' || Text[0] == '}') - break; - - // Drop the '%'. - Text = Text.drop_front(); - - // Extract the (optional) modifier. - size_t ModLength = Text.find_first_of("0123456789{"); - StringRef Modifier = Text.slice(0, ModLength); - Text = Text.slice(ModLength, StringRef::npos); - - // FIXME: Handle %ordinal here. - if (Modifier == "select" || Modifier == "plural") { - DiagText::SelectPiece Select; - do { - Text = Text.drop_front(); - if (Modifier == "plural") - while (Text[0] != ':') - Text = Text.drop_front(); - Select.Options.push_back(parseDiagText(Text, true)); - assert(!Text.empty() && "malformed %select"); - } while (Text.front() == '|'); - Parsed.add(std::move(Select)); - - // Drop the trailing '}n'. - Text = Text.drop_front(2); - continue; - } - - // For %diff, just take the second alternative (tree diagnostic). It would - // be preferable to take the first one, and replace the $ with the suitable - // placeholders. - if (Modifier == "diff") { - Text = Text.drop_front(); // '{' - parseDiagText(Text, true); - Text = Text.drop_front(); // '|' - - DiagText D = parseDiagText(Text, true); - for (auto &P : D.Pieces) - Parsed.Pieces.push_back(std::move(P)); - - Text = Text.drop_front(4); // '}n,m' - continue; - } - - if (Modifier == "s") { - Text = Text.drop_front(); - DiagText::SelectPiece Select; - Select.Options.push_back(DiagText("")); - Select.Options.push_back(DiagText("s")); - Parsed.add(std::move(Select)); - continue; - } - - assert(!Text.empty() && isdigit(Text[0]) && "malformed placeholder"); - DiagText::PlaceholderPiece Placeholder; - Placeholder.Index = Text[0] - '0'; - Parsed.add(std::move(Placeholder)); - Text = Text.drop_front(); - continue; - } - return Parsed; -} - -DiagText::DiagText() {} - -DiagText::DiagText(StringRef Text) : DiagText(parseDiagText(Text, false)) {} - -DiagText::DiagText(StringRef Kind, StringRef Text) : DiagText(parseDiagText(Text, false)) { - TextPiece Prefix; - Prefix.Role = Kind; - Prefix.Text = Kind; - Prefix.Text += ": "; - Pieces.insert(Pieces.begin(), - llvm::make_unique(std::move(Prefix))); -} - -void escapeRST(StringRef Str, std::string &Out) { - for (auto K : Str) { - if (StringRef("`*|_[]\\").count(K)) - Out.push_back('\\'); - Out.push_back(K); - } -} - -template void padToSameLength(It Begin, It End) { - size_t Width = 0; - for (It I = Begin; I != End; ++I) - Width = std::max(Width, I->size()); - for (It I = Begin; I != End; ++I) - (*I) += std::string(Width - I->size(), ' '); -} - -template void makeTableRows(It Begin, It End) { - if (Begin == End) return; - padToSameLength(Begin, End); - for (It I = Begin; I != End; ++I) - *I = "|" + *I + "|"; -} - -void makeRowSeparator(std::string &Str) { - for (char &K : Str) - K = (K == '|' ? '+' : '-'); -} - -void DiagText::print(std::vector &RST) { - if (Pieces.empty()) { - RST.push_back(""); - return; - } - - if (Pieces.size() == 1) - return Pieces[0]->print(RST); - - std::string EmptyLinePrefix; - size_t Start = RST.size(); - bool HasMultipleLines = true; - for (auto &P : Pieces) { - std::vector Lines; - P->print(Lines); - if (Lines.empty()) - continue; - - // We need a vertical separator if either this or the previous piece is a - // multi-line piece, or this is the last piece. - const char *Separator = (Lines.size() > 1 || HasMultipleLines) ? "|" : ""; - HasMultipleLines = Lines.size() > 1; - - if (Start + Lines.size() > RST.size()) - RST.resize(Start + Lines.size(), EmptyLinePrefix); - - padToSameLength(Lines.begin(), Lines.end()); - for (size_t I = 0; I != Lines.size(); ++I) - RST[Start + I] += Separator + Lines[I]; - std::string Empty(Lines[0].size(), ' '); - for (size_t I = Start + Lines.size(); I != RST.size(); ++I) - RST[I] += Separator + Empty; - EmptyLinePrefix += Separator + Empty; - } - for (size_t I = Start; I != RST.size(); ++I) - RST[I] += "|"; - EmptyLinePrefix += "|"; - - makeRowSeparator(EmptyLinePrefix); - RST.insert(RST.begin() + Start, EmptyLinePrefix); - RST.insert(RST.end(), EmptyLinePrefix); -} - -void DiagText::TextPiece::print(std::vector &RST) { - RST.push_back(""); - auto &S = RST.back(); - - StringRef T = Text; - while (!T.empty() && T.front() == ' ') { - RST.back() += " |nbsp| "; - T = T.drop_front(); - } - - std::string Suffix; - while (!T.empty() && T.back() == ' ') { - Suffix += " |nbsp| "; - T = T.drop_back(); - } - - if (!T.empty()) { - S += ':'; - S += Role; - S += ":`"; - escapeRST(T, S); - S += '`'; - } - - S += Suffix; -} - -void DiagText::PlaceholderPiece::print(std::vector &RST) { - RST.push_back(std::string(":placeholder:`") + char('A' + Index) + "`"); -} - -void DiagText::SelectPiece::print(std::vector &RST) { - std::vector SeparatorIndexes; - SeparatorIndexes.push_back(RST.size()); - RST.emplace_back(); - for (auto &O : Options) { - O.print(RST); - SeparatorIndexes.push_back(RST.size()); - RST.emplace_back(); - } - - makeTableRows(RST.begin() + SeparatorIndexes.front(), - RST.begin() + SeparatorIndexes.back() + 1); - for (size_t I : SeparatorIndexes) - makeRowSeparator(RST[I]); -} - bool isRemarkGroup(const Record *DiagGroup, const std::map &DiagsInGroup) { bool AnyRemarks = false, AnyNonRemarks = false; @@ -1181,12 +1645,13 @@ OS << Str << "\n" << std::string(Str.size(), Kind) << "\n"; } -void writeDiagnosticText(StringRef Role, StringRef Text, raw_ostream &OS) { +void writeDiagnosticText(DiagnosticTextBuilder &Builder, const Record *R, + StringRef Role, raw_ostream &OS) { + StringRef Text = R->getValueAsString("Text"); if (Text == "%0") OS << "The text of this diagnostic is not controlled by Clang.\n\n"; else { - std::vector Out; - DiagText(Role, Text).print(Out); + std::vector Out = Builder.buildForDocumentation(Role, R); for (auto &Line : Out) OS << Line << "\n"; OS << "\n"; @@ -1209,8 +1674,11 @@ OS << Documentation->getValueAsString("Intro") << "\n"; + DiagnosticTextBuilder Builder(Records); + std::vector Diags = Records.getAllDerivedDefinitions("Diagnostic"); + std::vector DiagGroups = Records.getAllDerivedDefinitions("DiagGroup"); llvm::sort(DiagGroups.begin(), DiagGroups.end(), diagGroupBeforeByName); @@ -1300,7 +1768,8 @@ Severity[0] = tolower(Severity[0]); if (Severity == "ignored") Severity = IsRemarkGroup ? "remark" : "warning"; - writeDiagnosticText(Severity, D->getValueAsString("Text"), OS); + + writeDiagnosticText(Builder, D, Severity, OS); } }