EricWF updated this revision to Diff 146467.
EricWF added a comment.

@rsmith How does this look?

Turns out we have to fully parse the diagnostic text in order to substitute 
modifier indexes, which is a bit of a complication. This patch does exactly 
that.

There is still some work done to produce useful error messages when the 
substitution is ill-formed, but hopefully the design of this patch is, in 
general, OK>


https://reviews.llvm.org/D46740

Files:
  include/clang/Basic/Diagnostic.td
  include/clang/Basic/DiagnosticSemaKinds.td
  test/TableGen/DiagnosticBase.inc
  test/TableGen/DiagnosticDocs.inc
  test/TableGen/emit-diag-docs.td
  test/TableGen/text-substitution.td
  test/lit.cfg.py
  utils/TableGen/ClangDiagnosticsEmitter.cpp

Index: utils/TableGen/ClangDiagnosticsEmitter.cpp
===================================================================
--- utils/TableGen/ClangDiagnosticsEmitter.cpp
+++ utils/TableGen/ClangDiagnosticsEmitter.cpp
@@ -14,12 +14,14 @@
 #include "llvm/ADT/DenseSet.h"
 #include "llvm/ADT/Optional.h"
 #include "llvm/ADT/PointerUnion.h"
+#include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/SmallPtrSet.h"
 #include "llvm/ADT/SmallString.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringMap.h"
-#include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/Twine.h"
+#include "llvm/Support/Casting.h"
+#include "llvm/Support/Regex.h"
 #include "llvm/TableGen/Error.h"
 #include "llvm/TableGen/Record.h"
 #include "llvm/TableGen/StringToOffsetTable.h"
@@ -441,6 +443,654 @@
   }
 }
 
+namespace {
+enum PieceKind {
+  MultiPieceClass,
+  TextPieceClass,
+  PlaceholderPieceClass,
+  SelectPieceClass,
+  DiffPieceClass,
+  SubstitutionPieceClass,
+};
+
+struct DiagText;
+
+enum ModifierType {
+  MT_Unknown,
+  MT_Placeholder,
+  MT_Select,
+  MT_Sub,
+  MT_Plural,
+  MT_Diff,
+  MT_Ordinal,
+  MT_S,
+  MT_Q,
+  MT_ObjCClass,
+  MT_ObjCInstance,
+};
+
+static StringRef getModifierName(ModifierType MT) {
+  switch (MT) {
+  case MT_Select:
+    return "select";
+  case MT_Sub:
+    return "sub";
+  case MT_Diff:
+    return "diff";
+  case MT_Plural:
+    return "plural";
+  case MT_Ordinal:
+    return "ordinal";
+  case MT_S:
+    return "s";
+  case MT_Q:
+    return "q";
+  case MT_Placeholder:
+    return "";
+  case MT_ObjCClass:
+    return "objcclass";
+  case MT_ObjCInstance:
+    return "objcinstance";
+  case MT_Unknown:
+    return "<<UNKNOWN>>";
+  }
+}
+
+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 {
+  SelectPiece(ModifierType Kind) : Piece(SelectPieceClass), Kind(Kind) {}
+
+  ModifierType Kind;
+  std::vector<Piece *> Options;
+  unsigned Index;
+
+  static bool classof(const Piece *P) {
+    return P->getPieceClass() == SelectPieceClass;
+  }
+};
+
+struct DiffPiece : Piece {
+  DiffPiece() : Piece(DiffPieceClass) {}
+
+  Piece *Options[2] = {};
+  unsigned Indexes[2] = {};
+
+  static bool classof(const Piece *P) {
+    return P->getPieceClass() == DiffPieceClass;
+  }
+};
+
+struct SubstitutionPiece : Piece {
+  SubstitutionPiece() : Piece(SubstitutionPieceClass) {}
+
+  std::string Name;
+  std::vector<unsigned> Modifiers;
+  Piece *Substitution = nullptr;
+
+  static bool classof(const Piece *P) {
+    return P->getPieceClass() == SubstitutionPieceClass;
+  }
+};
+
+/// Diagnostic text, parsed into pieces.
+struct DiagText {
+private:
+  friend struct DiagnosticTextBuilder;
+  std::vector<Piece *> AllocatedPieces;
+  Piece *Root = nullptr;
+
+  template <class T, class... Args> T *create(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() {}
+  Piece *parseDiagText(StringRef &Text, bool Nested = false);
+
+  DiagText(StringRef Text) : Root(nullptr) { Root = parseDiagText(Text); }
+
+  DiagText(StringRef Kind, StringRef Text) : DiagText(Text) {
+    TextPiece *Prefix = create<TextPiece>(Kind, Kind);
+    Prefix->Text += ": ";
+    auto *MP = dyn_cast<MultiPiece>(Root);
+    if (!MP) {
+      MP = create<MultiPiece>();
+      MP->Pieces.push_back(Root);
+      Root = MP;
+    }
+    MP->Pieces.insert(MP->Pieces.begin(), Prefix);
+  }
+
+public:
+  DiagText(DiagText &&O) noexcept
+      : AllocatedPieces(std::move(O.AllocatedPieces)), Root(O.Root) {
+    O.Root = nullptr;
+  }
+
+  Piece *getRoot() const { return Root; }
+
+  ~DiagText() {
+    for (Piece *P : AllocatedPieces)
+      delete P;
+  }
+};
+
+struct DiagnosticTextBuilder {
+  DiagnosticTextBuilder(DiagnosticTextBuilder const &) = delete;
+  DiagnosticTextBuilder &operator=(DiagnosticTextBuilder const &) = delete;
+
+  DiagnosticTextBuilder(RecordKeeper &Records) {
+    for (auto *S : Records.getAllDerivedDefinitions("TextSubstitution"))
+      Substitutions.try_emplace(S->getName(),
+                                DiagText(S->getValueAsString("Substitution")));
+  }
+
+  std::vector<std::string> buildForDocumentation(StringRef Role,
+                                                 const Record *R);
+  std::string buildForDiagnostic(const Record *R);
+
+private:
+  template <class Derived> friend struct DiagTextVisitor;
+  const Record *EvaluatingRecord = nullptr;
+  StringMap<DiagText> Substitutions;
+};
+
+template <class Derived> struct DiagTextVisitor {
+  using ModifierMappingsType = Optional<std::vector<unsigned>>;
+
+private:
+  Derived &getDerived() { return static_cast<Derived &>(*this); }
+
+public:
+  Piece *getSubstitution(SubstitutionPiece *S) const {
+    auto It = Substitutions.find(S->Name);
+    if (It == Substitutions.end())
+      PrintFatalError("Failed to find substitution with name: " + S->Name);
+    return It->second.getRoot();
+  }
+
+  std::vector<unsigned>
+  getSubstitutionMappings(SubstitutionPiece *P,
+                          const ModifierMappingsType &Mappings) const {
+    std::vector<unsigned> NewMappings;
+    for (unsigned Idx : P->Modifiers)
+      NewMappings.push_back(mapIndex(Idx, Mappings));
+    return NewMappings;
+  }
+
+  struct SubstitutionContext {
+    SubstitutionContext(DiagTextVisitor &Visitor, SubstitutionPiece *P)
+        : Visitor(Visitor) {
+      Substitution = Visitor.getSubstitution(P);
+      OldMappings = std::move(Visitor.ModifierMappings);
+      std::vector<unsigned> NewMappings =
+          Visitor.getSubstitutionMappings(P, OldMappings);
+      Visitor.ModifierMappings = std::move(NewMappings);
+    }
+
+    ~SubstitutionContext() {
+      Visitor.ModifierMappings = std::move(OldMappings);
+    }
+
+  private:
+    DiagTextVisitor &Visitor;
+    Optional<std::vector<unsigned>> OldMappings;
+
+  public:
+    Piece *Substitution;
+  };
+
+public:
+  DiagTextVisitor(DiagnosticTextBuilder &Builder)
+      : Builder(Builder), Substitutions(Builder.Substitutions) {}
+
+  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(Diff);
+      CASE(Substitution);
+#undef CASE
+    }
+  }
+
+  void VisitSubstitution(SubstitutionPiece *P) {
+    SubstitutionContext Guard(*this, P);
+    Visit(Guard.Substitution);
+  }
+
+  unsigned mapIndex(unsigned Idx,
+                    ModifierMappingsType const &ModifierMappings) const {
+    if (!ModifierMappings)
+      return Idx;
+    if (ModifierMappings->size() <= Idx)
+      PrintFatalError("Modifier value '" + std::to_string(Idx) +
+                      "' is not valid for this mapping (has " +
+                      std::to_string(ModifierMappings->size()) + " mappings)");
+    return (*ModifierMappings)[Idx];
+  }
+  unsigned mapIndex(unsigned Idx) const {
+    return mapIndex(Idx, ModifierMappings);
+  }
+
+protected:
+  DiagnosticTextBuilder &Builder;
+  StringMap<DiagText> &Substitutions;
+  ModifierMappingsType ModifierMappings;
+};
+
+void escapeRST(StringRef Str, std::string &Out) {
+  for (auto K : Str) {
+    if (StringRef("`*|_[]\\").count(K))
+      Out.push_back('\\');
+    Out.push_back(K);
+  }
+}
+
+template <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 == '|' ? '+' : '-');
+}
+
+struct DiagTextDocPrinter : DiagTextVisitor<DiagTextDocPrinter> {
+  using BaseTy = DiagTextVisitor<DiagTextDocPrinter>;
+  DiagTextDocPrinter(DiagnosticTextBuilder &Builder,
+                     std::vector<std::string> &RST)
+      : BaseTy(Builder), RST(RST) {}
+
+  void gatherNodes(
+      Piece *OrigP, const ModifierMappingsType &CurrentMappings,
+      std::vector<std::pair<Piece *, ModifierMappingsType>> &Pieces) const {
+    if (auto *Sub = dyn_cast<SubstitutionPiece>(OrigP)) {
+      ModifierMappingsType NewMappings =
+          getSubstitutionMappings(Sub, CurrentMappings);
+      return gatherNodes(getSubstitution(Sub), NewMappings, Pieces);
+    }
+    if (auto *MD = dyn_cast<MultiPiece>(OrigP)) {
+      for (auto *Node : MD->Pieces)
+        gatherNodes(Node, CurrentMappings, Pieces);
+      return;
+    }
+    Pieces.push_back(std::make_pair(OrigP, CurrentMappings));
+  }
+
+  void VisitMulti(MultiPiece *P) {
+    if (P->Pieces.empty()) {
+      RST.push_back("");
+      return;
+    }
+
+    if (P->Pieces.size() == 1)
+      return Visit(P->Pieces[0]);
+
+    // Flatten the list of nodes, replacing any substitution pieces with the
+    // recursively flattened substituted node.
+    std::vector<std::pair<Piece *, ModifierMappingsType>> Pieces;
+    gatherNodes(P, ModifierMappings, Pieces);
+
+    std::string EmptyLinePrefix;
+    size_t Start = RST.size();
+    bool HasMultipleLines = true;
+    for (auto NodePair : Pieces) {
+      std::vector<std::string> Lines;
+      DiagTextDocPrinter Visitor{Builder, Lines};
+      Visitor.ModifierMappings = NodePair.second;
+      Visitor.Visit(NodePair.first);
+
+      if (Lines.empty())
+        continue;
+
+      // We need a vertical separator if either this or the previous piece is a
+      // multi-line piece, or this is the last piece.
+      const char *Separator = (Lines.size() > 1 || HasMultipleLines) ? "|" : "";
+      HasMultipleLines = Lines.size() > 1;
+
+      if (Start + Lines.size() > RST.size())
+        RST.resize(Start + Lines.size(), EmptyLinePrefix);
+
+      padToSameLength(Lines.begin(), Lines.end());
+      for (size_t I = 0; I != Lines.size(); ++I)
+        RST[Start + I] += Separator + Lines[I];
+      std::string Empty(Lines[0].size(), ' ');
+      for (size_t I = Start + Lines.size(); I != RST.size(); ++I)
+        RST[I] += Separator + Empty;
+      EmptyLinePrefix += Separator + Empty;
+    }
+    for (size_t I = Start; I != RST.size(); ++I)
+      RST[I] += "|";
+    EmptyLinePrefix += "|";
+
+    makeRowSeparator(EmptyLinePrefix);
+    RST.insert(RST.begin() + Start, EmptyLinePrefix);
+    RST.insert(RST.end(), EmptyLinePrefix);
+  }
+
+  void VisitText(TextPiece *P) {
+    RST.push_back("");
+    auto &S = RST.back();
+
+    StringRef T = P->Text;
+    while (!T.empty() && T.front() == ' ') {
+      RST.back() += " |nbsp| ";
+      T = T.drop_front();
+    }
+
+    std::string Suffix;
+    while (!T.empty() && T.back() == ' ') {
+      Suffix += " |nbsp| ";
+      T = T.drop_back();
+    }
+
+    if (!T.empty()) {
+      S += ':';
+      S += P->Role;
+      S += ":`";
+      escapeRST(T, S);
+      S += '`';
+    }
+
+    S += Suffix;
+  }
+
+  void VisitPlaceholder(PlaceholderPiece *P) {
+    RST.push_back(std::string(":placeholder:`") +
+                  char('A' + mapIndex(P->Index)) + "`");
+  }
+
+  void VisitSelect(SelectPiece *P) {
+    std::vector<size_t> SeparatorIndexes;
+    SeparatorIndexes.push_back(RST.size());
+    RST.emplace_back();
+    for (auto *O : P->Options) {
+      Visit(O);
+      SeparatorIndexes.push_back(RST.size());
+      RST.emplace_back();
+    }
+
+    makeTableRows(RST.begin() + SeparatorIndexes.front(),
+                  RST.begin() + SeparatorIndexes.back() + 1);
+    for (size_t I : SeparatorIndexes)
+      makeRowSeparator(RST[I]);
+  }
+
+  void VisitDiff(DiffPiece *P) { Visit(P->Options[1]); }
+
+  std::vector<std::string> &RST;
+};
+
+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->Kind);
+    if (P->Kind == MT_Select) {
+      Result += "{";
+      for (auto *D : P->Options) {
+        Visit(D);
+        Result += '|';
+      }
+      if (!P->Options.empty())
+        Result.erase(--Result.end());
+      Result += '}';
+    }
+    addInt(mapIndex(P->Index));
+  }
+
+  void VisitDiff(DiffPiece *P) {
+    Result += "%diff{";
+    Visit(P->Options[0]);
+    Result += "|";
+    Visit(P->Options[1]);
+    Result += "}";
+    addInt(mapIndex(P->Indexes[0]));
+    Result += ",";
+    addInt(mapIndex(P->Indexes[1]));
+  }
+
+  void addInt(unsigned Val) {
+    assert(Val < 10);
+    Result += static_cast<char>('0' + Val);
+  }
+
+  std::string &Result;
+};
+
+
+unsigned parseModifier(StringRef &Text) {
+  if (Text.empty() || !isdigit(Text[0]))
+    PrintFatalError("expected modifier in diagnostic");
+
+  unsigned Val = Text[0] - '0';
+  Text = Text.drop_front();
+  return Val;
+}
+
+Piece *DiagText::parseDiagText(StringRef &Text, bool Nested) {
+  std::vector<Piece *> Parsed;
+
+  while (!Text.empty()) {
+    size_t End = (size_t)-2;
+    do
+      End = Nested ? Text.find_first_of("%|}", End + 2)
+                   : Text.find_first_of('%', End + 2);
+    while (End < Text.size() - 1 && Text[End] == '%' &&
+           (Text[End + 1] == '%' || Text[End + 1] == '|'));
+
+    if (End) {
+      Parsed.push_back(create<TextPiece>(Text.slice(0, End), "diagtext"));
+      Text = Text.slice(End, StringRef::npos);
+      if (Text.empty())
+        break;
+    }
+
+    if (Text[0] == '|' || Text[0] == '}')
+      break;
+
+    // Drop the '%'.
+    Text = Text.drop_front();
+
+    // Extract the (optional) modifier.
+    size_t ModLength = Text.find_first_of("0123456789{");
+    StringRef Modifier = Text.slice(0, ModLength);
+    Text = Text.slice(ModLength, StringRef::npos);
+    ModifierType ModType = llvm::StringSwitch<ModifierType>{Modifier}
+                               .Case("select", MT_Select)
+                               .Case("sub", MT_Sub)
+                               .Case("diff", MT_Diff)
+                               .Case("plural", MT_Plural)
+                               .Case("s", MT_S)
+                               .Case("ordinal", MT_Ordinal)
+                               .Case("q", MT_Q)
+                               .Case("", MT_Placeholder)
+                               .Case("objcclass", MT_ObjCClass)
+                               .Case("objcinstance", MT_ObjCInstance)
+                               .Default(MT_Unknown);
+
+    switch (ModType) {
+    case MT_Unknown:
+      PrintFatalError("Unknown modifier type: " + Modifier);
+    case MT_Select:
+    case MT_Plural: {
+      SelectPiece *Select = create<SelectPiece>(ModType);
+      do {
+        Text = Text.drop_front();
+        if (ModType == MT_Plural)
+          while (Text[0] != ':')
+            Text = Text.drop_front();
+        Select->Options.push_back(parseDiagText(Text, true));
+        assert(!Text.empty() && "malformed %select");
+      } while (Text.front() == '|');
+      // Drop the trailing '}'.
+      Text = Text.drop_front(1);
+      Select->Index = parseModifier(Text);
+      Parsed.push_back(Select);
+      continue;
+    }
+    case MT_Sub: {
+      SubstitutionPiece *Sub = create<SubstitutionPiece>();
+      Text = Text.drop_front(); // '{'
+      size_t NameSize = Text.find_first_of('}');
+      assert(NameSize != size_t(-1) && "failed to find the end of the name");
+      assert(NameSize != 0 && "empty name?");
+      Sub->Name = Text.substr(0, NameSize).str();
+      Text = Text.drop_front(NameSize);
+      Text = Text.drop_front(); // '}'
+      if (!Text.empty()) {
+        while (true) {
+          if (!isdigit(Text[0]))
+            break;
+          Sub->Modifiers.push_back(parseModifier(Text));
+          if (Text.empty() || Text[0] != ',')
+            break;
+          Text = Text.drop_front(); // ','
+          assert(!Text.empty() && isdigit(Text[0]) &&
+                 "expected another modifier");
+        }
+      }
+      Parsed.push_back(Sub);
+      continue;
+    }
+    case MT_Diff: {
+      DiffPiece *Diff = create<DiffPiece>();
+      Text = Text.drop_front(); // '{'
+      Diff->Options[0] = parseDiagText(Text, true);
+      Text = Text.drop_front(); // '|'
+      Diff->Options[1] = parseDiagText(Text, true);
+
+      Text = Text.drop_front(); // '}'
+      Diff->Indexes[0] = parseModifier(Text);
+      Text = Text.drop_front(); // ','
+      Diff->Indexes[1] = parseModifier(Text);
+      Parsed.push_back(Diff);
+      continue;
+    }
+    case MT_S: {
+      SelectPiece *Select = create<SelectPiece>(ModType);
+      Select->Options.push_back(create<TextPiece>(""));
+      Select->Options.push_back(create<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_Ordinal: {
+      Parsed.push_back(create<PlaceholderPiece>(ModType, parseModifier(Text)));
+      continue;
+    }
+    }
+  }
+
+  return create<MultiPiece>(Parsed);
+}
+
+std::vector<std::string>
+DiagnosticTextBuilder::buildForDocumentation(StringRef Role, const Record *R) {
+  EvaluatingRecord = R;
+  std::string Text = R->getValueAsString("Text");
+  DiagText D(Role, Text);
+  std::vector<std::string> Result;
+  DiagTextDocPrinter{*this, Result}.Visit(D.Root);
+  return Result;
+}
+
+std::string DiagnosticTextBuilder::buildForDiagnostic(const Record *R) {
+  EvaluatingRecord = R;
+  std::string Text = R->getValueAsString("Text");
+  DiagText D(Text);
+  std::string Result;
+  DiagTextPrinter{*this, Result}.Visit(D.Root);
+  return Result;
+}
+
+} // namespace
+
 //===----------------------------------------------------------------------===//
 // Warning Tables (.inc file) generation.
 //===----------------------------------------------------------------------===//
@@ -455,6 +1105,7 @@
   return ClsName == "CLASS_REMARK";
 }
 
+
 /// ClangDiagsDefsEmitter - The top-level class emits .def files containing
 /// declarations of Clang diagnostics.
 namespace clang {
@@ -470,8 +1121,9 @@
     OS << "#endif\n\n";
   }
 
-  const std::vector<Record*> &Diags =
-    Records.getAllDerivedDefinitions("Diagnostic");
+  DiagnosticTextBuilder DiagTextBuilder(Records);
+
+  std::vector<Record *> Diags = Records.getAllDerivedDefinitions("Diagnostic");
 
   std::vector<Record*> DiagGroups
     = Records.getAllDerivedDefinitions("DiagGroup");
@@ -520,7 +1172,7 @@
 
     // Description string.
     OS << ", \"";
-    OS.write_escaped(R.getValueAsString("Text")) << '"';
+    OS.write_escaped(DiagTextBuilder.buildForDiagnostic(&R)) << '"';
 
     // Warning associated with the diagnostic. This is stored as an index into
     // the alphabetically sorted warning table.
@@ -882,261 +1534,6 @@
 namespace docs {
 namespace {
 
-/// Diagnostic text, parsed into pieces.
-struct DiagText {
-  struct Piece {
-    // This type and its derived classes are move-only.
-    Piece() {}
-    Piece(Piece &&O) {}
-    Piece &operator=(Piece &&O) { return *this; }
-
-    virtual void print(std::vector<std::string> &RST) = 0;
-    virtual ~Piece() {}
-  };
-  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 {
-    SelectPiece() {}
-    SelectPiece(SelectPiece &&O) noexcept : Options(std::move(O.Options)) {}
-    std::vector<DiagText> Options;
-    void print(std::vector<std::string> &RST) override;
-  };
-
-  std::vector<std::unique_ptr<Piece>> Pieces;
-
-  DiagText();
-  DiagText(DiagText &&O) noexcept : Pieces(std::move(O.Pieces)) {}
-
-  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() {}
-
-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;
@@ -1181,12 +1578,13 @@
   OS << Str << "\n" << std::string(Str.size(), Kind) << "\n";
 }
 
-void writeDiagnosticText(StringRef Role, StringRef Text, raw_ostream &OS) {
+void writeDiagnosticText(DiagnosticTextBuilder &Builder, const Record *R,
+                         StringRef Role, raw_ostream &OS) {
+  StringRef Text = R->getValueAsString("Text");
   if (Text == "%0")
     OS << "The text of this diagnostic is not controlled by Clang.\n\n";
   else {
-    std::vector<std::string> Out;
-    DiagText(Role, Text).print(Out);
+    std::vector<std::string> Out = Builder.buildForDocumentation(Role, R);
     for (auto &Line : Out)
       OS << Line << "\n";
     OS << "\n";
@@ -1209,8 +1607,11 @@
 
   OS << Documentation->getValueAsString("Intro") << "\n";
 
+  DiagnosticTextBuilder Builder(Records);
+
   std::vector<Record*> Diags =
       Records.getAllDerivedDefinitions("Diagnostic");
+
   std::vector<Record*> DiagGroups =
       Records.getAllDerivedDefinitions("DiagGroup");
   llvm::sort(DiagGroups.begin(), DiagGroups.end(), diagGroupBeforeByName);
@@ -1300,7 +1701,8 @@
         Severity[0] = tolower(Severity[0]);
         if (Severity == "ignored")
           Severity = IsRemarkGroup ? "remark" : "warning";
-        writeDiagnosticText(Severity, D->getValueAsString("Text"), OS);
+
+        writeDiagnosticText(Builder, D, Severity, OS);
       }
     }
 
Index: test/lit.cfg.py
===================================================================
--- test/lit.cfg.py
+++ test/lit.cfg.py
@@ -57,7 +57,8 @@
 tool_dirs = [config.clang_tools_dir, config.llvm_tools_dir]
 
 tools = [
-    'c-index-test', 'clang-check', 'clang-diff', 'clang-format', 'opt',
+    'c-index-test', 'clang-check', 'clang-diff', 'clang-format', 'clang-tblgen',
+    'opt',
     ToolSubst('%clang_func_map', command=FindTool(
         'clang-func-mapping'), unresolved='ignore'),
 ]
Index: test/TableGen/text-substitution.td
===================================================================
--- /dev/null
+++ test/TableGen/text-substitution.td
@@ -0,0 +1,11 @@
+// RUN: clang-tblgen -gen-clang-diags-defs -I%S %s -o - 2>&1 | \
+// RUN:    FileCheck --strict-whitespace %s
+include "DiagnosticBase.inc"
+
+def DocumentedGroup : DiagGroup<"DocumentedGroup">;
+
+def select_sub : TextSubstitution<"%2 %select{%1}0">;
+
+// CHECK: AHHH
+def AGGG : Warning<"%select{one|two}0 is OK">, InGroup<DocumentedGroup>;
+def sub_diag : Note<"%sub{select_sub}0">;
Index: test/TableGen/emit-diag-docs.td
===================================================================
--- /dev/null
+++ test/TableGen/emit-diag-docs.td
@@ -0,0 +1,81 @@
+// RUN: clang-tblgen -gen-diag-docs -I%S %s -o - 2>&1 | \
+// RUN:    FileCheck --strict-whitespace %s
+include "DiagnosticBase.inc"
+
+def MyGroup : DiagGroup<"MyGroupName">;
+
+def MyKinds : TextSubstitution<"%select{food|forests}0">;
+def MyGoodBad : TextSubstitution<"%select{good|bad}0">;
+def MySubNested : TextSubstitution<"%sub{MyGoodBad}1 %sub{MyKinds}2 are %sub{MyGoodBad}1 according to %0">;
+
+// CHECK: -WMyGroupName
+// CHECK: **Diagnostic text:**
+
+let Group = MyGroup in {
+
+// CHECK:      |:warning:`warning:` |nbsp| :diagtext:`this is my diff text`|
+// CHECK-NEXT: +-----------------------------------------------------------+
+def CheckDiff : Warning<"%diff{$ is not $|this is my diff text}0,1">;
+
+
+// CHECK:      |:warning:`warning:` |nbsp| :placeholder:`A` |nbsp| :diagtext:`is my modifier test` |nbsp| :placeholder:`B`|
+// CHECK-NEXT: +----------------------------------------------------------------------------------------------------------+
+def CheckModifier : Warning<"%0 is my modifier test %1">;
+
+
+// CHECK:      |:warning:`warning:` |nbsp| :diagtext:`This is the` |nbsp| :placeholder:`A` |nbsp| :diagtext:`test I've written`|
+// CHECK-NEXT: +---------------------------------------------------------------------------------------------------------------+
+def CheckOrdinal : Warning<"This is the %ordinal0 test I've written">;
+
+
+// CHECK:      |:warning:`warning:` |nbsp| :diagtext:`I wrote` |nbsp| |+-------------------------------+| |nbsp| :diagtext:`tests`|
+// CHECK-NEXT: |                                                      ||:diagtext:`:no`                ||                         |
+// CHECK-NEXT: |                                                      |+-------------------------------+|                         |
+// CHECK-NEXT: |                                                      ||:diagtext:`:one`               ||                         |
+// CHECK-NEXT: |                                                      |+-------------------------------+|                         |
+// CHECK-NEXT: |                                                      ||+-----------------------------+||                         |
+// CHECK-NEXT: |                                                      |||:diagtext:`:`:placeholder:`A`|||                         |
+// CHECK-NEXT: |                                                      ||+-----------------------------+||                         |
+// CHECK-NEXT: |                                                      |+-------------------------------+|                         |
+// CHECK-NEXT: +------------------------------------------------------+---------------------------------+-------------------------+
+def CheckPlural : Warning<"I wrote %plural{0:no|1:one|:%0}0 tests">;
+
+
+// CHECK:      |:warning:`warning:` |nbsp| :diagtext:`bad type` |nbsp| :placeholder:`A`|
+// CHECK-NEXT: +-----------------------------------------------------------------------+
+def CheckQ : Warning<"bad type %q0">;
+
+
+// CHECK:      |:warning:`warning:` |nbsp| :diagtext:`My test`|+-------------+| |nbsp| :diagtext:`are the best!`|
+// CHECK-NEXT: |                                              ||             ||                                 |
+// CHECK-NEXT: |                                              |+-------------+|                                 |
+// CHECK-NEXT: |                                              ||:diagtext:`s`||                                 |
+// CHECK-NEXT: |                                              |+-------------+|                                 |
+// CHECK-NEXT: +----------------------------------------------+---------------+---------------------------------+
+def CheckS : Warning<"My test%s0 are the best!">;
+
+
+// CHECK:      |:warning:`warning:` |nbsp| :diagtext:`this is my select test:` |nbsp| |+---------------+|
+// CHECK-NEXT: |                                                                      ||:diagtext:`one`||
+// CHECK-NEXT: |                                                                      |+---------------+|
+// CHECK-NEXT: |                                                                      ||:diagtext:`two`||
+// CHECK-NEXT: |                                                                      |+---------------+|
+// CHECK-NEXT: +----------------------------------------------------------------------+-----------------+
+def CheckSelect : Warning<"this is my select test: %select{one|two}0 and it is %select{good|bad}1">;
+
+
+// CHECK:      +-------------------------------------------------------+------------------+--------+---------------------+-------------------------------+------------------+--------------------------------------------------------+
+// CHECK-NEXT: |:warning:`warning:` |nbsp| :diagtext:`They say` |nbsp| |+----------------+| |nbsp| |+-------------------+| |nbsp| :diagtext:`are` |nbsp| |+----------------+| |nbsp| :diagtext:`according to` |nbsp| :placeholder:`D`|
+// CHECK-NEXT: |                                                       ||:diagtext:`good`||        ||:diagtext:`food`   ||                               ||:diagtext:`good`||                                                        |
+// CHECK-NEXT: |                                                       |+----------------+|        |+-------------------+|                               |+----------------+|                                                        |
+// CHECK-NEXT: |                                                       ||:diagtext:`bad` ||        ||:diagtext:`forests`||                               ||:diagtext:`bad` ||                                                        |
+// CHECK-NEXT: |                                                       |+----------------+|        |+-------------------+|                               |+----------------+|                                                        |
+// CHECK-NEXT: +-------------------------------------------------------+------------------+--------+---------------------+-------------------------------+------------------+--------------------------------------------------------+
+def CheckSubstitution : Warning<"They say %sub{MySubNested}3,1,0">;
+
+
+// CHECK:      |:warning:`warning:` |nbsp| :diagtext:`this is my warning text`|
+// CHECK-NEXT: +--------------------------------------------------------------+
+def CheckText : Warning<"this is my warning text">;
+
+}
Index: test/TableGen/DiagnosticDocs.inc
===================================================================
--- /dev/null
+++ test/TableGen/DiagnosticDocs.inc
@@ -0,0 +1,75 @@
+
+def GlobalDocumentation {
+  code Intro =[{..
+  -------------------------------------------------------------------
+  NOTE: This file is automatically generated by running clang-tblgen
+  -gen-diag-docs. Do not edit this file by hand!!
+  -------------------------------------------------------------------
+
+.. Add custom CSS to output. FIXME: This should be put into <head> rather
+   than the start of <body>.
+.. raw:: html
+
+    <style>
+    table.docutils {
+      width: 1px;
+    }
+    table.docutils td {
+      border: none;
+      padding: 0 0 0 0.2em;
+      vertical-align: middle;
+      white-space: nowrap;
+      width: 1px;
+      font-family: monospace;
+    }
+    table.docutils tr + tr {
+      border-top: 0.2em solid #aaa;
+    }
+    .error {
+      font-family: monospace;
+      font-weight: bold;
+      color: #c00;
+    }
+    .warning {
+      font-family: monospace;
+      font-weight: bold;
+      color: #80a;
+    }
+    .remark {
+      font-family: monospace;
+      font-weight: bold;
+      color: #00c;
+    }
+    .diagtext {
+      font-family: monospace;
+      font-weight: bold;
+    }
+    </style>
+
+.. FIXME: rST doesn't support formatting this, so we format all <td> elements
+          as monospace font face instead.
+.. |nbsp| unicode:: 0xA0
+   :trim:
+
+.. Roles generated by clang-tblgen.
+.. role:: error
+.. role:: warning
+.. role:: remark
+.. role:: diagtext
+.. role:: placeholder(emphasis)
+
+=========================
+Diagnostic flags in Clang
+=========================
+.. contents::
+   :local:
+
+Introduction
+============
+
+This page lists the diagnostic flags currently supported by Clang.
+
+Diagnostic flags
+================
+}];
+}
Index: test/TableGen/DiagnosticBase.inc
===================================================================
--- test/TableGen/DiagnosticBase.inc
+++ test/TableGen/DiagnosticBase.inc
@@ -1,35 +1,130 @@
-// Define the diagnostic mappings.
-class DiagMapping;
-def MAP_IGNORE  : DiagMapping;
-def MAP_WARNING : DiagMapping;
-def MAP_ERROR   : DiagMapping;
-def MAP_FATAL   : DiagMapping;
+//===--- Diagnostic.td - C Language Family Diagnostic Handling ------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+//  This file defines the TableGen core definitions for the diagnostics
+//  and diagnostic control.
+//
+//===----------------------------------------------------------------------===//
+
+// See the Internals Manual, section The Diagnostics Subsystem for an overview.
+
+// Define the diagnostic severities.
+class Severity<string N> {
+  string Name = N;
+}
+def SEV_Ignored : Severity<"Ignored">;
+def SEV_Remark  : Severity<"Remark">;
+def SEV_Warning : Severity<"Warning">;
+def SEV_Error   : Severity<"Error">;
+def SEV_Fatal   : Severity<"Fatal">;
 
 // Define the diagnostic classes.
 class DiagClass;
 def CLASS_NOTE      : DiagClass;
+def CLASS_REMARK    : DiagClass;
 def CLASS_WARNING   : DiagClass;
 def CLASS_EXTENSION : DiagClass;
 def CLASS_ERROR     : DiagClass;
 
+// Responses to a diagnostic in a SFINAE context.
+class SFINAEResponse;
+def SFINAE_SubstitutionFailure : SFINAEResponse;
+def SFINAE_Suppress            : SFINAEResponse;
+def SFINAE_Report              : SFINAEResponse;
+def SFINAE_AccessControl       : SFINAEResponse;
+
+// Textual substitutions which may be performed on the text of diagnostics
+class TextSubstitution<string Text> {
+  string Substitution = Text;
+  // TODO: These are only here to allow substitutions to be declared inline with
+  // diagnostics
+  string Component = "";
+  string CategoryName = "";
+}
+
+// Diagnostic Categories.  These can be applied to groups or individual
+// diagnostics to specify a category.
+class DiagCategory<string Name> {
+  string CategoryName = Name;
+}
+
+// Diagnostic Groups.
 class DiagGroup<string Name, list<DiagGroup> subgroups = []> {
   string GroupName = Name;
   list<DiagGroup> SubGroups = subgroups;
   string CategoryName = "";
+  code Documentation = [{}];
 }
 class InGroup<DiagGroup G> { DiagGroup Group = G; }
+//class IsGroup<string Name> { DiagGroup Group = DiagGroup<Name>; }
+
+include "DiagnosticDocs.inc"
 
 // All diagnostics emitted by the compiler are an indirect subclass of this.
-class Diagnostic<string text, DiagClass DC, DiagMapping defaultmapping> {
+class Diagnostic<string text, DiagClass DC, Severity defaultmapping> {
+  /// Component is specified by the file with a big let directive.
+  string         Component = ?;
   string         Text = text;
   DiagClass      Class = DC;
-  DiagMapping DefaultMapping = defaultmapping;
+  SFINAEResponse SFINAE = SFINAE_Suppress;
+  bit            AccessControl = 0;
+  bit            WarningNoWerror = 0;
+  bit            ShowInSystemHeader = 0;
+  Severity       DefaultSeverity = defaultmapping;
   DiagGroup      Group;
   string         CategoryName = "";
 }
 
-class Error<string str>     : Diagnostic<str, CLASS_ERROR, MAP_ERROR>;
-class Warning<string str>   : Diagnostic<str, CLASS_WARNING, MAP_WARNING>;
-class Extension<string str> : Diagnostic<str, CLASS_EXTENSION, MAP_IGNORE>;
-class ExtWarn<string str>   : Diagnostic<str, CLASS_EXTENSION, MAP_WARNING>;
-class Note<string str>      : Diagnostic<str, CLASS_NOTE, MAP_FATAL/*ignored*/>;
+class SFINAEFailure {
+  SFINAEResponse SFINAE = SFINAE_SubstitutionFailure;
+}
+class NoSFINAE {
+  SFINAEResponse SFINAE = SFINAE_Report;
+}
+class AccessControl {
+  SFINAEResponse SFINAE = SFINAE_AccessControl;
+}
+
+class ShowInSystemHeader {
+  bit ShowInSystemHeader = 1;
+}
+
+class SuppressInSystemHeader {
+  bit ShowInSystemHeader = 0;
+}
+
+// FIXME: ExtWarn and Extension should also be SFINAEFailure by default.
+class Error<string str>     : Diagnostic<str, CLASS_ERROR, SEV_Error>, SFINAEFailure {
+  bit ShowInSystemHeader = 1;
+}
+// Warnings default to on (but can be default-off'd with DefaultIgnore).
+// This is used for warnings about questionable code; warnings about
+// accepted language extensions should use Extension or ExtWarn below instead.
+class Warning<string str>   : Diagnostic<str, CLASS_WARNING, SEV_Warning>;
+// Remarks can be turned on with -R flags and provide commentary, e.g. on
+// optimizer decisions.
+class Remark<string str>    : Diagnostic<str, CLASS_REMARK, SEV_Ignored>;
+// Extensions are warnings about accepted language extensions.
+// Extension warnings are default-off but enabled by -pedantic.
+class Extension<string str> : Diagnostic<str, CLASS_EXTENSION, SEV_Ignored>;
+// ExtWarns are warnings about accepted language extensions.
+// ExtWarn warnings are default-on.
+class ExtWarn<string str>   : Diagnostic<str, CLASS_EXTENSION, SEV_Warning>;
+// Notes can provide supplementary information on errors, warnings, and remarks.
+class Note<string str>      : Diagnostic<str, CLASS_NOTE, SEV_Fatal/*ignored*/>;
+
+
+class DefaultIgnore { Severity DefaultSeverity = SEV_Ignored; }
+class DefaultWarn   { Severity DefaultSeverity = SEV_Warning; }
+class DefaultError  { Severity DefaultSeverity = SEV_Error; }
+class DefaultFatal  { Severity DefaultSeverity = SEV_Fatal; }
+class DefaultWarnNoWerror {
+  bit WarningNoWerror = 1;
+}
+class DefaultRemark { Severity DefaultSeverity = SEV_Remark; }
Index: include/clang/Basic/DiagnosticSemaKinds.td
===================================================================
--- include/clang/Basic/DiagnosticSemaKinds.td
+++ include/clang/Basic/DiagnosticSemaKinds.td
@@ -3613,41 +3613,39 @@
 def note_ovl_candidate_non_deduced_mismatch_qualified : Note<
     "candidate template ignored: could not match %q0 against %q1">;
 
-// Note that we don't treat templates differently for this diagnostic.
-def note_ovl_candidate_arity : Note<"candidate "
+def select_ovl_candidate_kind : TextSubstitution<
     "%select{function|function|constructor|function|function|constructor|"
     "constructor (the implicit default constructor)|"
     "constructor (the implicit copy constructor)|"
     "constructor (the implicit move constructor)|"
     "function (the implicit copy assignment operator)|"
     "function (the implicit move assignment operator)|"
     "inherited constructor|"
-    "inherited constructor}0 %select{|template }1"
-    "not viable: requires%select{ at least| at most|}2 %3 argument%s3, but %4 "
-    "%plural{1:was|:were}4 provided">;
-
-def note_ovl_candidate_arity_one : Note<"candidate "
-    "%select{function|function|constructor|function|function|constructor|"
+    "inherited constructor}0">;
+def select_ovl_template_kind : TextSubstitution<
+    "%select{function|function|constructor|"
+    "function |function |constructor |"
     "constructor (the implicit default constructor)|"
     "constructor (the implicit copy constructor)|"
     "constructor (the implicit move constructor)|"
     "function (the implicit copy assignment operator)|"
     "function (the implicit move assignment operator)|"
     "inherited constructor|"
-    "inherited constructor}0 %select{|template }1not viable: "
+    "inherited constructor }0">;
+
+// Note that we don't treat templates differently for this diagnostic.
+def note_ovl_candidate_arity : Note<"candidate "
+    "%sub{select_ovl_candidate_kind}0 %select{|template }1"
+    "not viable: requires%select{ at least| at most|}2 %3 argument%s3, but %4 "
+    "%plural{1:was|:were}4 provided">;
+
+def note_ovl_candidate_arity_one : Note<"candidate "
+    "%sub{select_ovl_candidate_kind}0 %select{|template }1not viable: "
     "%select{requires at least|allows at most single|requires single}2 "
     "argument %3, but %plural{0:no|:%4}4 arguments were provided">;
 
 def note_ovl_candidate_deleted : Note<
-    "candidate %select{function|function|constructor|"
-    "function |function |constructor |"
-    "constructor (the implicit default constructor)|"
-    "constructor (the implicit copy constructor)|"
-    "constructor (the implicit move constructor)|"
-    "function (the implicit copy assignment operator)|"
-    "function (the implicit move assignment operator)|"
-    "inherited constructor|"
-    "inherited constructor }0%1 has been "
+    "candidate %sub{select_ovl_template_kind}0%1 has been "
     "%select{explicitly made unavailable|explicitly deleted|"
     "implicitly deleted}2">;
 
@@ -3657,120 +3655,48 @@
 // to complete the type, which doesn't involve changes to the call line
 // anyway.  If people complain, we can change it.
 def note_ovl_candidate_bad_conv_incomplete : Note<"candidate "
-    "%select{function|function|constructor|"
-    "function |function |constructor |"
-    "constructor (the implicit default constructor)|"
-    "constructor (the implicit copy constructor)|"
-    "constructor (the implicit move constructor)|"
-    "function (the implicit copy assignment operator)|"
-    "function (the implicit move assignment operator)|"
-    "inherited constructor|"
-    "inherited constructor }0%1 "
+    "%sub{select_ovl_template_kind}0%1 "
     "not viable: cannot convert argument of incomplete type "
     "%diff{$ to $|to parameter type}2,3 for "
     "%select{%ordinal5 argument|object argument}4"
     "%select{|; dereference the argument with *|"
     "; take the address of the argument with &|"
     "; remove *|"
     "; remove &}6">;
 def note_ovl_candidate_bad_list_argument : Note<"candidate "
-    "%select{function|function|constructor|"
-    "function |function |constructor |"
-    "constructor (the implicit default constructor)|"
-    "constructor (the implicit copy constructor)|"
-    "constructor (the implicit move constructor)|"
-    "function (the implicit copy assignment operator)|"
-    "function (the implicit move assignment operator)|"
-    "inherited constructor|"
-    "inherited constructor }0%1 "
+    "%sub{select_ovl_template_kind}0%1 "
     "not viable: cannot convert initializer list argument to %3">;
 def note_ovl_candidate_bad_overload : Note<"candidate "
-    "%select{function|function|constructor|"
-    "function |function |constructor |"
-    "constructor (the implicit default constructor)|"
-    "constructor (the implicit copy constructor)|"
-    "constructor (the implicit move constructor)|"
-    "function (the implicit copy assignment operator)|"
-    "function (the implicit move assignment operator)|"
-    "inherited constructor|"
-    "inherited constructor }0%1"
+    "%sub{select_ovl_template_kind}0%1"
     " not viable: no overload of %3 matching %2 for %ordinal4 argument">;
 def note_ovl_candidate_bad_conv : Note<"candidate "
-    "%select{function|function|constructor|"
-    "function |function |constructor |"
-    "constructor (the implicit default constructor)|"
-    "constructor (the implicit copy constructor)|"
-    "constructor (the implicit move constructor)|"
-    "function (the implicit copy assignment operator)|"
-    "function (the implicit move assignment operator)|"
-    "inherited constructor|"
-    "inherited constructor }0%1"
+    "%sub{select_ovl_template_kind}0%1"
     " not viable: no known conversion "
     "%diff{from $ to $|from argument type to parameter type}2,3 for "
     "%select{%ordinal5 argument|object argument}4"
     "%select{|; dereference the argument with *|"
     "; take the address of the argument with &|"
     "; remove *|"
     "; remove &}6">;
 def note_ovl_candidate_bad_arc_conv : Note<"candidate "
-    "%select{function|function|constructor|"
-    "function |function |constructor |"
-    "constructor (the implicit default constructor)|"
-    "constructor (the implicit copy constructor)|"
-    "constructor (the implicit move constructor)|"
-    "function (the implicit copy assignment operator)|"
-    "function (the implicit move assignment operator)|"
-    "inherited constructor|"
-    "inherited constructor }0%1"
+    "%sub{select_ovl_template_kind}0%1"
     " not viable: cannot implicitly convert argument "
     "%diff{of type $ to $|type to parameter type}2,3 for "
     "%select{%ordinal5 argument|object argument}4 under ARC">;
 def note_ovl_candidate_bad_lvalue : Note<"candidate "
-    "%select{function|function|constructor|"
-    "function |function |constructor |"
-    "constructor (the implicit default constructor)|"
-    "constructor (the implicit copy constructor)|"
-    "constructor (the implicit move constructor)|"
-    "function (the implicit copy assignment operator)|"
-    "function (the implicit move assignment operator)|"
-    "inherited constructor|"
-    "inherited constructor }0%1"
+    "%sub{select_ovl_template_kind}0%1"
     " not viable: expects an l-value for "
     "%select{%ordinal3 argument|object argument}2">;
 def note_ovl_candidate_bad_addrspace : Note<"candidate "
-    "%select{function|function|constructor|"
-    "function |function |constructor |"
-    "constructor (the implicit default constructor)|"
-    "constructor (the implicit copy constructor)|"
-    "constructor (the implicit move constructor)|"
-    "function (the implicit copy assignment operator)|"
-    "function (the implicit move assignment operator)|"
-    "inherited constructor|"
-    "inherited constructor }0%1 not viable: "
+    "%sub{select_ovl_template_kind}0%1 not viable: "
     "%select{%ordinal6|'this'}5 argument (%2) is in "
     "address space %3, but parameter must be in address space %4">;
 def note_ovl_candidate_bad_gc : Note<"candidate "
-    "%select{function|function|constructor|"
-    "function |function |constructor |"
-    "constructor (the implicit default constructor)|"
-    "constructor (the implicit copy constructor)|"
-    "constructor (the implicit move constructor)|"
-    "function (the implicit copy assignment operator)|"
-    "function (the implicit move assignment operator)|"
-    "inherited constructor|"
-    "inherited constructor }0%1 not viable: "
+    "%sub{select_ovl_template_kind}0%1 not viable: "
     "%select{%ordinal6|'this'}5 argument (%2) has %select{no|__weak|__strong}3 "
     "ownership, but parameter has %select{no|__weak|__strong}4 ownership">;
 def note_ovl_candidate_bad_ownership : Note<"candidate "
-    "%select{function|function|constructor|"
-    "function |function |constructor |"
-    "constructor (the implicit default constructor)|"
-    "constructor (the implicit copy constructor)|"
-    "constructor (the implicit move constructor)|"
-    "function (the implicit copy assignment operator)|"
-    "function (the implicit move assignment operator)|"
-    "inherited constructor|"
-    "inherited constructor }0%1 not viable: "
+    "%sub{select_ovl_template_kind}0%1 not viable: "
     "%select{%ordinal6|'this'}5 argument (%2) has "
     "%select{no|__unsafe_unretained|__strong|__weak|__autoreleasing}3 ownership,"
     " but parameter has %select{no|__unsafe_unretained|__strong|__weak|"
@@ -3783,54 +3709,22 @@
     "%select{const|restrict|const or restrict|volatile|const or volatile|"
     "volatile or restrict|const, volatile, or restrict}3">;
 def note_ovl_candidate_bad_cvr : Note<"candidate "
-    "%select{function|function|constructor|"
-    "function |function |constructor |"
-    "constructor (the implicit default constructor)|"
-    "constructor (the implicit copy constructor)|"
-    "constructor (the implicit move constructor)|"
-    "function (the implicit copy assignment operator)|"
-    "function (the implicit move assignment operator)|"
-    "inherited constructor|"
-    "inherited constructor }0%1 not viable: "
+    "%sub{select_ovl_template_kind}0%1 not viable: "
     "%ordinal4 argument (%2) would lose "
     "%select{const|restrict|const and restrict|volatile|const and volatile|"
     "volatile and restrict|const, volatile, and restrict}3 qualifier"
     "%select{||s||s|s|s}3">;
 def note_ovl_candidate_bad_unaligned : Note<"candidate "
-    "%select{function|function|constructor|"
-    "function |function |constructor |"
-    "constructor (the implicit default constructor)|"
-    "constructor (the implicit copy constructor)|"
-    "constructor (the implicit move constructor)|"
-    "function (the implicit copy assignment operator)|"
-    "function (the implicit move assignment operator)|"
-    "inherited constructor|"
-    "inherited constructor }0%1 not viable: "
+    "%sub{select_ovl_template_kind}0%1 not viable: "
     "%ordinal4 argument (%2) would lose __unaligned qualifier">;
 def note_ovl_candidate_bad_base_to_derived_conv : Note<"candidate "
-    "%select{function|function|constructor|"
-    "function |function |constructor |"
-    "constructor (the implicit default constructor)|"
-    "constructor (the implicit copy constructor)|"
-    "constructor (the implicit move constructor)|"
-    "function (the implicit copy assignment operator)|"
-    "function (the implicit move assignment operator)|"
-    "inherited constructor|"
-    "inherited constructor }0%1 not viable: "
+    "%sub{select_ovl_template_kind}0%1 not viable: "
     "cannot %select{convert from|convert from|bind}2 "
     "%select{base class pointer|superclass|base class object of type}2 %3 to "
     "%select{derived class pointer|subclass|derived class reference}2 %4 for "
     "%ordinal5 argument">;
 def note_ovl_candidate_bad_target : Note<
-    "candidate %select{function|function|constructor|"
-    "function|function|constructor|"
-    "constructor (the implicit default constructor)|"
-    "constructor (the implicit copy constructor)|"
-    "constructor (the implicit move constructor)|"
-    "function (the implicit copy assignment operator)|"
-    "function (the implicit move assignment operator)|"
-    "inherited constructor|"
-    "inherited constructor}0 not viable: "
+    "candidate %sub{select_ovl_candidate_kind}0 not viable: "
     "call to "
     "%select{__device__|__global__|__host__|__host__ __device__|invalid}1 function from"
     " %select{__device__|__global__|__host__|__host__ __device__|invalid}2 function">;
@@ -7749,6 +7643,10 @@
   "explicit conversion functions are incompatible with C++98">,
   InGroup<CXX98Compat>, DefaultIgnore;
 
+def select_special_member : TextSubstitution<
+  "%select{default constructor|copy constructor|move constructor|"
+  "copy assignment operator|move assignment operator|destructor}0">;
+
 // C++11 defaulted functions
 def err_defaulted_special_member_params : Error<
   "an explicitly-defaulted %select{|copy |move }0constructor cannot "
@@ -7763,9 +7661,8 @@
   "an explicitly-defaulted %select{copy|move}0 assignment operator may not "
   "have 'const'%select{, 'constexpr'|}1 or 'volatile' qualifiers">;
 def err_defaulted_special_member_volatile_param : Error<
-  "the parameter for an explicitly-defaulted %select{<<ERROR>>|"
-  "copy constructor|move constructor|copy assignment operator|"
-  "move assignment operator|<<ERROR>>}0 may not be volatile">;
+  "the parameter for an explicitly-defaulted %sub{select_special_member}0 "
+  "may not be volatile">;
 def err_defaulted_special_member_move_const_param : Error<
   "the parameter for an explicitly-defaulted move "
   "%select{constructor|assignment operator}0 may not be const">;
@@ -7777,17 +7674,13 @@
   "the parameter for an explicitly-defaulted copy assignment operator must be an "
   "lvalue reference type">;
 def err_incorrect_defaulted_exception_spec : Error<
-  "exception specification of explicitly defaulted %select{default constructor|"
-  "copy constructor|move constructor|copy assignment operator|move assignment "
-  "operator|destructor}0 does not match the "
-  "calculated one">;
+  "exception specification of explicitly defaulted %sub{select_special_member}0 "
+  "does not match the calculated one">;
 def err_incorrect_defaulted_constexpr : Error<
-  "defaulted definition of %select{default constructor|copy constructor|"
-  "move constructor|copy assignment operator|move assignment operator}0 "
+  "defaulted definition of %sub{select_special_member}0 "
   "is not constexpr">;
 def err_out_of_line_default_deletes : Error<
-  "defaulting this %select{default constructor|copy constructor|move "
-  "constructor|copy assignment operator|move assignment operator|destructor}0 "
+  "defaulting this %sub{select_special_member}0 "
   "would delete it after its first declaration">;
 def warn_vbase_moved_multiple_times : Warning<
   "defaulted move assignment operator of %0 will move assign virtual base "
Index: include/clang/Basic/Diagnostic.td
===================================================================
--- include/clang/Basic/Diagnostic.td
+++ include/clang/Basic/Diagnostic.td
@@ -39,6 +39,15 @@
 def SFINAE_Report              : SFINAEResponse;
 def SFINAE_AccessControl       : SFINAEResponse;
 
+// Textual substitutions which may be performed on the text of diagnostics
+class TextSubstitution<string Text> {
+  string Substitution = Text;
+  // TODO: These are only here to allow substitutions to be declared inline with
+  // diagnostics
+  string Component = "";
+  string CategoryName = "";
+}
+
 // Diagnostic Categories.  These can be applied to groups or individual
 // diagnostics to specify a category.
 class DiagCategory<string Name> {
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to