diff options
author | Richard Smith <richard-llvm@metafoo.co.uk> | 2016-09-12 05:58:29 +0000 |
---|---|---|
committer | Richard Smith <richard-llvm@metafoo.co.uk> | 2016-09-12 05:58:29 +0000 |
commit | 89f2f0d1857f59e6c3625b0aaee69b76a74722cb (patch) | |
tree | 09856018517de17600f613479d6ac6d7ef464a3b /utils | |
parent | c5d3857912daeb84f22ab9fb849f43c0a541f7fe (diff) |
Add a mode to clang-tblgen to generate reference documentation for warning and
remark flags. For now I'm checking in a copy of the built documentation, but we
can replace this with a placeholder (as we do for the attributes reference
documentation) once we enable building this server-side.
git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@281192 91177308-0d34-0410-b5e6-96231b3b80d8
Diffstat (limited to 'utils')
-rw-r--r-- | utils/TableGen/ClangDiagnosticsEmitter.cpp | 424 | ||||
-rw-r--r-- | utils/TableGen/TableGen.cpp | 8 | ||||
-rw-r--r-- | utils/TableGen/TableGenBackends.h | 1 |
3 files changed, 432 insertions, 1 deletions
diff --git a/utils/TableGen/ClangDiagnosticsEmitter.cpp b/utils/TableGen/ClangDiagnosticsEmitter.cpp index dfb715e7f4..2af3e91a49 100644 --- a/utils/TableGen/ClangDiagnosticsEmitter.cpp +++ b/utils/TableGen/ClangDiagnosticsEmitter.cpp @@ -18,6 +18,7 @@ #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/TableGen/Error.h" #include "llvm/TableGen/Record.h" @@ -27,6 +28,7 @@ #include <cctype> #include <functional> #include <map> +#include <set> using namespace llvm; //===----------------------------------------------------------------------===// @@ -141,6 +143,11 @@ static bool beforeThanCompare(const Record *LHS, const Record *RHS) { LHS->getLoc().front().getPointer() < RHS->getLoc().front().getPointer(); } +static bool diagGroupBeforeByName(const Record *LHS, const Record *RHS) { + return LHS->getValueAsString("GroupName") < + RHS->getValueAsString("GroupName"); +} + static bool beforeThanCompareGroups(const GroupInfo *LHS, const GroupInfo *RHS){ assert(!LHS->DiagsInGroup.empty() && !RHS->DiagsInGroup.empty()); return beforeThanCompare(LHS->DiagsInGroup.front(), @@ -892,4 +899,421 @@ void EmitClangDiagsIndexName(RecordKeeper &Records, raw_ostream &OS) { OS << "DIAG_NAME_INDEX(" << R.Name << ")\n"; } } + +//===----------------------------------------------------------------------===// +// Diagnostic documentation generation +//===----------------------------------------------------------------------===// + +namespace docs { +namespace { + +/// Diagnostic text, parsed into pieces. +struct DiagText { + struct Piece { + virtual void print(std::vector<std::string> &RST) = 0; + }; + struct TextPiece : Piece { + StringRef Role; + std::string Text; + void print(std::vector<std::string> &RST) override; + }; + struct PlaceholderPiece : Piece { + int Index; + void print(std::vector<std::string> &RST) override; + }; + struct SelectPiece : Piece { + std::vector<DiagText> Options; + void print(std::vector<std::string> &RST) override; + }; + + std::vector<std::unique_ptr<Piece>> Pieces; + + DiagText() {} + DiagText(StringRef Text); + DiagText(StringRef Kind, StringRef Text); + + template<typename P> void add(P Piece) { + Pieces.push_back(llvm::make_unique<P>(std::move(Piece))); + } + void print(std::vector<std::string> &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(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<TextPiece>(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<typename It> 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<typename It> 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<std::string> &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<std::string> 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<std::string> &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<std::string> &RST) { + RST.push_back(std::string(":placeholder:`") + char('A' + Index) + "`"); +} + +void DiagText::SelectPiece::print(std::vector<std::string> &RST) { + std::vector<size_t> 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<std::string, GroupInfo> &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 (const auto &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 Diag->getValueAsDef("DefaultSeverity")->getValueAsString("Name"); +} + +std::set<std::string> +getDefaultSeverities(const Record *DiagGroup, + const std::map<std::string, GroupInfo> &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; +} + +void writeHeader(StringRef Str, raw_ostream &OS, char Kind = '-') { + OS << Str << "\n" << std::string(Str.size(), Kind) << "\n"; +} + +void writeDiagnosticText(StringRef Role, StringRef Text, raw_ostream &OS) { + if (Text == "%0") + OS << "The text of this diagnostic is not controlled by Clang.\n\n"; + else { + std::vector<std::string> Out; + DiagText(Role, Text).print(Out); + for (auto &Line : Out) + OS << Line << "\n"; + OS << "\n"; + } +} + +} // namespace +} // namespace docs + +void EmitClangDiagDocs(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"; + + std::vector<Record*> Diags = + Records.getAllDerivedDefinitions("Diagnostic"); + std::vector<Record*> DiagGroups = + Records.getAllDerivedDefinitions("DiagGroup"); + std::sort(DiagGroups.begin(), DiagGroups.end(), diagGroupBeforeByName); + + DiagGroupParentMap DGParentMap(Records); + + std::map<std::string, GroupInfo> DiagsInGroup; + groupDiagnostics(Diags, DiagGroups, DiagsInGroup); + + // Compute the set of diagnostics that are in -Wpedantic. + { + RecordSet DiagsInPedantic; + RecordSet 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); + auto &GroupInfo = DiagsInGroup[G->getValueAsString("GroupName")]; + bool IsSynonym = GroupInfo.DiagsInGroup.empty() && + GroupInfo.SubGroups.size() == 1; + + writeHeader((IsRemarkGroup ? "-R" : "-W") + + G->getValueAsString("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-" + << G->getValueAsString("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 "; + + bool First = true; + for (const auto &Name : GroupInfo.SubGroups) { + if (!First) OS << ", "; + OS << "`" << (IsRemarkGroup ? "-R" : "-W") << Name << "`_"; + First = false; + } + 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(Severity, D->getValueAsString("Text"), OS); + } + } + + auto Doc = G->getValueAsString("Documentation"); + if (!Doc.empty()) + OS << Doc; + 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"; + } +} + } // end namespace clang diff --git a/utils/TableGen/TableGen.cpp b/utils/TableGen/TableGen.cpp index ef6ad3ac6c..9aa0c495ce 100644 --- a/utils/TableGen/TableGen.cpp +++ b/utils/TableGen/TableGen.cpp @@ -52,7 +52,8 @@ enum ActionType { GenArmNeon, GenArmNeonSema, GenArmNeonTest, - GenAttrDocs + GenAttrDocs, + GenDiagDocs }; namespace { @@ -133,6 +134,8 @@ cl::opt<ActionType> Action( "Generate ARM NEON tests for clang"), clEnumValN(GenAttrDocs, "gen-attr-docs", "Generate attribute documentation"), + clEnumValN(GenDiagDocs, "gen-diag-docs", + "Generate attribute documentation"), clEnumValEnd)); cl::opt<std::string> @@ -233,6 +236,9 @@ bool ClangTableGenMain(raw_ostream &OS, RecordKeeper &Records) { case GenAttrDocs: EmitClangAttrDocs(Records, OS); break; + case GenDiagDocs: + EmitClangDiagDocs(Records, OS); + break; } return false; diff --git a/utils/TableGen/TableGenBackends.h b/utils/TableGen/TableGenBackends.h index 4adf368cbd..0305ed1c8c 100644 --- a/utils/TableGen/TableGenBackends.h +++ b/utils/TableGen/TableGenBackends.h @@ -69,6 +69,7 @@ void EmitNeonSema2(RecordKeeper &Records, raw_ostream &OS); void EmitNeonTest2(RecordKeeper &Records, raw_ostream &OS); void EmitClangAttrDocs(RecordKeeper &Records, raw_ostream &OS); +void EmitClangDiagDocs(RecordKeeper &Records, raw_ostream &OS); } // end namespace clang |