| //===-- ClangDiagnosticsEmitter.cpp - Generate Clang diagnostics tables ---===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // | |
| // These tablegen backends emit Clang diagnostics tables. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "TableGenBackends.h" | |
| #include "llvm/ADT/DenseSet.h" | |
| #include "llvm/ADT/PointerUnion.h" | |
| #include "llvm/ADT/STLExtras.h" | |
| #include "llvm/ADT/SmallString.h" | |
| #include "llvm/ADT/SmallVector.h" | |
| #include "llvm/ADT/StringExtras.h" | |
| #include "llvm/ADT/StringMap.h" | |
| #include "llvm/ADT/StringSwitch.h" | |
| #include "llvm/ADT/Twine.h" | |
| #include "llvm/Support/Casting.h" | |
| #include "llvm/Support/Format.h" | |
| #include "llvm/TableGen/Error.h" | |
| #include "llvm/TableGen/Record.h" | |
| #include "llvm/TableGen/StringToOffsetTable.h" | |
| #include "llvm/TableGen/TableGenBackend.h" | |
| #include <algorithm> | |
| #include <cctype> | |
| #include <functional> | |
| #include <map> | |
| #include <optional> | |
| #include <set> | |
| using namespace llvm; | |
| //===----------------------------------------------------------------------===// | |
| // Diagnostic category computation code. | |
| //===----------------------------------------------------------------------===// | |
| namespace { | |
| class DiagGroupParentMap { | |
| const RecordKeeper &Records; | |
| std::map<const Record *, std::vector<const Record *>> Mapping; | |
| public: | |
| DiagGroupParentMap(const RecordKeeper &records) : Records(records) { | |
| for (const Record *Group : Records.getAllDerivedDefinitions("DiagGroup")) | |
| for (const Record *SubGroup : Group->getValueAsListOfDefs("SubGroups")) | |
| Mapping[SubGroup].push_back(Group); | |
| } | |
| ArrayRef<const Record *> getParents(const Record *Group) { | |
| return Mapping[Group]; | |
| } | |
| }; | |
| } // end anonymous namespace. | |
| static StringRef | |
| getCategoryFromDiagGroup(const Record *Group, | |
| DiagGroupParentMap &DiagGroupParents) { | |
| // If the DiagGroup has a category, return it. | |
| StringRef CatName = Group->getValueAsString("CategoryName"); | |
| if (!CatName.empty()) return CatName; | |
| // The diag group may the subgroup of one or more other diagnostic groups, | |
| // check these for a category as well. | |
| for (const Record *Parent : DiagGroupParents.getParents(Group)) { | |
| CatName = getCategoryFromDiagGroup(Parent, DiagGroupParents); | |
| if (!CatName.empty()) return CatName; | |
| } | |
| return ""; | |
| } | |
| /// getDiagnosticCategory - Return the category that the specified diagnostic | |
| /// lives in. | |
| static StringRef getDiagnosticCategory(const Record *R, | |
| DiagGroupParentMap &DiagGroupParents) { | |
| // If the diagnostic is in a group, and that group has a category, use it. | |
| if (const auto *Group = dyn_cast<DefInit>(R->getValueInit("Group"))) { | |
| // Check the diagnostic's diag group for a category. | |
| StringRef CatName = | |
| getCategoryFromDiagGroup(Group->getDef(), DiagGroupParents); | |
| if (!CatName.empty()) return CatName; | |
| } | |
| // If the diagnostic itself has a category, get it. | |
| return R->getValueAsString("CategoryName"); | |
| } | |
| namespace { | |
| class DiagCategoryIDMap { | |
| const RecordKeeper &Records; | |
| StringMap<unsigned> CategoryIDs; | |
| std::vector<StringRef> CategoryStrings; | |
| public: | |
| DiagCategoryIDMap(const RecordKeeper &records) : Records(records) { | |
| DiagGroupParentMap ParentInfo(Records); | |
| // The zero'th category is "". | |
| CategoryStrings.push_back(""); | |
| CategoryIDs[""] = 0; | |
| for (const Record *Diag : | |
| Records.getAllDerivedDefinitions("Diagnostic")) { | |
| StringRef Category = getDiagnosticCategory(Diag, ParentInfo); | |
| if (Category.empty()) continue; // Skip diags with no category. | |
| unsigned &ID = CategoryIDs[Category]; | |
| if (ID != 0) continue; // Already seen. | |
| ID = CategoryStrings.size(); | |
| CategoryStrings.push_back(Category); | |
| } | |
| } | |
| unsigned getID(StringRef CategoryString) { | |
| return CategoryIDs[CategoryString]; | |
| } | |
| typedef std::vector<StringRef>::const_iterator const_iterator; | |
| const_iterator begin() const { return CategoryStrings.begin(); } | |
| const_iterator end() const { return CategoryStrings.end(); } | |
| }; | |
| struct GroupInfo { | |
| StringRef GroupName; | |
| std::vector<const Record*> DiagsInGroup; | |
| std::vector<StringRef> SubGroups; | |
| unsigned IDNo = 0; | |
| SmallVector<const Record *, 1> Defs; | |
| GroupInfo() = default; | |
| }; | |
| } // end anonymous namespace. | |
| static bool diagGroupBeforeByName(const Record *LHS, const Record *RHS) { | |
| return LHS->getValueAsString("GroupName") < | |
| RHS->getValueAsString("GroupName"); | |
| } | |
| using DiagsInGroupTy = std::map<StringRef, GroupInfo>; | |
| /// Invert the 1-[0/1] mapping of diags to group into a one to many | |
| /// mapping of groups to diags in the group. | |
| static void groupDiagnostics(ArrayRef<const Record *> Diags, | |
| ArrayRef<const Record *> DiagGroups, | |
| DiagsInGroupTy &DiagsInGroup) { | |
| for (const Record *R : Diags) { | |
| const auto *DI = dyn_cast<DefInit>(R->getValueInit("Group")); | |
| if (!DI) | |
| continue; | |
| assert(R->getValueAsDef("Class")->getName() != "CLASS_NOTE" && | |
| "Note can't be in a DiagGroup"); | |
| StringRef GroupName = DI->getDef()->getValueAsString("GroupName"); | |
| DiagsInGroup[GroupName].DiagsInGroup.push_back(R); | |
| } | |
| // Add all DiagGroup's to the DiagsInGroup list to make sure we pick up empty | |
| // groups (these are warnings that GCC supports that clang never produces). | |
| for (const Record *Group : DiagGroups) { | |
| GroupInfo &GI = DiagsInGroup[Group->getValueAsString("GroupName")]; | |
| GI.GroupName = Group->getName(); | |
| GI.Defs.push_back(Group); | |
| for (const Record *SubGroup : Group->getValueAsListOfDefs("SubGroups")) | |
| GI.SubGroups.push_back(SubGroup->getValueAsString("GroupName")); | |
| } | |
| // Assign unique ID numbers to the groups. | |
| for (auto [IdNo, Iter] : enumerate(DiagsInGroup)) | |
| Iter.second.IDNo = IdNo; | |
| // Warn if the same group is defined more than once (including implicitly). | |
| for (auto &Group : DiagsInGroup) { | |
| if (Group.second.Defs.size() == 1 && | |
| (!Group.second.Defs.front()->isAnonymous() || | |
| Group.second.DiagsInGroup.size() <= 1)) | |
| continue; | |
| bool First = true; | |
| for (const Record *Def : Group.second.Defs) { | |
| // Skip implicit definitions from diagnostics; we'll report those | |
| // separately below. | |
| bool IsImplicit = false; | |
| for (const Record *Diag : Group.second.DiagsInGroup) { | |
| if (cast<DefInit>(Diag->getValueInit("Group"))->getDef() == Def) { | |
| IsImplicit = true; | |
| break; | |
| } | |
| } | |
| if (IsImplicit) | |
| continue; | |
| SMLoc Loc = Def->getLoc().front(); | |
| if (First) { | |
| SrcMgr.PrintMessage(Loc, SourceMgr::DK_Error, | |
| Twine("group '") + Group.first + | |
| "' is defined more than once"); | |
| First = false; | |
| } else { | |
| SrcMgr.PrintMessage(Loc, SourceMgr::DK_Note, "also defined here"); | |
| } | |
| } | |
| for (const Record *Diag : Group.second.DiagsInGroup) { | |
| if (!cast<DefInit>(Diag->getValueInit("Group"))->getDef()->isAnonymous()) | |
| continue; | |
| SMLoc Loc = Diag->getLoc().front(); | |
| if (First) { | |
| SrcMgr.PrintMessage(Loc, SourceMgr::DK_Error, | |
| Twine("group '") + Group.first + | |
| "' is implicitly defined more than once"); | |
| First = false; | |
| } else { | |
| SrcMgr.PrintMessage(Loc, SourceMgr::DK_Note, | |
| "also implicitly defined here"); | |
| } | |
| } | |
| } | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // Infer members of -Wpedantic. | |
| //===----------------------------------------------------------------------===// | |
| typedef std::vector<const Record *> RecordVec; | |
| typedef DenseSet<const Record *> RecordSet; | |
| typedef PointerUnion<RecordVec *, RecordSet *> VecOrSet; | |
| namespace { | |
| class InferPedantic { | |
| typedef DenseMap<const Record *, std::pair<unsigned, std::optional<unsigned>>> | |
| GMap; | |
| DiagGroupParentMap &DiagGroupParents; | |
| ArrayRef<const Record *> Diags; | |
| const std::vector<const Record *> DiagGroups; | |
| DiagsInGroupTy &DiagsInGroup; | |
| DenseSet<const Record *> DiagsSet; | |
| GMap GroupCount; | |
| public: | |
| InferPedantic(DiagGroupParentMap &DiagGroupParents, | |
| ArrayRef<const Record *> Diags, | |
| ArrayRef<const Record *> DiagGroups, | |
| DiagsInGroupTy &DiagsInGroup) | |
| : DiagGroupParents(DiagGroupParents), Diags(Diags), | |
| DiagGroups(DiagGroups), DiagsInGroup(DiagsInGroup) {} | |
| /// Compute the set of diagnostics and groups that are immediately | |
| /// in -Wpedantic. | |
| void compute(VecOrSet DiagsInPedantic, | |
| VecOrSet GroupsInPedantic); | |
| private: | |
| /// Determine whether a group is a subgroup of another group. | |
| bool isSubGroupOfGroup(const Record *Group, StringRef RootGroupName); | |
| /// Determine if the diagnostic is an extension. | |
| bool isExtension(const Record *Diag); | |
| /// Determine if the diagnostic is off by default. | |
| bool isOffByDefault(const Record *Diag); | |
| /// Increment the count for a group, and transitively marked | |
| /// parent groups when appropriate. | |
| void markGroup(const Record *Group); | |
| /// Return true if the diagnostic is in a pedantic group. | |
| bool groupInPedantic(const Record *Group, bool increment = false); | |
| }; | |
| } // end anonymous namespace | |
| bool InferPedantic::isSubGroupOfGroup(const Record *Group, StringRef GName) { | |
| StringRef GroupName = Group->getValueAsString("GroupName"); | |
| if (GName == GroupName) | |
| return true; | |
| for (const Record *Parent : DiagGroupParents.getParents(Group)) | |
| if (isSubGroupOfGroup(Parent, GName)) | |
| return true; | |
| return false; | |
| } | |
| /// Determine if the diagnostic is an extension. | |
| bool InferPedantic::isExtension(const Record *Diag) { | |
| return Diag->getValueAsDef("Class")->getName() == "CLASS_EXTENSION"; | |
| } | |
| bool InferPedantic::isOffByDefault(const Record *Diag) { | |
| return Diag->getValueAsDef("DefaultSeverity")->getValueAsString("Name") == | |
| "Ignored"; | |
| } | |
| bool InferPedantic::groupInPedantic(const Record *Group, bool increment) { | |
| GMap::mapped_type &V = GroupCount[Group]; | |
| // Lazily compute the threshold value for the group count. | |
| if (!V.second) { | |
| const GroupInfo &GI = DiagsInGroup[Group->getValueAsString("GroupName")]; | |
| V.second = GI.SubGroups.size() + GI.DiagsInGroup.size(); | |
| } | |
| if (increment) | |
| ++V.first; | |
| // Consider a group in -Wpendatic IFF if has at least one diagnostic | |
| // or subgroup AND all of those diagnostics and subgroups are covered | |
| // by -Wpedantic via our computation. | |
| return V.first != 0 && V.first == *V.second; | |
| } | |
| void InferPedantic::markGroup(const Record *Group) { | |
| // If all the diagnostics and subgroups have been marked as being | |
| // covered by -Wpedantic, increment the count of parent groups. Once the | |
| // group's count is equal to the number of subgroups and diagnostics in | |
| // that group, we can safely add this group to -Wpedantic. | |
| if (groupInPedantic(Group, /* increment */ true)) | |
| for (const Record *Parent : DiagGroupParents.getParents(Group)) | |
| markGroup(Parent); | |
| } | |
| void InferPedantic::compute(VecOrSet DiagsInPedantic, | |
| VecOrSet GroupsInPedantic) { | |
| // All extensions that are not on by default are implicitly in the | |
| // "pedantic" group. For those that aren't explicitly included in -Wpedantic, | |
| // mark them for consideration to be included in -Wpedantic directly. | |
| for (const Record *R : Diags) { | |
| if (!isExtension(R) || !isOffByDefault(R)) | |
| continue; | |
| DiagsSet.insert(R); | |
| if (const auto *Group = dyn_cast<DefInit>(R->getValueInit("Group"))) { | |
| const Record *GroupRec = Group->getDef(); | |
| if (!isSubGroupOfGroup(GroupRec, "pedantic")) { | |
| markGroup(GroupRec); | |
| } | |
| } | |
| } | |
| // Compute the set of diagnostics that are directly in -Wpedantic. We | |
| // march through Diags a second time to ensure the results are emitted | |
| // in deterministic order. | |
| for (const Record *R : Diags) { | |
| if (!DiagsSet.count(R)) | |
| continue; | |
| // Check if the group is implicitly in -Wpedantic. If so, | |
| // the diagnostic should not be directly included in the -Wpedantic | |
| // diagnostic group. | |
| if (const auto *Group = dyn_cast<DefInit>(R->getValueInit("Group"))) | |
| if (groupInPedantic(Group->getDef())) | |
| continue; | |
| // The diagnostic is not included in a group that is (transitively) in | |
| // -Wpedantic. Include it in -Wpedantic directly. | |
| if (auto *V = dyn_cast<RecordVec *>(DiagsInPedantic)) | |
| V->push_back(R); | |
| else | |
| cast<RecordSet *>(DiagsInPedantic)->insert(R); | |
| } | |
| if (!GroupsInPedantic) | |
| return; | |
| // Compute the set of groups that are directly in -Wpedantic. We | |
| // march through the groups to ensure the results are emitted | |
| /// in a deterministc order. | |
| for (const Record *Group : DiagGroups) { | |
| if (!groupInPedantic(Group)) | |
| continue; | |
| const std::vector<const Record *> &Parents = | |
| DiagGroupParents.getParents(Group); | |
| bool AllParentsInPedantic = | |
| all_of(Parents, [&](const Record *R) { return groupInPedantic(R); }); | |
| // If all the parents are in -Wpedantic, this means that this diagnostic | |
| // group will be indirectly included by -Wpedantic already. In that | |
| // case, do not add it directly to -Wpedantic. If the group has no | |
| // parents, obviously it should go into -Wpedantic. | |
| if (Parents.size() > 0 && AllParentsInPedantic) | |
| continue; | |
| if (auto *V = dyn_cast<RecordVec *>(GroupsInPedantic)) | |
| V->push_back(Group); | |
| else | |
| cast<RecordSet *>(GroupsInPedantic)->insert(Group); | |
| } | |
| } | |
| namespace { | |
| enum PieceKind { | |
| MultiPieceClass, | |
| TextPieceClass, | |
| PlaceholderPieceClass, | |
| SelectPieceClass, | |
| EnumSelectPieceClass, | |
| PluralPieceClass, | |
| DiffPieceClass, | |
| SubstitutionPieceClass, | |
| }; | |
| enum ModifierType { | |
| MT_Unknown, | |
| MT_Placeholder, | |
| MT_Select, | |
| MT_EnumSelect, | |
| MT_Sub, | |
| MT_Plural, | |
| MT_Diff, | |
| MT_Ordinal, | |
| MT_Human, | |
| MT_S, | |
| MT_Q, | |
| MT_ObjCClass, | |
| MT_ObjCInstance, | |
| MT_Quoted, | |
| }; | |
| static StringRef getModifierName(ModifierType MT) { | |
| switch (MT) { | |
| case MT_EnumSelect: | |
| 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_Human: | |
| return "human"; | |
| 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_Quoted: | |
| return "quoted"; | |
| case MT_Unknown: | |
| llvm_unreachable("invalid modifier type"); | |
| } | |
| // Unhandled case | |
| 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<Piece *> Pieces) | |
| : Piece(MultiPieceClass), Pieces(std::move(Pieces)) {} | |
| std::vector<Piece *> 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<Piece *> Options; | |
| int Index = 0; | |
| static bool classof(const Piece *P) { | |
| return P->getPieceClass() == SelectPieceClass || | |
| P->getPieceClass() == EnumSelectPieceClass || | |
| P->getPieceClass() == PluralPieceClass; | |
| } | |
| }; | |
| struct EnumSelectPiece : SelectPiece { | |
| EnumSelectPiece() : SelectPiece(EnumSelectPieceClass, MT_EnumSelect) {} | |
| StringRef EnumName; | |
| std::vector<StringRef> OptionEnumNames; | |
| static bool classof(const Piece *P) { | |
| return P->getPieceClass() == EnumSelectPieceClass; | |
| } | |
| }; | |
| struct EnumValuePiece : Piece { | |
| ModifierType Kind; | |
| }; | |
| struct PluralPiece : SelectPiece { | |
| PluralPiece() : SelectPiece(PluralPieceClass, MT_Plural) {} | |
| std::vector<Piece *> OptionPrefixes; | |
| int Index = 0; | |
| static bool classof(const Piece *P) { | |
| return P->getPieceClass() == PluralPieceClass; | |
| } | |
| }; | |
| struct DiffPiece : Piece { | |
| DiffPiece() : Piece(DiffPieceClass) {} | |
| Piece *Parts[4] = {}; | |
| int Indexes[2] = {}; | |
| static bool classof(const Piece *P) { | |
| return P->getPieceClass() == DiffPieceClass; | |
| } | |
| }; | |
| struct SubstitutionPiece : Piece { | |
| SubstitutionPiece() : Piece(SubstitutionPieceClass) {} | |
| std::string Name; | |
| std::vector<int> 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(const 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"))); | |
| } | |
| // Check that no diagnostic definitions have the same name as a | |
| // substitution. | |
| for (const Record *Diag : Records.getAllDerivedDefinitions("Diagnostic")) { | |
| StringRef Name = Diag->getName(); | |
| if (Substitutions.count(Name)) | |
| llvm::PrintFatalError( | |
| Diag->getLoc(), | |
| "Diagnostic '" + Name + | |
| "' has same name as TextSubstitution definition"); | |
| } | |
| } | |
| std::string buildForDocumentation(StringRef Role, const Record *R); | |
| std::string buildForDefinition(const Record *R); | |
| llvm::SmallVector<std::pair< | |
| std::string, llvm::SmallVector<std::pair<unsigned, std::string>>>> | |
| buildForEnum(const Record *R); | |
| Piece *getSubstitution(SubstitutionPiece *S) const { | |
| auto It = Substitutions.find(S->Name); | |
| if (It == Substitutions.end()) | |
| llvm::PrintFatalError("Failed to find substitution with name: " + | |
| S->Name); | |
| return It->second.Root; | |
| } | |
| [[noreturn]] void PrintFatalError(Twine const &Msg) const { | |
| assert(EvaluatingRecord && "not evaluating a record?"); | |
| llvm::PrintFatalError(EvaluatingRecord->getLoc(), Msg); | |
| } | |
| private: | |
| struct DiagText { | |
| DiagnosticTextBuilder &Builder; | |
| std::vector<Piece *> AllocatedPieces; | |
| Piece *Root = nullptr; | |
| template <class T, class... Args> T *New(Args &&... args) { | |
| static_assert(std::is_base_of<Piece, T>::value, "must be piece"); | |
| T *Mem = new T(std::forward<Args>(args)...); | |
| AllocatedPieces.push_back(Mem); | |
| return Mem; | |
| } | |
| DiagText(DiagnosticTextBuilder &Builder, StringRef Text) | |
| : Builder(Builder), Root(parseDiagText(Text, StopAt::End)) {} | |
| enum class StopAt { | |
| // Parse until the end of the string. | |
| End, | |
| // Additionally stop if we hit a non-nested '|' or '}'. | |
| PipeOrCloseBrace, | |
| // Additionally stop if we hit a non-nested '$'. | |
| Dollar, | |
| }; | |
| Piece *parseDiagText(StringRef &Text, StopAt Stop); | |
| int parseModifier(StringRef &) const; | |
| public: | |
| DiagText(DiagText &&O) noexcept | |
| : Builder(O.Builder), AllocatedPieces(std::move(O.AllocatedPieces)), | |
| Root(O.Root) { | |
| O.Root = nullptr; | |
| } | |
| // The move assignment operator is defined as deleted pending further | |
| // motivation. | |
| DiagText &operator=(DiagText &&) = delete; | |
| // The copy constrcutor and copy assignment operator is defined as deleted | |
| // pending further motivation. | |
| DiagText(const DiagText &) = delete; | |
| DiagText &operator=(const DiagText &) = delete; | |
| ~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<DiagText> Substitutions; | |
| }; | |
| template <class Derived> struct DiagTextVisitor { | |
| using ModifierMappingsType = std::optional<std::vector<int>>; | |
| private: | |
| Derived &getDerived() { return static_cast<Derived &>(*this); } | |
| public: | |
| std::vector<int> | |
| getSubstitutionMappings(SubstitutionPiece *P, | |
| const ModifierMappingsType &Mappings) const { | |
| std::vector<int> NewMappings; | |
| for (int 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<int> NewMappings = | |
| Visitor.getSubstitutionMappings(P, OldMappings); | |
| Visitor.ModifierMappings = std::move(NewMappings); | |
| } | |
| ~SubstitutionContext() { | |
| Visitor.ModifierMappings = std::move(OldMappings); | |
| } | |
| private: | |
| DiagTextVisitor &Visitor; | |
| std::optional<std::vector<int>> 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<T##Piece *>(P)) | |
| CASE(Multi); | |
| CASE(Text); | |
| CASE(Placeholder); | |
| CASE(Select); | |
| CASE(EnumSelect); | |
| CASE(Plural); | |
| CASE(Diff); | |
| CASE(Substitution); | |
| #undef CASE | |
| } | |
| } | |
| void VisitSubstitution(SubstitutionPiece *P) { | |
| SubstitutionContext Guard(*this, P); | |
| Visit(Guard.Substitution); | |
| } | |
| int mapIndex(int Idx, | |
| ModifierMappingsType const &ModifierMappings) const { | |
| if (!ModifierMappings) | |
| return Idx; | |
| if (ModifierMappings->size() <= static_cast<unsigned>(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]; | |
| } | |
| int mapIndex(int Idx) const { | |
| return mapIndex(Idx, ModifierMappings); | |
| } | |
| protected: | |
| DiagnosticTextBuilder &Builder; | |
| ModifierMappingsType ModifierMappings; | |
| }; | |
| /// Markers written in front of a table cell. Nested tables are distinguished | |
| /// by indentation rather than by fence length: openCell indents the | |
| /// continuation lines of a cell by the width of the marker written here, which | |
| /// puts an inner fence exactly one marker to the right of its parent's. The | |
| /// two markers must therefore stay the same width. | |
| constexpr StringRef RowMarker = "* - "; | |
| constexpr StringRef ColumnMarker = " - "; | |
| static_assert(RowMarker.size() == ColumnMarker.size()); | |
| struct DiagTextDocPrinter : DiagTextVisitor<DiagTextDocPrinter> { | |
| using BaseTy = DiagTextVisitor<DiagTextDocPrinter>; | |
| DiagTextDocPrinter(DiagnosticTextBuilder &Builder, std::string &Result) | |
| : BaseTy(Builder), Result(Result) {} | |
| bool containsTablePiece(Piece *P) const { | |
| if (isa<SelectPiece, DiffPiece>(P)) | |
| return true; | |
| if (auto *Multi = dyn_cast<MultiPiece>(P)) | |
| return any_of(Multi->Pieces, | |
| [this](Piece *Child) { return containsTablePiece(Child); }); | |
| if (auto *Substitution = dyn_cast<SubstitutionPiece>(P)) | |
| return containsTablePiece(Builder.getSubstitution(Substitution)); | |
| return false; | |
| } | |
| /// Append \p P without letting it open a table of its own: a MultiPiece is | |
| /// spliced in piece by piece and a substitution is replaced by its | |
| /// expansion. Only pieces that contain no table belong here. | |
| void appendInline(Piece *P) { | |
| assert(!containsTablePiece(P)); | |
| if (auto *Multi = dyn_cast<MultiPiece>(P)) { | |
| for (Piece *Child : Multi->Pieces) | |
| appendInline(Child); | |
| } else if (auto *Substitution = dyn_cast<SubstitutionPiece>(P)) { | |
| SubstitutionContext Guard(*this, Substitution); | |
| appendInline(Guard.Substitution); | |
| } else { | |
| Visit(P); | |
| } | |
| } | |
| /// Append \p P as the body of a cell, using a table for it only if it needs | |
| /// one. | |
| void appendCellBody(Piece *P) { | |
| if (containsTablePiece(P)) | |
| Visit(P); | |
| else | |
| appendInline(P); | |
| } | |
| void VisitSubstitution(SubstitutionPiece *P) { | |
| SubstitutionContext Guard(*this, P); | |
| appendCellBody(Guard.Substitution); | |
| } | |
| /// Start a new line, indenting it to the depth of the enclosing cells. A | |
| /// blank line is left blank rather than padded out with spaces. | |
| void newLine(bool Blank = false) { | |
| Result += '\n'; | |
| if (!Blank) | |
| Result.append(Indent, ' '); | |
| } | |
| /// The cells of the table currently being written. Cells are written | |
| /// straight into \p Result, so the only state a table needs is how many | |
| /// cells it has so far and, while a cell is open, where in \p Result that | |
| /// cell began. | |
| struct TableState { | |
| /// Whether every cell starts a row of its own, rather than the first cell | |
| /// starting a row that the rest extend. | |
| bool OnePiecePerRow; | |
| unsigned NumCells = 0; | |
| bool CellOpen = false; | |
| /// Offsets of the open cell's marker and of the body following it. | |
| size_t MarkerStart = 0; | |
| size_t BodyStart = 0; | |
| }; | |
| void startTable() { | |
| Result += ":::{list-table}"; | |
| newLine(); | |
| Result += ":widths: auto"; | |
| newLine(/*Blank=*/true); | |
| } | |
| void endTable() { | |
| newLine(); | |
| Result += ":::"; | |
| } | |
| /// Open a cell, unless one is already open, by writing its marker. The | |
| /// body's continuation lines line up just past the marker. | |
| void openCell(TableState &T) { | |
| if (T.CellOpen) | |
| return; | |
| T.MarkerStart = Result.size(); | |
| newLine(); | |
| Result += (T.OnePiecePerRow || T.NumCells == 0) ? RowMarker : ColumnMarker; | |
| T.BodyStart = Result.size(); | |
| T.CellOpen = true; | |
| Indent += RowMarker.size(); | |
| } | |
| /// Close the open cell. A cell whose body turned out to be empty is dropped | |
| /// entirely when \p DropIfEmpty, and otherwise keeps its marker, minus the | |
| /// trailing space that would be left dangling. | |
| void closeCell(TableState &T, bool DropIfEmpty) { | |
| assert(T.CellOpen); | |
| T.CellOpen = false; | |
| Indent -= RowMarker.size(); | |
| if (Result.size() != T.BodyStart) { | |
| ++T.NumCells; | |
| } else if (DropIfEmpty) { | |
| Result.resize(T.MarkerStart); | |
| } else { | |
| Result.pop_back(); | |
| ++T.NumCells; | |
| } | |
| } | |
| /// Distribute \p Pieces over the cells of the table \p T is writing. A cell | |
| /// is either inline text or a nested table, never a mixture, so inline | |
| /// pieces extend the open cell while a piece that needs a table of its own | |
| /// closes it and takes a cell to itself. | |
| void appendPiecesToCells(ArrayRef<Piece *> Pieces, TableState &T) { | |
| for (Piece *Child : Pieces) { | |
| if (auto *Substitution = dyn_cast<SubstitutionPiece>(Child); | |
| Substitution && | |
| isa<MultiPiece>(Builder.getSubstitution(Substitution))) { | |
| SubstitutionContext Guard(*this, Substitution); | |
| appendPiecesToCells(cast<MultiPiece>(Guard.Substitution)->Pieces, T); | |
| continue; | |
| } | |
| if (!containsTablePiece(Child)) { | |
| openCell(T); | |
| appendInline(Child); | |
| continue; | |
| } | |
| if (T.CellOpen) | |
| closeCell(T, /*DropIfEmpty=*/true); | |
| openCell(T); | |
| Visit(Child); | |
| closeCell(T, /*DropIfEmpty=*/true); | |
| } | |
| } | |
| void VisitMulti(MultiPiece *P) { | |
| if (P->Pieces.empty()) | |
| return; | |
| if (P->Pieces.size() == 1 && containsTablePiece(P->Pieces.front())) { | |
| Visit(P->Pieces.front()); | |
| return; | |
| } | |
| startTable(); | |
| TableState T{/*OnePiecePerRow=*/false}; | |
| appendPiecesToCells(P->Pieces, T); | |
| if (T.CellOpen) | |
| closeCell(T, /*DropIfEmpty=*/true); | |
| endTable(); | |
| } | |
| void VisitText(TextPiece *P) { | |
| StringRef Text = P->Text; | |
| while (Text.consume_front(" ")) | |
| Result += " "; | |
| unsigned TrailingSpaces = 0; | |
| while (Text.consume_back(" ")) | |
| ++TrailingSpaces; | |
| bool HasText = !Text.empty(); | |
| if (HasText && !P->Role.empty()) { | |
| Result += "{"; | |
| Result += P->Role; | |
| Result += "}`"; | |
| } | |
| for (char C : Text) { | |
| if (C == '`') | |
| Result += "`"; | |
| else { | |
| if (C == '\\') | |
| Result += '\\'; | |
| Result += C; | |
| } | |
| } | |
| if (HasText && !P->Role.empty()) | |
| Result += '`'; | |
| for (unsigned I = 0; I != TrailingSpaces; ++I) | |
| Result += " "; | |
| } | |
| void VisitPlaceholder(PlaceholderPiece *P) { | |
| // Use a role rather than plain `*A*` emphasis: two adjacent placeholders | |
| // would render as `*A**B*`, which CommonMark parses as a single emphasis | |
| // run containing literal asterisks. | |
| Result += "{placeholder}`"; | |
| Result += char('A' + mapIndex(P->Index)); | |
| Result += '`'; | |
| } | |
| void VisitSelect(SelectPiece *P) { | |
| startTable(); | |
| TableState T{/*OnePiecePerRow=*/true}; | |
| for (Piece *Option : P->Options) { | |
| openCell(T); | |
| appendCellBody(Option); | |
| closeCell(T, /*DropIfEmpty=*/false); | |
| } | |
| endTable(); | |
| } | |
| void VisitEnumSelect(EnumSelectPiece *P) { | |
| // Document this as if it were a 'select', which properly prints all of the | |
| // options correctly in a readable/reasonable manner. There isn't really | |
| // anything valuable we could add to readers here. | |
| VisitSelect(P); | |
| } | |
| void VisitPlural(PluralPiece *P) { VisitSelect(P); } | |
| void VisitDiff(DiffPiece *P) { | |
| // Render %diff{a $ b $ c|d}e,f as %select{a %e b %f c|d}. | |
| PlaceholderPiece E(MT_Placeholder, P->Indexes[0]); | |
| PlaceholderPiece F(MT_Placeholder, P->Indexes[1]); | |
| MultiPiece FirstOption; | |
| FirstOption.Pieces.push_back(P->Parts[0]); | |
| FirstOption.Pieces.push_back(&E); | |
| FirstOption.Pieces.push_back(P->Parts[1]); | |
| FirstOption.Pieces.push_back(&F); | |
| FirstOption.Pieces.push_back(P->Parts[2]); | |
| SelectPiece Select(MT_Diff); | |
| Select.Options.push_back(&FirstOption); | |
| Select.Options.push_back(P->Parts[3]); | |
| VisitSelect(&Select); | |
| } | |
| std::string &Result; | |
| /// Number of spaces every new line is indented by, one marker per enclosing | |
| /// cell. | |
| unsigned Indent = 0; | |
| }; | |
| struct DiagEnumPrinter : DiagTextVisitor<DiagEnumPrinter> { | |
| public: | |
| using BaseTy = DiagTextVisitor<DiagEnumPrinter>; | |
| using EnumeratorItem = std::pair<unsigned, std::string>; | |
| using EnumeratorList = llvm::SmallVector<EnumeratorItem>; | |
| using ResultTy = llvm::SmallVector<std::pair<std::string, EnumeratorList>>; | |
| DiagEnumPrinter(DiagnosticTextBuilder &Builder, ResultTy &Result) | |
| : BaseTy(Builder), Result(Result) {} | |
| ResultTy &Result; | |
| void VisitMulti(MultiPiece *P) { | |
| for (auto *Child : P->Pieces) | |
| Visit(Child); | |
| } | |
| void VisitText(TextPiece *P) {} | |
| void VisitPlaceholder(PlaceholderPiece *P) {} | |
| void VisitDiff(DiffPiece *P) {} | |
| void VisitSelect(SelectPiece *P) { | |
| for (auto *D : P->Options) | |
| Visit(D); | |
| } | |
| void VisitPlural(PluralPiece *P) { VisitSelect(P); } | |
| void VisitEnumSelect(EnumSelectPiece *P) { | |
| assert(P->Options.size() == P->OptionEnumNames.size()); | |
| if (!P->EnumName.empty()) { | |
| EnumeratorList List; | |
| for (const auto &Tup : llvm::enumerate(P->OptionEnumNames)) | |
| if (!Tup.value().empty()) | |
| List.emplace_back(Tup.index(), Tup.value()); | |
| Result.emplace_back(P->EnumName, List); | |
| } | |
| VisitSelect(P); | |
| } | |
| }; | |
| struct DiagTextPrinter : DiagTextVisitor<DiagTextPrinter> { | |
| public: | |
| using BaseTy = DiagTextVisitor<DiagTextPrinter>; | |
| 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 || P->ModKind == MT_EnumSelect) { | |
| 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 (const auto [Prefix, Option] : | |
| zip_equal(P->OptionPrefixes, P->Options)) { | |
| if (Prefix) | |
| Visit(Prefix); | |
| Visit(Option); | |
| Result += "|"; | |
| } | |
| if (!P->Options.empty()) | |
| Result.erase(--Result.end()); | |
| Result += '}'; | |
| addInt(mapIndex(P->Index)); | |
| } | |
| void VisitEnumSelect(EnumSelectPiece *P) { | |
| // Print as if we are a 'select', which will result in the compiler just | |
| // treating this like a normal select. This way we don't have to do any | |
| // special work for the compiler to consume these. | |
| VisitSelect(P); | |
| } | |
| void VisitDiff(DiffPiece *P) { | |
| Result += "%diff{"; | |
| Visit(P->Parts[0]); | |
| Result += "$"; | |
| Visit(P->Parts[1]); | |
| Result += "$"; | |
| Visit(P->Parts[2]); | |
| Result += "|"; | |
| Visit(P->Parts[3]); | |
| Result += "}"; | |
| addInt(mapIndex(P->Indexes[0])); | |
| Result += ","; | |
| addInt(mapIndex(P->Indexes[1])); | |
| } | |
| void addInt(int Val) { Result += std::to_string(Val); } | |
| std::string &Result; | |
| }; | |
| int DiagnosticTextBuilder::DiagText::parseModifier(StringRef &Text) const { | |
| if (Text.empty() || !isdigit(Text[0])) | |
| Builder.PrintFatalError("expected modifier in diagnostic"); | |
| int Val = 0; | |
| do { | |
| Val *= 10; | |
| Val += Text[0] - '0'; | |
| Text = Text.drop_front(); | |
| } while (!Text.empty() && isdigit(Text[0])); | |
| return Val; | |
| } | |
| Piece *DiagnosticTextBuilder::DiagText::parseDiagText(StringRef &Text, | |
| StopAt Stop) { | |
| std::vector<Piece *> Parsed; | |
| constexpr StringLiteral StopSets[] = {"%", "%|}", "%|}$"}; | |
| StringRef StopSet = StopSets[static_cast<int>(Stop)]; | |
| while (!Text.empty()) { | |
| size_t End = (size_t)-2; | |
| do | |
| End = Text.find_first_of(StopSet, End + 2); | |
| while ( | |
| End < Text.size() - 1 && Text[End] == '%' && | |
| (Text[End + 1] == '%' || Text[End + 1] == '|' || Text[End + 1] == '$')); | |
| if (End) { | |
| Parsed.push_back(New<TextPiece>(Text.slice(0, End), "diagtext")); | |
| Text = Text.substr(End); | |
| if (Text.empty()) | |
| break; | |
| } | |
| if (Text[0] == '|' || 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.substr(ModLength); | |
| ModifierType ModType = StringSwitch<ModifierType>{Modifier} | |
| .Case("select", MT_Select) | |
| .Case("enum_select", MT_EnumSelect) | |
| .Case("sub", MT_Sub) | |
| .Case("diff", MT_Diff) | |
| .Case("plural", MT_Plural) | |
| .Case("s", MT_S) | |
| .Case("ordinal", MT_Ordinal) | |
| .Case("human", MT_Human) | |
| .Case("q", MT_Q) | |
| .Case("objcclass", MT_ObjCClass) | |
| .Case("objcinstance", MT_ObjCInstance) | |
| .Case("quoted", MT_Quoted) | |
| .Case("", MT_Placeholder) | |
| .Default(MT_Unknown); | |
| auto ExpectAndConsume = [&](StringRef Prefix) { | |
| if (!Text.consume_front(Prefix)) | |
| Builder.PrintFatalError("expected '" + Prefix + "' while parsing %" + | |
| Modifier); | |
| }; | |
| if (ModType != MT_EnumSelect && Text[0] == '<') | |
| Builder.PrintFatalError("modifier '<' syntax not valid with %" + | |
| Modifier); | |
| switch (ModType) { | |
| case MT_Unknown: | |
| Builder.PrintFatalError("Unknown modifier type: " + Modifier); | |
| case MT_Select: { | |
| SelectPiece *Select = New<SelectPiece>(MT_Select); | |
| do { | |
| Text = Text.drop_front(); // '{' or '|' | |
| Select->Options.push_back( | |
| parseDiagText(Text, StopAt::PipeOrCloseBrace)); | |
| assert(!Text.empty() && "malformed %select"); | |
| } while (Text.front() == '|'); | |
| ExpectAndConsume("}"); | |
| Select->Index = parseModifier(Text); | |
| Parsed.push_back(Select); | |
| continue; | |
| } | |
| case MT_EnumSelect: { | |
| EnumSelectPiece *EnumSelect = New<EnumSelectPiece>(); | |
| if (Text[0] != '<') | |
| Builder.PrintFatalError("expected '<' after " + Modifier); | |
| Text = Text.drop_front(); // Drop '<' | |
| size_t EnumNameLen = Text.find_first_of('>'); | |
| EnumSelect->EnumName = Text.slice(0, EnumNameLen); | |
| Text = Text.substr(EnumNameLen); | |
| ExpectAndConsume(">"); | |
| if (Text[0] != '{') | |
| Builder.PrintFatalError("expected '{' after " + Modifier); | |
| do { | |
| Text = Text.drop_front(); // '{' or '|' | |
| bool BracketsRequired = false; | |
| if (Text[0] == '%') { | |
| BracketsRequired = true; | |
| Text = Text.drop_front(); // '%' | |
| size_t OptionNameLen = Text.find_first_of("{"); | |
| EnumSelect->OptionEnumNames.push_back(Text.slice(0, OptionNameLen)); | |
| Text = Text.substr(OptionNameLen); | |
| } else { | |
| EnumSelect->OptionEnumNames.push_back({}); | |
| } | |
| if (BracketsRequired) | |
| ExpectAndConsume("{"); | |
| else if (Text.front() == '{') { | |
| Text = Text.drop_front(); | |
| BracketsRequired = true; | |
| } | |
| EnumSelect->Options.push_back( | |
| parseDiagText(Text, StopAt::PipeOrCloseBrace)); | |
| if (BracketsRequired) | |
| ExpectAndConsume("}"); | |
| assert(!Text.empty() && "malformed %select"); | |
| } while (Text.front() == '|'); | |
| ExpectAndConsume("}"); | |
| EnumSelect->Index = parseModifier(Text); | |
| Parsed.push_back(EnumSelect); | |
| continue; | |
| } | |
| case MT_Plural: { | |
| PluralPiece *Plural = New<PluralPiece>(); | |
| 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<TextPiece>(Text.slice(0, End), "diagtext")); | |
| Text = Text.substr(End); | |
| Plural->Options.push_back( | |
| parseDiagText(Text, StopAt::PipeOrCloseBrace)); | |
| assert(!Text.empty() && "malformed %plural"); | |
| } while (Text.front() == '|'); | |
| ExpectAndConsume("}"); | |
| Plural->Index = parseModifier(Text); | |
| Parsed.push_back(Plural); | |
| continue; | |
| } | |
| case MT_Sub: { | |
| SubstitutionPiece *Sub = New<SubstitutionPiece>(); | |
| ExpectAndConsume("{"); | |
| 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); | |
| ExpectAndConsume("}"); | |
| if (!Text.empty()) { | |
| while (true) { | |
| if (!isdigit(Text[0])) | |
| break; | |
| Sub->Modifiers.push_back(parseModifier(Text)); | |
| if (!Text.consume_front(",")) | |
| break; | |
| assert(!Text.empty() && isdigit(Text[0]) && | |
| "expected another modifier"); | |
| } | |
| } | |
| Parsed.push_back(Sub); | |
| continue; | |
| } | |
| case MT_Diff: { | |
| DiffPiece *Diff = New<DiffPiece>(); | |
| ExpectAndConsume("{"); | |
| Diff->Parts[0] = parseDiagText(Text, StopAt::Dollar); | |
| ExpectAndConsume("$"); | |
| Diff->Parts[1] = parseDiagText(Text, StopAt::Dollar); | |
| ExpectAndConsume("$"); | |
| Diff->Parts[2] = parseDiagText(Text, StopAt::PipeOrCloseBrace); | |
| ExpectAndConsume("|"); | |
| Diff->Parts[3] = parseDiagText(Text, StopAt::PipeOrCloseBrace); | |
| ExpectAndConsume("}"); | |
| Diff->Indexes[0] = parseModifier(Text); | |
| ExpectAndConsume(","); | |
| Diff->Indexes[1] = parseModifier(Text); | |
| Parsed.push_back(Diff); | |
| continue; | |
| } | |
| case MT_S: { | |
| SelectPiece *Select = New<SelectPiece>(ModType); | |
| Select->Options.push_back(New<TextPiece>("")); | |
| Select->Options.push_back(New<TextPiece>("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_Quoted: | |
| case MT_Ordinal: | |
| case MT_Human: { | |
| Parsed.push_back(New<PlaceholderPiece>(ModType, parseModifier(Text))); | |
| continue; | |
| } | |
| } | |
| } | |
| return New<MultiPiece>(Parsed); | |
| } | |
| std::string DiagnosticTextBuilder::buildForDocumentation(StringRef Severity, | |
| const Record *R) { | |
| EvaluatingRecordGuard Guard(&EvaluatingRecord, R); | |
| StringRef Text = R->getValueAsString("Summary"); | |
| DiagText D(*this, Text); | |
| TextPiece *Prefix = D.New<TextPiece>(Severity, Severity); | |
| Prefix->Text += ": "; | |
| auto *MP = dyn_cast<MultiPiece>(D.Root); | |
| if (!MP) { | |
| MP = D.New<MultiPiece>(); | |
| MP->Pieces.push_back(D.Root); | |
| D.Root = MP; | |
| } | |
| MP->Pieces.insert(MP->Pieces.begin(), Prefix); | |
| std::string Result; | |
| DiagTextDocPrinter{*this, Result}.Visit(D.Root); | |
| // The printer indents the line it is on rather than the line it just wrote, | |
| // so it leaves the last line unterminated. | |
| Result += '\n'; | |
| return Result; | |
| } | |
| DiagEnumPrinter::ResultTy DiagnosticTextBuilder::buildForEnum(const Record *R) { | |
| EvaluatingRecordGuard Guard(&EvaluatingRecord, R); | |
| StringRef Text = R->getValueAsString("Summary"); | |
| DiagText D(*this, Text); | |
| DiagEnumPrinter::ResultTy Result; | |
| DiagEnumPrinter{*this, Result}.Visit(D.Root); | |
| return Result; | |
| } | |
| std::string DiagnosticTextBuilder::buildForDefinition(const Record *R) { | |
| EvaluatingRecordGuard Guard(&EvaluatingRecord, R); | |
| StringRef Text = R->getValueAsString("Summary"); | |
| DiagText D(*this, Text); | |
| std::string Result; | |
| DiagTextPrinter{*this, Result}.Visit(D.Root); | |
| return Result; | |
| } | |
| } // namespace | |
| //===----------------------------------------------------------------------===// | |
| // Warning Tables (.inc file) generation. | |
| //===----------------------------------------------------------------------===// | |
| static bool isError(const Record &Diag) { | |
| return Diag.getValueAsDef("Class")->getName() == "CLASS_ERROR"; | |
| } | |
| static bool isRemark(const Record &Diag) { | |
| return Diag.getValueAsDef("Class")->getName() == "CLASS_REMARK"; | |
| } | |
| // Presumes the text has been split at the first whitespace or hyphen. | |
| static bool isExemptAtStart(StringRef Text) { | |
| // Fast path, the first character is lowercase or not alphanumeric. | |
| if (Text.empty() || isLower(Text[0]) || !isAlnum(Text[0])) | |
| return true; | |
| // If the text is all uppercase (or numbers, +, or _), then we assume it's an | |
| // acronym and that's allowed. This covers cases like ISO, C23, C++14, and | |
| // OBJECT_MODE. However, if there's only a single letter other than "C", we | |
| // do not exempt it so that we catch a case like "A really bad idea" while | |
| // still allowing a case like "C does not allow...". | |
| if (all_of(Text, [](char C) { | |
| return isUpper(C) || isDigit(C) || C == '+' || C == '_'; | |
| })) | |
| return Text.size() > 1 || Text[0] == 'C'; | |
| // Otherwise, there are a few other exemptions. | |
| return StringSwitch<bool>(Text) | |
| .Case("AddressSanitizer", true) | |
| .Case("CFString", true) | |
| .Case("Clang", true) | |
| .Case("Fuchsia", true) | |
| .Case("GNUstep", true) | |
| .Case("IBOutletCollection", true) | |
| .Case("Itanium", true) | |
| .Case("Microsoft", true) | |
| .Case("Neon", true) | |
| .StartsWith("NSInvocation", true) // NSInvocation, NSInvocation's | |
| .Case("Objective", true) // Objective-C (hyphen is a word boundary) | |
| .Case("OpenACC", true) | |
| .Case("OpenCL", true) | |
| .Case("OpenMP", true) | |
| .Case("Pascal", true) | |
| .Case("Swift", true) | |
| .Case("Unicode", true) | |
| .Case("Vulkan", true) | |
| .Case("WebAssembly", true) | |
| .Default(false); | |
| } | |
| // Does not presume the text has been split at all. | |
| static bool isExemptAtEnd(StringRef Text) { | |
| // Rather than come up with a list of characters that are allowed, we go the | |
| // other way and look only for characters that are not allowed. | |
| switch (Text.back()) { | |
| default: | |
| return true; | |
| case '?': | |
| // Explicitly allowed to support "; did you mean?". | |
| return true; | |
| case '.': | |
| case '!': | |
| return false; | |
| } | |
| } | |
| static void verifyDiagnosticWording(const Record &Diag) { | |
| StringRef FullDiagText = Diag.getValueAsString("Summary"); | |
| auto DiagnoseStart = [&](StringRef Text) { | |
| // Verify that the text does not start with a capital letter, except for | |
| // special cases that are exempt like ISO and C++. Find the first word | |
| // by looking for a word breaking character. | |
| char Separators[] = {' ', '-', ',', '}'}; | |
| auto Iter = std::find_first_of( | |
| Text.begin(), Text.end(), std::begin(Separators), std::end(Separators)); | |
| StringRef First = Text.substr(0, Iter - Text.begin()); | |
| if (!isExemptAtStart(First)) { | |
| PrintError(&Diag, | |
| "Diagnostics should not start with a capital letter; '" + | |
| First + "' is invalid"); | |
| } | |
| }; | |
| auto DiagnoseEnd = [&](StringRef Text) { | |
| // Verify that the text does not end with punctuation like '.' or '!'. | |
| if (!isExemptAtEnd(Text)) { | |
| PrintError(&Diag, "Diagnostics should not end with punctuation; '" + | |
| Text.substr(Text.size() - 1, 1) + "' is invalid"); | |
| } | |
| }; | |
| // If the diagnostic starts with %select, look through it to see whether any | |
| // of the options will cause a problem. | |
| if (FullDiagText.starts_with("%select{")) { | |
| // Do a balanced delimiter scan from the start of the text to find the | |
| // closing '}', skipping intermediary {} pairs. | |
| size_t BraceCount = 1; | |
| constexpr size_t PercentSelectBraceLen = sizeof("%select{") - 1; | |
| auto Iter = FullDiagText.begin() + PercentSelectBraceLen; | |
| for (auto End = FullDiagText.end(); Iter != End; ++Iter) { | |
| char Ch = *Iter; | |
| if (Ch == '{') | |
| ++BraceCount; | |
| else if (Ch == '}') | |
| --BraceCount; | |
| if (!BraceCount) | |
| break; | |
| } | |
| // Defending against a malformed diagnostic string. | |
| if (BraceCount != 0) | |
| return; | |
| StringRef SelectText = | |
| FullDiagText.substr(PercentSelectBraceLen, Iter - FullDiagText.begin() - | |
| PercentSelectBraceLen); | |
| SmallVector<StringRef, 4> SelectPieces; | |
| SelectText.split(SelectPieces, '|'); | |
| // Walk over all of the individual pieces of select text to see if any of | |
| // them start with an invalid character. If any of the select pieces is | |
| // empty, we need to look at the first word after the %select to see | |
| // whether that is invalid or not. If all of the pieces are fine, then we | |
| // don't need to check anything else about the start of the diagnostic. | |
| bool CheckSecondWord = false; | |
| for (StringRef Piece : SelectPieces) { | |
| if (Piece.empty()) | |
| CheckSecondWord = true; | |
| else | |
| DiagnoseStart(Piece); | |
| } | |
| if (CheckSecondWord) { | |
| // There was an empty select piece, so we need to check the second | |
| // word. This catches situations like '%select{|fine}0 Not okay'. Add | |
| // two to account for the closing curly brace and the number after it. | |
| StringRef AfterSelect = | |
| FullDiagText.substr(Iter - FullDiagText.begin() + 2).ltrim(); | |
| DiagnoseStart(AfterSelect); | |
| } | |
| } else { | |
| // If the start of the diagnostic is not %select, we can check the first | |
| // word and be done with it. | |
| DiagnoseStart(FullDiagText); | |
| } | |
| // If the last character in the diagnostic is a number preceded by a }, scan | |
| // backwards to see if this is for a %select{...}0. If it is, we need to look | |
| // at each piece to see whether it ends in punctuation or not. | |
| bool StillNeedToDiagEnd = true; | |
| if (isDigit(FullDiagText.back()) && *(FullDiagText.end() - 2) == '}') { | |
| // Scan backwards to find the opening curly brace. | |
| size_t BraceCount = 1; | |
| auto Iter = FullDiagText.end() - sizeof("}0"); | |
| for (auto End = FullDiagText.begin(); Iter != End; --Iter) { | |
| char Ch = *Iter; | |
| if (Ch == '}') | |
| ++BraceCount; | |
| else if (Ch == '{') | |
| --BraceCount; | |
| if (!BraceCount) | |
| break; | |
| } | |
| // Defending against a malformed diagnostic string. | |
| if (BraceCount != 0) | |
| return; | |
| // Continue the backwards scan to find the word before the '{' to see if it | |
| // is 'select'. | |
| constexpr size_t SelectLen = sizeof("select") - 1; | |
| bool IsSelect = | |
| (FullDiagText.substr(Iter - SelectLen - FullDiagText.begin(), | |
| SelectLen) == "select"); | |
| if (IsSelect) { | |
| // Gather the content between the {} for the select in question so we can | |
| // split it into pieces. | |
| StillNeedToDiagEnd = false; // No longer need to handle the end. | |
| StringRef SelectText = | |
| FullDiagText.substr(Iter - FullDiagText.begin() + /*{*/ 1, | |
| FullDiagText.end() - Iter - /*pos before }0*/ 3); | |
| SmallVector<StringRef, 4> SelectPieces; | |
| SelectText.split(SelectPieces, '|'); | |
| for (StringRef Piece : SelectPieces) { | |
| // Not worrying about a situation like: "this is bar. %select{foo|}0". | |
| if (!Piece.empty()) | |
| DiagnoseEnd(Piece); | |
| } | |
| } | |
| } | |
| // If we didn't already cover the diagnostic because of a %select, handle it | |
| // now. | |
| if (StillNeedToDiagEnd) | |
| DiagnoseEnd(FullDiagText); | |
| // FIXME: This could also be improved by looking for instances of clang or | |
| // gcc in the diagnostic and recommend Clang or GCC instead. However, this | |
| // runs into odd situations like [[clang::warn_unused_result]], | |
| // #pragma clang, or --unwindlib=libgcc. | |
| } | |
| /// ClangDiagsCompatIDsEmitter - Emit a set of 'compatibility diagnostic ids' | |
| /// that map to a set of 2 regular diagnostic ids each and which are used to | |
| /// simplify emitting compatibility warnings. | |
| void clang::EmitClangDiagsCompatIDs(const llvm::RecordKeeper &Records, | |
| llvm::raw_ostream &OS, | |
| const std::string &Component) { | |
| ArrayRef<const Record *> Ids = | |
| Records.getAllDerivedDefinitions("CompatWarningId"); | |
| StringRef PrevComponent = ""; | |
| for (auto [I, R] : enumerate(make_pointee_range(Ids))) { | |
| StringRef DiagComponent = R.getValueAsString("Component"); | |
| if (!Component.empty() && Component != DiagComponent) | |
| continue; | |
| StringRef CompatDiagName = R.getValueAsString("Name"); | |
| StringRef Diag = R.getValueAsString("Diag"); | |
| StringRef DiagPre = R.getValueAsString("DiagPre"); | |
| int64_t CXXStdVer = R.getValueAsInt("Std"); | |
| // We don't want to create empty enums since some compilers (including | |
| // Clang) warn about that, so these macros are used to avoid having to | |
| // unconditionally write 'enum {' and '};' in the headers. | |
| if (PrevComponent != DiagComponent) { | |
| if (!PrevComponent.empty()) | |
| OS << "DIAG_COMPAT_IDS_END()\n"; | |
| OS << "DIAG_COMPAT_IDS_BEGIN()\n"; | |
| PrevComponent = DiagComponent; | |
| } | |
| // FIXME: We sometimes define multiple compat diagnostics with the same | |
| // name, e.g. 'constexpr_body_invalid_stmt' exists for C++14/20/23. It would | |
| // be nice if we could combine all of them into a single compatibility diag | |
| // id. | |
| OS << "DIAG_COMPAT_ID(" << I << ","; | |
| OS << CompatDiagName << "," << CXXStdVer << "," << Diag << "," << DiagPre; | |
| OS << ")\n"; | |
| } | |
| if (!PrevComponent.empty()) | |
| OS << "DIAG_COMPAT_IDS_END()\n"; | |
| } | |
| /// ClangDiagsIntefaceEmitter - Emit the diagnostics interface header for | |
| /// a Clang component. | |
| void clang::EmitClangDiagsInterface(llvm::raw_ostream &OS, | |
| const std::string &Component) { | |
| if (Component.empty()) | |
| PrintFatalError("'-gen-clang-diags-iface' requires a component name"); | |
| std::string ComponentUpper = StringRef(Component).upper(); | |
| const char *Comp = Component.c_str(); | |
| const char *Upper = ComponentUpper.c_str(); | |
| OS << llvm::format(R"c++( | |
| namespace clang { | |
| namespace diag { | |
| enum { | |
| #define DIAG(ENUM, FLAGS, DEFAULT_MAPPING, DESC, GROUP, SFINAE, NOWERROR, \ | |
| SHOWINSYSHEADER, SHOWINSYSMACRO, DEFERRABLE, CATEGORY, STABLE_ID, \ | |
| LEGACY_STABLE_IDS) \ | |
| ENUM, | |
| #define %sSTART | |
| #include "clang/Basic/Diagnostic%sKinds.inc" | |
| #undef DIAG | |
| NUM_BUILTIN_%s_DIAGNOSTICS | |
| }; | |
| #define DIAG_ENUM(ENUM_NAME) \ | |
| namespace ENUM_NAME { \ | |
| enum { | |
| #define DIAG_ENUM_ITEM(IDX, NAME) NAME = IDX, | |
| #define DIAG_ENUM_END() \ | |
| } \ | |
| ; \ | |
| } | |
| #include "clang/Basic/Diagnostic%sEnums.inc" | |
| #undef DIAG_ENUM_END | |
| #undef DIAG_ENUM_ITEM | |
| #undef DIAG_ENUM | |
| } // end namespace diag | |
| namespace diag_compat { | |
| #define DIAG_COMPAT_IDS_BEGIN() enum { | |
| #define DIAG_COMPAT_IDS_END() \ | |
| } \ | |
| ; | |
| #define DIAG_COMPAT_ID(IDX, NAME, ...) NAME = IDX, | |
| #include "clang/Basic/Diagnostic%sCompatIDs.inc" | |
| #undef DIAG_COMPAT_ID | |
| #undef DIAG_COMPAT_IDS_BEGIN | |
| #undef DIAG_COMPAT_IDS_END | |
| } // end namespace diag_compat | |
| } // end namespace clang | |
| )c++", | |
| Upper, Comp, Upper, Comp, Comp); | |
| } | |
| /// ClangDiagsEnumsEmitter - The top-level class emits .def files containing | |
| /// declarations of Clang diagnostic enums for selects. | |
| void clang::EmitClangDiagsEnums(const RecordKeeper &Records, raw_ostream &OS, | |
| const std::string &Component) { | |
| DiagnosticTextBuilder DiagTextBuilder(Records); | |
| ArrayRef<const Record *> Diags = | |
| Records.getAllDerivedDefinitions("Diagnostic"); | |
| llvm::SmallVector<std::pair<const Record *, std::string>> EnumerationNames; | |
| for (const Record &R : make_pointee_range(Diags)) { | |
| DiagEnumPrinter::ResultTy Enums = DiagTextBuilder.buildForEnum(&R); | |
| for (auto &Enumeration : Enums) { | |
| bool ShouldPrint = | |
| Component.empty() || Component == R.getValueAsString("Component"); | |
| auto PreviousByName = llvm::find_if(EnumerationNames, [&](auto &Prev) { | |
| return Prev.second == Enumeration.first; | |
| }); | |
| if (PreviousByName != EnumerationNames.end()) { | |
| PrintError(&R, | |
| "Duplicate enumeration name '" + Enumeration.first + "'"); | |
| PrintNote(PreviousByName->first->getLoc(), | |
| "Previous diagnostic is here"); | |
| } | |
| EnumerationNames.emplace_back(&R, Enumeration.first); | |
| if (ShouldPrint) | |
| OS << "DIAG_ENUM(" << Enumeration.first << ")\n"; | |
| llvm::SmallVector<std::string> EnumeratorNames; | |
| for (auto &Enumerator : Enumeration.second) { | |
| if (llvm::is_contained(EnumeratorNames, Enumerator.second)) | |
| PrintError(&R, | |
| "Duplicate enumerator name '" + Enumerator.second + "'"); | |
| EnumeratorNames.push_back(Enumerator.second); | |
| if (ShouldPrint) | |
| OS << "DIAG_ENUM_ITEM(" << Enumerator.first << ", " | |
| << Enumerator.second << ")\n"; | |
| } | |
| if (ShouldPrint) | |
| OS << "DIAG_ENUM_END()\n"; | |
| } | |
| } | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // Stable ID Tables generation | |
| //===----------------------------------------------------------------------===// | |
| namespace { | |
| /// Holds the string table for all Stable IDs, plus the arrays of legacy Stable | |
| /// IDs for renamed diagnostics. | |
| class DiagStableIDsMap { | |
| StringToOffsetTable StableIDs; | |
| std::vector<uint32_t> LegacyStableIDs; | |
| llvm::StringMap<uint32_t> LegacyStableIDsStartOffsets; | |
| public: | |
| DiagStableIDsMap(const RecordKeeper &Records) { | |
| LegacyStableIDs.push_back(0); // Empty array at offset 0 | |
| for (const Record *Diag : Records.getAllDerivedDefinitions("Diagnostic")) { | |
| StringRef StableID = getStableID(*Diag); | |
| // Memoize the Stable ID | |
| StableIDs.GetOrAddStringOffset(StableID); | |
| auto LegacyIDList = Diag->getValueAsListOfStrings("LegacyStableIds"); | |
| if (!LegacyIDList.empty()) { | |
| // Memoize any Legacy Stable IDs, and list their offsets in an array. | |
| size_t StartOffset = LegacyStableIDs.size(); | |
| LegacyStableIDsStartOffsets.insert( | |
| std::make_pair(Diag->getName(), StartOffset)); | |
| for (const auto LegacyID : LegacyIDList) { | |
| unsigned Offset = StableIDs.GetOrAddStringOffset(LegacyID); | |
| LegacyStableIDs.push_back(Offset); | |
| } | |
| LegacyStableIDs.push_back(0); // Terminate the array. | |
| } | |
| } | |
| } | |
| /// Gets the string table offset of the Stable ID for the specified Diagnostic | |
| /// record. | |
| uint32_t getStableIDOffset(const Record &R) const { | |
| return StableIDs.GetStringOffset(getStableID(R)).value(); | |
| } | |
| /// Gets the offset in the DiagLegacyStableIDs array of the first element of | |
| /// the diagnostic's list of legacy Stable IDs. | |
| uint32_t getLegacyStableIDsStartOffset(StringRef Name) const { | |
| // `lookup()` will return zero if not found, which is exactly what we want | |
| // anyway. | |
| return LegacyStableIDsStartOffsets.lookup(Name); | |
| } | |
| /// Emit diagnostic stable ID arrays and related data structures. | |
| /// | |
| /// This creates the table of stable IDs, plus the array of arrays of old | |
| /// stable IDs. | |
| /// | |
| /// \code | |
| /// #ifdef GET_DIAG_STABLE_ID_ARRAYS | |
| /// static const int32_t DiagOldStableIds[]; | |
| /// static constexpr llvm::StringTable DiagStableIds; | |
| /// #endif | |
| /// \endcode | |
| void emit(raw_ostream &OS, const RecordKeeper &Records) const { | |
| OS << "\n#ifdef GET_DIAG_STABLE_ID_ARRAYS\n"; | |
| emitStableIDs(OS); | |
| emitLegacyStableIDs(OS); | |
| emitDescriptions(OS, Records); | |
| OS << "#endif // GET_DIAG_STABLE_ID_ARRAYS\n\n"; | |
| } | |
| private: | |
| /// Gets the Stable ID for the specified Diagnostic record. | |
| /// The Stable ID can be explicitly specified via the "StableId" | |
| /// property. If not specified explicitly, the Stable ID defaults | |
| /// to the name of the diagnostic. | |
| static StringRef getStableID(const Record &R) { | |
| StringRef StableID = R.getValueAsString("StableId"); | |
| return StableID.empty() ? R.getName() : StableID; | |
| } | |
| /// Emit a list of stable IDs, used by both the "StableId" and | |
| /// "LegacyStableIds" properties. | |
| /// | |
| /// This creates an `llvm::StringTable` of all the stable ids in use. | |
| void emitStableIDs(raw_ostream &OS) const { | |
| StableIDs.EmitStringTableDef(OS, "DiagStableIDs"); | |
| OS << "\n"; | |
| } | |
| /// Emit the array of legacy stable IDs for diagnostics. | |
| /// | |
| /// The array of stable IDs contains for each diagnostic a list of its legacy | |
| /// stable IDs. The individual lists are separated by '0'. Diagnostics with | |
| /// no legacy stable IDs are skipped. | |
| /// | |
| /// \code | |
| /// static const uint16_t DiagOldStableIds[] = { | |
| /// /* Empty */ 0, | |
| /// /* Diag0 */ 142, 0, | |
| /// /* Diag13 */ 265, 322, 399, 0 | |
| /// } | |
| /// \endcode | |
| /// | |
| void emitLegacyStableIDs(raw_ostream &OS) const { | |
| OS << "static const uint32_t DiagLegacyStableIDs[] = {\n"; | |
| bool StartOfLine = true; | |
| for (auto Offset : LegacyStableIDs) { | |
| if (StartOfLine) { | |
| OS << " "; | |
| StartOfLine = false; | |
| } | |
| OS << Offset << ","; | |
| if (Offset > 0) { | |
| OS << " "; | |
| } else { | |
| OS << "\n"; | |
| StartOfLine = true; | |
| } | |
| } | |
| OS << "};\n\n"; | |
| } | |
| static void emitDescriptions(raw_ostream &OS, const RecordKeeper &Records) { | |
| DiagnosticTextBuilder DiagTextBuilder(Records); | |
| StringToOffsetTable Descriptions; | |
| OS << "enum {\n"; | |
| for (const Record &R : | |
| make_pointee_range(Records.getAllDerivedDefinitions("Diagnostic"))) { | |
| std::string Description = DiagTextBuilder.buildForDefinition(&R); | |
| if (Description.size() >= (1U << 12)) | |
| PrintFatalError(R.getLoc(), | |
| "diagnostic description exceeds 12-bit length limit"); | |
| unsigned Offset = Descriptions.GetOrAddStringOffset(Description); | |
| OS << " DIAG_DESC_OFFSET_" << R.getName() << " = " << Offset << ",\n"; | |
| } | |
| OS << "};\n\n"; | |
| if (Descriptions.size() >= (1U << 20)) | |
| PrintFatalError("diagnostic descriptions exceed 20-bit offset limit"); | |
| Descriptions.EmitStringTableStorageDef(OS, "StaticDiagInfoDescriptions"); | |
| } | |
| }; | |
| } // namespace | |
| /// Emit the definitions of the Stable ID and old Stable ID tables. | |
| void clang::EmitClangDiagsStableIDs(const RecordKeeper &Records, | |
| raw_ostream &OS) { | |
| DiagStableIDsMap StableIDs(Records); | |
| StableIDs.emit(OS, Records); | |
| } | |
| /// ClangDiagsDefsEmitter - The top-level class emits .def files containing | |
| /// declarations of Clang diagnostics. | |
| void clang::EmitClangDiagsDefs(const RecordKeeper &Records, raw_ostream &OS, | |
| const std::string &Component) { | |
| // Write the #if guard | |
| if (!Component.empty()) { | |
| std::string ComponentName = StringRef(Component).upper(); | |
| OS << "#ifdef " << ComponentName << "START\n"; | |
| OS << "__" << ComponentName << "START = DIAG_START_" << ComponentName | |
| << ",\n"; | |
| OS << "#undef " << ComponentName << "START\n"; | |
| OS << "#endif\n\n"; | |
| } | |
| DiagnosticTextBuilder DiagTextBuilder(Records); | |
| ArrayRef<const Record *> Diags = | |
| Records.getAllDerivedDefinitions("Diagnostic"); | |
| ArrayRef<const Record *> DiagGroups = | |
| Records.getAllDerivedDefinitions("DiagGroup"); | |
| DiagsInGroupTy DiagsInGroup; | |
| groupDiagnostics(Diags, DiagGroups, DiagsInGroup); | |
| DiagCategoryIDMap CategoryIDs(Records); | |
| DiagGroupParentMap DGParentMap(Records); | |
| DiagStableIDsMap StableIDs(Records); | |
| // Compute the set of diagnostics that are in -Wpedantic. | |
| RecordSet DiagsInPedantic; | |
| InferPedantic inferPedantic(DGParentMap, Diags, DiagGroups, DiagsInGroup); | |
| inferPedantic.compute(&DiagsInPedantic, (RecordVec*)nullptr); | |
| for (const Record &R : make_pointee_range(Diags)) { | |
| // Check if this is an error that is accidentally in a warning | |
| // group. | |
| if (isError(R)) { | |
| if (const auto *Group = dyn_cast<DefInit>(R.getValueInit("Group"))) { | |
| const Record *GroupRec = Group->getDef(); | |
| StringRef GroupName = GroupRec->getValueAsString("GroupName"); | |
| PrintFatalError(R.getLoc(), "Error " + R.getName() + | |
| " cannot be in a warning group [" + GroupName + "]"); | |
| } | |
| } | |
| // Check that all remarks have an associated diagnostic group. | |
| if (isRemark(R)) { | |
| if (!isa<DefInit>(R.getValueInit("Group"))) { | |
| PrintFatalError(R.getLoc(), "Error " + R.getName() + | |
| " not in any diagnostic group"); | |
| } | |
| } | |
| // Filter by component. | |
| if (!Component.empty() && Component != R.getValueAsString("Component")) | |
| continue; | |
| // Validate diagnostic wording for common issues. | |
| verifyDiagnosticWording(R); | |
| OS << "DIAG(" << R.getName() << ", "; | |
| OS << R.getValueAsDef("Class")->getName(); | |
| OS << ", (unsigned)diag::Severity::" | |
| << R.getValueAsDef("DefaultSeverity")->getValueAsString("Name"); | |
| // Description string. | |
| OS << ", \""; | |
| OS.write_escaped(DiagTextBuilder.buildForDefinition(&R)) << '"'; | |
| // Warning group associated with the diagnostic. This is stored as an index | |
| // into the alphabetically sorted warning group table. | |
| if (const auto *DI = dyn_cast<DefInit>(R.getValueInit("Group"))) { | |
| auto I = DiagsInGroup.find(DI->getDef()->getValueAsString("GroupName")); | |
| assert(I != DiagsInGroup.end()); | |
| OS << ", " << I->second.IDNo; | |
| } else if (DiagsInPedantic.count(&R)) { | |
| auto I = DiagsInGroup.find("pedantic"); | |
| assert(I != DiagsInGroup.end() && "pedantic group not defined"); | |
| OS << ", " << I->second.IDNo; | |
| } else { | |
| OS << ", 0"; | |
| } | |
| // SFINAE response. | |
| OS << ", " << R.getValueAsDef("SFINAE")->getName(); | |
| // Default warning has no Werror bit. | |
| if (R.getValueAsBit("WarningNoWerror")) | |
| OS << ", true"; | |
| else | |
| OS << ", false"; | |
| if (R.getValueAsBit("ShowInSystemHeader")) | |
| OS << ", true"; | |
| else | |
| OS << ", false"; | |
| if (R.getValueAsBit("ShowInSystemMacro")) | |
| OS << ", true"; | |
| else | |
| OS << ", false"; | |
| if (R.getValueAsBit("Deferrable")) | |
| OS << ", true"; | |
| else | |
| OS << ", false"; | |
| // Category number. | |
| OS << ", " << CategoryIDs.getID(getDiagnosticCategory(&R, DGParentMap)); | |
| // Stable ID. | |
| uint32_t StableIDOffset = StableIDs.getStableIDOffset(R); | |
| OS << ", " << StableIDOffset; | |
| // Previous Stable IDs. | |
| uint32_t LegacyStableIDsStartOffset = | |
| StableIDs.getLegacyStableIDsStartOffset(R.getName()); | |
| OS << ", " << LegacyStableIDsStartOffset; | |
| OS << ")\n"; | |
| } | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // Warning Group Tables generation | |
| //===----------------------------------------------------------------------===// | |
| static std::string getDiagCategoryEnum(StringRef name) { | |
| if (name.empty()) | |
| return "DiagCat_None"; | |
| SmallString<256> enumName = StringRef("DiagCat_"); | |
| for (char C : name) | |
| enumName += isalnum(C) ? C : '_'; | |
| return std::string(enumName); | |
| } | |
| /// Emit the array of diagnostic subgroups. | |
| /// | |
| /// The array of diagnostic subgroups contains for each group a list of its | |
| /// subgroups. The individual lists are separated by '-1'. Groups with no | |
| /// subgroups are skipped. | |
| /// | |
| /// \code | |
| /// static const int16_t DiagSubGroups[] = { | |
| /// /* Empty */ -1, | |
| /// /* DiagSubGroup0 */ 142, -1, | |
| /// /* DiagSubGroup13 */ 265, 322, 399, -1 | |
| /// } | |
| /// \endcode | |
| /// | |
| static void emitDiagSubGroups(DiagsInGroupTy &DiagsInGroup, | |
| RecordVec &GroupsInPedantic, raw_ostream &OS) { | |
| OS << "static const int16_t DiagSubGroups[] = {\n" | |
| << " /* Empty */ -1,\n"; | |
| for (auto const &[Name, Group] : DiagsInGroup) { | |
| const bool IsPedantic = Name == "pedantic"; | |
| const std::vector<StringRef> &SubGroups = Group.SubGroups; | |
| if (!SubGroups.empty() || (IsPedantic && !GroupsInPedantic.empty())) { | |
| OS << " /* DiagSubGroup" << Group.IDNo << " */ "; | |
| for (StringRef SubGroup : SubGroups) { | |
| auto RI = DiagsInGroup.find(SubGroup); | |
| assert(RI != DiagsInGroup.end() && "Referenced without existing?"); | |
| OS << RI->second.IDNo << ", "; | |
| } | |
| // Emit the groups implicitly in "pedantic". | |
| if (IsPedantic) { | |
| for (auto const &Group : GroupsInPedantic) { | |
| StringRef GroupName = Group->getValueAsString("GroupName"); | |
| auto RI = DiagsInGroup.find(GroupName); | |
| assert(RI != DiagsInGroup.end() && "Referenced without existing?"); | |
| OS << RI->second.IDNo << ", "; | |
| } | |
| } | |
| OS << "-1,\n"; | |
| } | |
| } | |
| OS << "};\n\n"; | |
| } | |
| /// Emit the list of diagnostic arrays. | |
| /// | |
| /// This data structure is a large array that contains itself arrays of varying | |
| /// size. Each array represents a list of diagnostics. The different arrays are | |
| /// separated by the value '-1'. | |
| /// | |
| /// \code | |
| /// static const int16_t DiagArrays[] = { | |
| /// /* Empty */ -1, | |
| /// /* DiagArray1 */ diag::warn_pragma_message, | |
| /// -1, | |
| /// /* DiagArray2 */ diag::warn_abs_too_small, | |
| /// diag::warn_unsigned_abs, | |
| /// diag::warn_wrong_absolute_value_type, | |
| /// -1 | |
| /// }; | |
| /// \endcode | |
| /// | |
| static void emitDiagArrays(DiagsInGroupTy &DiagsInGroup, | |
| RecordVec &DiagsInPedantic, raw_ostream &OS) { | |
| OS << "static const int16_t DiagArrays[] = {\n" | |
| << " /* Empty */ -1,\n"; | |
| for (const auto &[Name, Group] : DiagsInGroup) { | |
| const bool IsPedantic = Name == "pedantic"; | |
| const std::vector<const Record *> &V = Group.DiagsInGroup; | |
| if (!V.empty() || (IsPedantic && !DiagsInPedantic.empty())) { | |
| OS << " /* DiagArray" << Group.IDNo << " */ "; | |
| for (auto *Record : V) | |
| OS << "diag::" << Record->getName() << ", "; | |
| // Emit the diagnostics implicitly in "pedantic". | |
| if (IsPedantic) { | |
| for (auto const &Diag : DiagsInPedantic) | |
| OS << "diag::" << Diag->getName() << ", "; | |
| } | |
| OS << "-1,\n"; | |
| } | |
| } | |
| OS << "};\n\n"; | |
| } | |
| /// Emit a list of group names. | |
| /// | |
| /// This creates an `llvm::StringTable` of all the diagnostic group names. | |
| static void emitDiagGroupNames(const StringToOffsetTable &GroupNames, | |
| raw_ostream &OS) { | |
| GroupNames.EmitStringTableDef(OS, "DiagGroupNames"); | |
| OS << "\n"; | |
| } | |
| /// Emit diagnostic arrays and related data structures. | |
| /// | |
| /// This creates the actual diagnostic array, an array of diagnostic subgroups | |
| /// and an array of subgroup names. | |
| /// | |
| /// \code | |
| /// #ifdef GET_DIAG_ARRAYS | |
| /// static const int16_t DiagArrays[]; | |
| /// static const int16_t DiagSubGroups[]; | |
| /// static constexpr llvm::StringTable DiagGroupNames; | |
| /// #endif | |
| /// \endcode | |
| static void emitAllDiagArrays(DiagsInGroupTy &DiagsInGroup, | |
| RecordVec &DiagsInPedantic, | |
| RecordVec &GroupsInPedantic, | |
| const StringToOffsetTable &GroupNames, | |
| raw_ostream &OS) { | |
| OS << "\n#ifdef GET_DIAG_ARRAYS\n"; | |
| emitDiagArrays(DiagsInGroup, DiagsInPedantic, OS); | |
| emitDiagSubGroups(DiagsInGroup, GroupsInPedantic, OS); | |
| emitDiagGroupNames(GroupNames, OS); | |
| OS << "#endif // GET_DIAG_ARRAYS\n\n"; | |
| } | |
| /// Emit diagnostic table. | |
| /// | |
| /// The table is sorted by the name of the diagnostic group. Each element | |
| /// consists of the name of the diagnostic group (given as offset in the | |
| /// group name table), a reference to a list of diagnostics (optional) and a | |
| /// reference to a set of subgroups (optional). | |
| /// | |
| /// \code | |
| /// #ifdef GET_DIAG_TABLE | |
| /// {/* abi */ 159, /* DiagArray11 */ 19, /* Empty */ 0}, | |
| /// {/* aggregate-return */ 180, /* Empty */ 0, /* Empty */ 0}, | |
| /// {/* all */ 197, /* Empty */ 0, /* DiagSubGroup13 */ 3}, | |
| /// {/* deprecated */ 1981,/* DiagArray1 */ 348, /* DiagSubGroup3 */ 9}, | |
| /// #endif | |
| /// \endcode | |
| static void emitDiagTable(DiagsInGroupTy &DiagsInGroup, | |
| RecordVec &DiagsInPedantic, | |
| RecordVec &GroupsInPedantic, | |
| const StringToOffsetTable &GroupNames, | |
| raw_ostream &OS) { | |
| unsigned MaxLen = 0; | |
| for (auto const &I: DiagsInGroup) | |
| MaxLen = std::max(MaxLen, (unsigned)I.first.size()); | |
| OS << "\n#ifdef DIAG_ENTRY\n"; | |
| unsigned SubGroupIndex = 1, DiagArrayIndex = 1; | |
| for (auto const &[Name, GroupInfo] : DiagsInGroup) { | |
| // Group option string. | |
| OS << "DIAG_ENTRY("; | |
| OS << GroupInfo.GroupName << " /* "; | |
| if (Name.find_first_not_of("abcdefghijklmnopqrstuvwxyz" | |
| "ABCDEFGHIJKLMNOPQRSTUVWXYZ" | |
| "0123456789!@#$%^*-+=:?") != std::string::npos) | |
| PrintFatalError("Invalid character in diagnostic group '" + Name + "'"); | |
| OS << Name << " */, "; | |
| OS << *GroupNames.GetStringOffset(Name) << ", "; | |
| // Special handling for 'pedantic'. | |
| const bool IsPedantic = Name == "pedantic"; | |
| // Diagnostics in the group. | |
| const std::vector<const Record *> &V = GroupInfo.DiagsInGroup; | |
| const bool hasDiags = | |
| !V.empty() || (IsPedantic && !DiagsInPedantic.empty()); | |
| if (hasDiags) { | |
| OS << "/* DiagArray" << GroupInfo.IDNo << " */ " << DiagArrayIndex | |
| << ", "; | |
| if (IsPedantic) | |
| DiagArrayIndex += DiagsInPedantic.size(); | |
| DiagArrayIndex += V.size() + 1; | |
| } else { | |
| OS << "0, "; | |
| } | |
| // Subgroups. | |
| const std::vector<StringRef> &SubGroups = GroupInfo.SubGroups; | |
| const bool hasSubGroups = | |
| !SubGroups.empty() || (IsPedantic && !GroupsInPedantic.empty()); | |
| if (hasSubGroups) { | |
| OS << "/* DiagSubGroup" << GroupInfo.IDNo << " */ " << SubGroupIndex | |
| << ", "; | |
| if (IsPedantic) | |
| SubGroupIndex += GroupsInPedantic.size(); | |
| SubGroupIndex += SubGroups.size() + 1; | |
| } else { | |
| OS << "0, "; | |
| } | |
| std::string Documentation = GroupInfo.Defs.back() | |
| ->getValue("Documentation") | |
| ->getValue() | |
| ->getAsUnquotedString(); | |
| OS << "R\"(" << StringRef(Documentation).trim() << ")\""; | |
| OS << ")\n"; | |
| } | |
| OS << "#endif // DIAG_ENTRY\n\n"; | |
| } | |
| /// Emit the table of diagnostic categories. | |
| /// | |
| /// The table has the form of macro calls that have two parameters. The | |
| /// category's name as well as an enum that represents the category. The | |
| /// table can be used by defining the macro 'CATEGORY' and including this | |
| /// table right after. | |
| /// | |
| /// \code | |
| /// #ifdef GET_CATEGORY_TABLE | |
| /// CATEGORY("Semantic Issue", DiagCat_Semantic_Issue) | |
| /// CATEGORY("Lambda Issue", DiagCat_Lambda_Issue) | |
| /// #endif | |
| /// \endcode | |
| static void emitCategoryTable(const RecordKeeper &Records, raw_ostream &OS) { | |
| DiagCategoryIDMap CategoriesByID(Records); | |
| OS << "\n#ifdef GET_CATEGORY_TABLE\n"; | |
| for (auto const &C : CategoriesByID) | |
| OS << "CATEGORY(\"" << C << "\", " << getDiagCategoryEnum(C) << ")\n"; | |
| OS << "#endif // GET_CATEGORY_TABLE\n\n"; | |
| } | |
| void clang::EmitClangDiagGroups(const RecordKeeper &Records, raw_ostream &OS) { | |
| // Compute a mapping from a DiagGroup to all of its parents. | |
| DiagGroupParentMap DGParentMap(Records); | |
| ArrayRef<const Record *> Diags = | |
| Records.getAllDerivedDefinitions("Diagnostic"); | |
| ArrayRef<const Record *> DiagGroups = | |
| Records.getAllDerivedDefinitions("DiagGroup"); | |
| DiagsInGroupTy DiagsInGroup; | |
| groupDiagnostics(Diags, DiagGroups, DiagsInGroup); | |
| // All extensions are implicitly in the "pedantic" group. Record the | |
| // implicit set of groups in the "pedantic" group, and use this information | |
| // later when emitting the group information for Pedantic. | |
| RecordVec DiagsInPedantic; | |
| RecordVec GroupsInPedantic; | |
| InferPedantic inferPedantic(DGParentMap, Diags, DiagGroups, DiagsInGroup); | |
| inferPedantic.compute(&DiagsInPedantic, &GroupsInPedantic); | |
| StringToOffsetTable GroupNames; | |
| for (const auto &[Name, Group] : DiagsInGroup) { | |
| GroupNames.GetOrAddStringOffset(Name); | |
| } | |
| emitAllDiagArrays(DiagsInGroup, DiagsInPedantic, GroupsInPedantic, GroupNames, | |
| OS); | |
| emitDiagTable(DiagsInGroup, DiagsInPedantic, GroupsInPedantic, GroupNames, | |
| OS); | |
| emitCategoryTable(Records, OS); | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // Diagnostic name index generation | |
| //===----------------------------------------------------------------------===// | |
| void clang::EmitClangDiagsIndexName(const RecordKeeper &Records, | |
| raw_ostream &OS) { | |
| std::vector<const Record *> Diags = | |
| Records.getAllDerivedDefinitions("Diagnostic"); | |
| sort(Diags, [](const Record *LHS, const Record *RHS) { | |
| return LHS->getName() < RHS->getName(); | |
| }); | |
| for (const Record *Elem : Diags) | |
| OS << "DIAG_NAME_INDEX(" << Elem->getName() << ")\n"; | |
| } | |
| //===----------------------------------------------------------------------===// | |
| // Diagnostic documentation generation | |
| //===----------------------------------------------------------------------===// | |
| namespace docs { | |
| namespace { | |
| bool isRemarkGroup(const Record *DiagGroup, | |
| const DiagsInGroupTy &DiagsInGroup) { | |
| bool AnyRemarks = false, AnyNonRemarks = false; | |
| std::function<void(StringRef)> Visit = [&](StringRef GroupName) { | |
| auto &GroupInfo = DiagsInGroup.find(GroupName)->second; | |
| for (const Record *Diag : GroupInfo.DiagsInGroup) | |
| (isRemark(*Diag) ? AnyRemarks : AnyNonRemarks) = true; | |
| for (StringRef Name : GroupInfo.SubGroups) | |
| Visit(Name); | |
| }; | |
| Visit(DiagGroup->getValueAsString("GroupName")); | |
| if (AnyRemarks && AnyNonRemarks) | |
| PrintFatalError( | |
| DiagGroup->getLoc(), | |
| "Diagnostic group contains both remark and non-remark diagnostics"); | |
| return AnyRemarks; | |
| } | |
| std::string getDefaultSeverity(const Record *Diag) { | |
| return std::string( | |
| Diag->getValueAsDef("DefaultSeverity")->getValueAsString("Name")); | |
| } | |
| std::set<std::string> getDefaultSeverities(const Record *DiagGroup, | |
| const DiagsInGroupTy &DiagsInGroup) { | |
| std::set<std::string> States; | |
| std::function<void(StringRef)> Visit = [&](StringRef GroupName) { | |
| auto &GroupInfo = DiagsInGroup.find(GroupName)->second; | |
| for (const Record *Diag : GroupInfo.DiagsInGroup) | |
| States.insert(getDefaultSeverity(Diag)); | |
| for (const auto &Name : GroupInfo.SubGroups) | |
| Visit(Name); | |
| }; | |
| Visit(DiagGroup->getValueAsString("GroupName")); | |
| return States; | |
| } | |
| /// Write the heading for a diagnostic flag, preceded by an explicit | |
| /// cross-reference target. Naming the target keeps intra-page links working | |
| /// without depending on how Sphinx slugifies heading text, which is a docutils | |
| /// implementation detail that LLVM additionally overrides via | |
| /// `myst_heading_slug_func`. | |
| void writeHeader(StringRef Prefix, StringRef GroupName, raw_ostream &OS) { | |
| OS << "(" << Prefix << GroupName << ")=\n\n### " << Prefix << GroupName | |
| << "\n\n"; | |
| } | |
| void writeDiagnosticText(DiagnosticTextBuilder &Builder, const Record *R, | |
| StringRef Role, raw_ostream &OS) { | |
| StringRef Text = R->getValueAsString("Summary"); | |
| if (Text == "%0") | |
| OS << "The text of this diagnostic is not controlled by Clang.\n\n"; | |
| else | |
| OS << Builder.buildForDocumentation(Role, R) << '\n'; | |
| } | |
| void writeDocumentation(StringRef Documentation, raw_ostream &OS) { | |
| SmallVector<StringRef> Lines; | |
| Documentation.trim("\n").split(Lines, '\n'); | |
| size_t Indent = StringRef::npos; | |
| for (StringRef Line : Lines) | |
| if (size_t I = Line.find_first_not_of(" \t"); I != StringRef::npos) | |
| Indent = std::min(Indent, I); | |
| for (StringRef Line : Lines) | |
| OS << Line.drop_front(std::min(Indent, Line.size())) << '\n'; | |
| } | |
| } // namespace | |
| } // namespace docs | |
| void clang::EmitClangDiagDocs(const RecordKeeper &Records, raw_ostream &OS) { | |
| using namespace docs; | |
| // Get the documentation introduction paragraph. | |
| const Record *Documentation = Records.getDef("GlobalDocumentation"); | |
| if (!Documentation) { | |
| PrintFatalError("The Documentation top-level definition is missing, " | |
| "no documentation will be generated."); | |
| return; | |
| } | |
| OS << Documentation->getValueAsString("Intro") << "\n"; | |
| DiagnosticTextBuilder Builder(Records); | |
| ArrayRef<const Record *> Diags = | |
| Records.getAllDerivedDefinitions("Diagnostic"); | |
| std::vector<const Record *> DiagGroups = | |
| Records.getAllDerivedDefinitions("DiagGroup"); | |
| sort(DiagGroups, diagGroupBeforeByName); | |
| DiagGroupParentMap DGParentMap(Records); | |
| DiagsInGroupTy DiagsInGroup; | |
| groupDiagnostics(Diags, DiagGroups, DiagsInGroup); | |
| // Compute the set of diagnostics that are in -Wpedantic. | |
| { | |
| // Collect into vectors rather than sets: InferPedantic fills a vector by | |
| // marching the records in source order, so the result is deterministic | |
| // without a sort. A set would have to be sorted back into order, and | |
| // source location alone is not a total order because every record from a | |
| // multiclass reports the location of the `def` inside it. | |
| RecordVec DiagsInPedantic; | |
| RecordVec GroupsInPedantic; | |
| InferPedantic inferPedantic(DGParentMap, Diags, DiagGroups, DiagsInGroup); | |
| inferPedantic.compute(&DiagsInPedantic, &GroupsInPedantic); | |
| auto &PedDiags = DiagsInGroup["pedantic"]; | |
| PedDiags.DiagsInGroup.insert(PedDiags.DiagsInGroup.end(), | |
| DiagsInPedantic.begin(), | |
| DiagsInPedantic.end()); | |
| for (auto *Group : GroupsInPedantic) | |
| PedDiags.SubGroups.push_back(Group->getValueAsString("GroupName")); | |
| } | |
| // FIXME: Write diagnostic categories and link to diagnostic groups in each. | |
| // Write out the diagnostic groups. | |
| for (const Record *G : DiagGroups) { | |
| bool IsRemarkGroup = isRemarkGroup(G, DiagsInGroup); | |
| StringRef GroupName = G->getValueAsString("GroupName"); | |
| StringRef Prefix = IsRemarkGroup ? "-R" : "-W"; | |
| auto &GroupInfo = DiagsInGroup[GroupName]; | |
| bool IsSynonym = | |
| GroupInfo.DiagsInGroup.empty() && GroupInfo.SubGroups.size() == 1; | |
| writeHeader(Prefix, GroupName, OS); | |
| if (!IsSynonym) { | |
| // FIXME: Ideally, all the diagnostics in a group should have the same | |
| // default state, but that is not currently the case. | |
| auto DefaultSeverities = getDefaultSeverities(G, DiagsInGroup); | |
| if (!DefaultSeverities.empty() && !DefaultSeverities.count("Ignored")) { | |
| bool AnyNonErrors = DefaultSeverities.count("Warning") || | |
| DefaultSeverities.count("Remark"); | |
| if (!AnyNonErrors) | |
| OS << "This diagnostic is an error by default, but the flag `-Wno-" | |
| << GroupName << "` can be used to disable the error.\n\n"; | |
| else | |
| OS << "This diagnostic is enabled by default.\n\n"; | |
| } else if (DefaultSeverities.size() > 1) { | |
| OS << "Some of the diagnostics controlled by this flag are enabled " | |
| << "by default.\n\n"; | |
| } | |
| } | |
| if (!GroupInfo.SubGroups.empty()) { | |
| if (IsSynonym) | |
| OS << "Synonym for "; | |
| else if (GroupInfo.DiagsInGroup.empty()) | |
| OS << "Controls "; | |
| else | |
| OS << "Also controls "; | |
| sort(GroupInfo.SubGroups); | |
| ListSeparator LS; | |
| // writeHeader emits an explicit target named after the flag, so a `{ref}` | |
| // with no explicit title links to it and renders as the flag name. | |
| for (StringRef Name : GroupInfo.SubGroups) | |
| OS << LS << "{ref}`" << Prefix << Name << "`"; | |
| OS << ".\n\n"; | |
| } | |
| if (!GroupInfo.DiagsInGroup.empty()) { | |
| OS << "**Diagnostic text:**\n\n"; | |
| for (const Record *D : GroupInfo.DiagsInGroup) { | |
| auto Severity = getDefaultSeverity(D); | |
| Severity[0] = tolower(Severity[0]); | |
| if (Severity == "ignored") | |
| Severity = IsRemarkGroup ? "remark" : "warning"; | |
| writeDiagnosticText(Builder, D, Severity, OS); | |
| } | |
| } | |
| auto Doc = G->getValueAsString("Documentation"); | |
| if (!Doc.empty()) | |
| writeDocumentation(Doc, OS); | |
| else if (GroupInfo.SubGroups.empty() && GroupInfo.DiagsInGroup.empty()) | |
| OS << "This diagnostic flag exists for GCC compatibility, and has no " | |
| "effect in Clang.\n"; | |
| OS << "\n"; | |
| } | |
| } |