[clang] [clang][docs] Generate diagnostics reference as Markdown (PR #221361)

Reid Kleckner via cfe-commits cfe-commits at lists.llvm.org
Tue Sep 8 13:02:08 PDT 2026


https://github.com/rnk updated https://github.com/llvm/llvm-project/pull/221361

>From decfe2d051ab9f72679f2307d57b048914bc2c12 Mon Sep 17 00:00:00 2001
From: Reid Kleckner <rkleckner at nvidia.com>
Date: Tue, 1 Sep 2026 21:51:12 +0000
Subject: [PATCH] [clang][docs] Generate diagnostics reference as Markdown

Generate Clang's diagnostics reference from MyST Markdown rather than
reStructuredText. Update diagnostic-group documentation to use native MyST
roles and references, and have the TableGen emitter produce MyST list tables
for diagnostic alternatives.

Preserve the existing rendered diagnostic layout and styling, including
readable group anchors. Flatten substitutions that contain alternatives into
the enclosing table so they do not introduce redundant one-cell table
wrappers.
---
 clang/docs/CMakeLists.txt                     |   4 +-
 clang/include/clang/Basic/DiagnosticDocs.td   | 143 ++++---
 clang/include/clang/Basic/DiagnosticGroups.td |  98 ++---
 clang/test/TableGen/DiagnosticDocs.inc        |  77 +---
 clang/test/TableGen/diag-docs-determinism.td  |  33 ++
 clang/test/TableGen/emit-diag-docs.td         | 156 ++++---
 .../TableGen/ClangDiagnosticsEmitter.cpp      | 405 +++++++++++-------
 7 files changed, 511 insertions(+), 405 deletions(-)
 create mode 100644 clang/test/TableGen/diag-docs-determinism.td

diff --git a/clang/docs/CMakeLists.txt b/clang/docs/CMakeLists.txt
index 09dd4e5a60fa7d..f6f70cd7ce01b3 100644
--- a/clang/docs/CMakeLists.txt
+++ b/clang/docs/CMakeLists.txt
@@ -104,7 +104,7 @@ if (LLVM_ENABLE_SPHINX)
     # directory before we run sphinx.
     set(clang_generated_docs
       AttributeReference.md
-      DiagnosticsReference.rst
+      DiagnosticsReference.md
       AMDGPUBuiltinReference.rst
       ClangCommandLineReference.rst
       analyzer/user-docs/Options.rst)
@@ -146,7 +146,7 @@ if (LLVM_ENABLE_SPHINX)
 
     # Generated files
     gen_rst_file_from_td(AttributeReference.md -gen-attr-docs ../include/clang/Basic/Attr.td "${docs_targets}")
-    gen_rst_file_from_td(DiagnosticsReference.rst -gen-diag-docs ../include/clang/Basic/Diagnostic.td "${docs_targets}")
+    gen_rst_file_from_td(DiagnosticsReference.md -gen-diag-docs ../include/clang/Basic/Diagnostic.td "${docs_targets}")
     gen_rst_file_from_td(AMDGPUBuiltinReference.rst -gen-builtin-docs ../include/clang/Basic/BuiltinsAMDGPU.td "${docs_targets}")
     gen_rst_file_from_td(ClangCommandLineReference.rst -gen-opt-docs ../include/clang/Options/ClangOptionDocs.td "${docs_targets}")
 
diff --git a/clang/include/clang/Basic/DiagnosticDocs.td b/clang/include/clang/Basic/DiagnosticDocs.td
index 8c024b5cad740a..0bfa8b973b4536 100644
--- a/clang/include/clang/Basic/DiagnosticDocs.td
+++ b/clang/include/clang/Basic/DiagnosticDocs.td
@@ -7,92 +7,97 @@
 //===---------------------------------------------------------------------===//
 
 def GlobalDocumentation {
-  code Intro =[{..
-  -------------------------------------------------------------------
-  NOTE: This file is automatically generated by running clang-tblgen
-  -gen-diag-docs. Do not edit this file by hand!!
-  -------------------------------------------------------------------
+  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
+```{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>
+```
 
-    <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>
+# Diagnostic flags in Clang
 
-.. FIXME: rST doesn't support formatting this, so we format all <td> elements
-          as monospace font face instead.
-.. |nbsp| unicode:: 0xA0
-   :trim:
+:::{contents}
+:local:
+:::
 
-.. Roles generated by clang-tblgen.
-.. role:: error
-.. role:: warning
-.. role:: remark
-.. role:: diagtext
-.. role:: placeholder(emphasis)
+## Introduction
 
-=========================
-Diagnostic flags in Clang
-=========================
-.. contents::
-   :local:
+This page lists the diagnostic flags currently supported by Clang.
 
-Introduction
-============
+:::{role} warning
+:class: warning
+:::
 
-This page lists the diagnostic flags currently supported by Clang.
+:::{role} diagtext
+:class: diagtext
+:::
+
+:::{role} error
+:class: error
+:::
+
+:::{role} remark
+:class: remark
+:::
+
+% Based on `emphasis` so that adjacent placeholders stay separate elements
+% instead of merging into one `*A**B*` emphasis run.
+:::{role} placeholder(emphasis)
+:::
 
-Diagnostic flags
-================
+## Diagnostic flags
 }];
 }
 
 defvar GCCWriteStringsDocs = [{
 **Note:** enabling this warning in C will change the semantic behavior of the
-program by treating all string literals as having type ``const char *``
-instead of ``char *``. This can cause unexpected behaviors with type-sensitive
-constructs like ``_Generic``.
+program by treating all string literals as having type `const char *`
+instead of `char *`. This can cause unexpected behaviors with type-sensitive
+constructs like `_Generic`.
 }];
 
 defvar NanInfDisabledDocs = [{
-This warning is enabled when source code using the macros ``INFINITY`` or ``NAN``
+This warning is enabled when source code using the macros `INFINITY` or `NAN`
 is compiled with floating-point options preventing these two values. This can
 lead to undefined behavior. Check the order of command line arguments that modify
-this behavior, such as ``-ffast-math``, ``-fhonor-infinities``, and
-``-fhonor-nans`` (etc), as well as ``#pragma`` directives if this diagnostic is
+this behavior, such as `-ffast-math`, `-fhonor-infinities`, and
+`-fhonor-nans` (etc), as well as `#pragma` directives if this diagnostic is
 generated unexpectedly.
 }];
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 3e91d74cd66a96..1da7698944b24b 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -455,13 +455,13 @@ def CXX11WarnSuggestOverride : DiagGroup<"suggest-override">;
 
 def WarnUnnecessaryVirtualSpecifier : DiagGroup<"unnecessary-virtual-specifier"> {
   code Documentation = [{
-Warns when a ``final`` class contains a virtual method (including virtual
-destructors) that does not override anything. Since ``final`` classes cannot
+Warns when a `final` class contains a virtual method (including virtual
+destructors) that does not override anything. Since `final` classes cannot
 be subclassed, their methods cannot be overridden, so there is no point to
-introducing new ``virtual`` methods.
+introducing new `virtual` methods.
 
 The warning also detects virtual methods in classes whose destructor is
-``final``, for the same reason.
+`final`, for the same reason.
   }];
 }
 
@@ -932,23 +932,23 @@ def MSBitfieldCompatibility : DiagGroup<"ms-bitfield-padding"> {
 
     This can occur when mixing different types explicitly:
 
-    .. code-block:: c++
-
-      struct S {
-        uint16_t field1 : 1;
-        uint32_t field2 : 1;
-      };
+    ```c++
+    struct S {
+      uint16_t field1 : 1;
+      uint32_t field2 : 1;
+    };
+    ```
 
     or more subtly through enums
 
-    .. code-block:: c++
-
-      enum Enum1 { /* ... */ };
-      enum class Enum2 : unsigned char { /* ... */ };
-      struct S {
-        Enum1 field1 : 1;
-        Enum2 field2 : 1;
-      };
+    ```c++
+    enum Enum1 { /* ... */ };
+    enum class Enum2 : unsigned char { /* ... */ };
+    struct S {
+      Enum1 field1 : 1;
+      Enum2 field2 : 1;
+    };
+    ```
 
     In each of these cases under the Microsoft ABI the second bit-field
     will not be packed with the preceding bit-field, and instead will be aligned
@@ -965,7 +965,7 @@ def MSBitfieldCompatibility : DiagGroup<"ms-bitfield-padding"> {
     to int sized storage for all specifiers or to resort to declaring the
     bit-fields with explicit integer storage types and cast in and out of the field.
     If such a solution is required the
-    :ref:`preferred_type <langext-preferred_type_documentation>` attribute can be
+    {ref}`preferred_type <langext-preferred_type_documentation>` attribute can be
     used to convey the actual field type to debuggers and other tooling.
   }];
 }
@@ -1049,29 +1049,29 @@ once per copy, etc.
 
 Specifically, this warning fires when it detects an object which:
 
-#. Is defined as ``inline`` in a header file (so it might get compiled into multiple libaries), and
-#. Has external linkage (otherwise it's supposed to be duplicated), and
-#. Has hidden visibility (posix) or lacks a dllimport/dllexport attribute (windows).
+1. Is defined as `inline` in a header file (so it might get compiled into multiple libaries), and
+1. Has external linkage (otherwise it's supposed to be duplicated), and
+1. Has hidden visibility (posix) or lacks a dllimport/dllexport attribute (windows).
 
 As well as one of the following:
 
-#. The object is mutable, or
-#. The object's initializer definitely has side effects.
+1. The object is mutable, or
+1. The object's initializer definitely has side effects.
 
 The warning can be resolved by removing one of the conditions above. In rough
 order of preference, this may be done by:
 
-#. Marking the object ``const`` (if possible)
-#. Moving the object's definition to a source file
-#. Making the object visible using ``__attribute((visibility("default")))``,
-   ``__declspec(dllimport)``, or ``__declspec(dllexport)``.
+1. Marking the object `const` (if possible)
+1. Moving the object's definition to a source file
+1. Making the object visible using `__attribute((visibility("default")))`,
+   `__declspec(dllimport)`, or `__declspec(dllexport)`.
 
-When annotating an object with ``__declspec(dllimport)`` or ``__declspec(dllexport)``,
+When annotating an object with `__declspec(dllimport)` or `__declspec(dllexport)`,
 take care to ensure that the object is only exported from one dll, and is imported
 everywhere else.
 
 Note that for (2), all levels of a pointer variable must be constant;
-``const int*`` will trigger the warning because the pointer itself is mutable.
+`const int*` will trigger the warning because the pointer itself is mutable.
 }];
 }
 
@@ -1802,13 +1802,13 @@ like YAML by adding the `-foptimization-record-file=<file>` command-line flag.
 Results can be filtered by function name by passing
 `-mllvm -filter-print-funcs=foo`, where `foo` is the target function's name.
 
-   .. code-block:: console
-
-      clang -c a.cpp -Rpass-analysis=stack-frame-layout -mllvm -filter-print-funcs=foo
-
-   .. code-block:: console
+   ```console
+   clang -c a.cpp -Rpass-analysis=stack-frame-layout -mllvm -filter-print-funcs=foo
+   ```
 
-      clang -c a.cpp -Rpass-analysis=stack-frame-layout -foptimization-record-file=<file>
+   ```console
+   clang -c a.cpp -Rpass-analysis=stack-frame-layout -foptimization-record-file=<file>
+   ```
 }];
 }
 // Compatibility flag name from old versions of Clang.
@@ -1907,26 +1907,26 @@ def MaxTokens : DiagGroup<"max-tokens"> {
 The warning is issued if the number of pre-processor tokens exceeds
 the token limit, which can be set in three ways:
 
-1. As a limit at a specific point in a file, using the ``clang max_tokens_here``
+1. As a limit at a specific point in a file, using the `clang max_tokens_here`
    pragma:
 
-   .. code-block:: c++
+   ```c++
+   #pragma clang max_tokens_here 1234
+   ```
 
-      #pragma clang max_tokens_here 1234
-
-2. As a per-translation unit limit, using the ``-fmax-tokens=`` command-line
+2. As a per-translation unit limit, using the `-fmax-tokens=` command-line
    flag:
 
-   .. code-block:: console
-
-      clang -c a.cpp -fmax-tokens=1234
-
-3. As a per-translation unit limit using the ``clang max_tokens_total`` pragma,
-   which works like and overrides the ``-fmax-tokens=`` flag:
+   ```console
+   clang -c a.cpp -fmax-tokens=1234
+   ```
 
-   .. code-block:: c++
+3. As a per-translation unit limit using the `clang max_tokens_total` pragma,
+   which works like and overrides the `-fmax-tokens=` flag:
 
-      #pragma clang max_tokens_total 1234
+   ```c++
+   #pragma clang max_tokens_total 1234
+   ```
 
 These limits can be helpful in limiting code growth through included files.
 
diff --git a/clang/test/TableGen/DiagnosticDocs.inc b/clang/test/TableGen/DiagnosticDocs.inc
index 4b114059382fd3..1e5b6b5d5ac70b 100644
--- a/clang/test/TableGen/DiagnosticDocs.inc
+++ b/clang/test/TableGen/DiagnosticDocs.inc
@@ -1,75 +1,10 @@
+// A stub stand-in for clang/include/clang/Basic/DiagnosticDocs.td. The emitter
+// copies Intro to the top of the generated file verbatim, and no test checks
+// its contents, so there is nothing to gain from mirroring the real banner,
+// stylesheet, and role definitions here.
 
 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
-================
+  code Intro =[{
+## Diagnostic flags
 }];
 }
diff --git a/clang/test/TableGen/diag-docs-determinism.td b/clang/test/TableGen/diag-docs-determinism.td
new file mode 100644
index 00000000000000..47fad9069b2397
--- /dev/null
+++ b/clang/test/TableGen/diag-docs-determinism.td
@@ -0,0 +1,33 @@
+// RUN: clang-tblgen -gen-diag-docs -I%S %s -o - 2>&1 | FileCheck %s
+
+include "DiagnosticBase.inc"
+
+def Pedantic : DiagGroup<"pedantic">;
+
+// Extensions are implicitly members of -Wpedantic, and InferPedantic collects
+// that membership for the docs. Collecting it into an unordered set would make
+// the emitted order depend on pointer hashing, and sorting the set back by
+// source location would not repair that: every record produced by a multiclass
+// reports the location of the `def` inside it, so records from different
+// `defm`s tie. Membership is collected into a vector instead, by marching the
+// records in the order the TableGen parser holds them.
+multiclass PedanticExt<string str> {
+  def "" : Extension<str>;
+  def _alt : Extension<str # " (alt)">;
+}
+
+defm ext_ddd : PedanticExt<"ddd is an extension">;
+defm ext_bbb : PedanticExt<"bbb is an extension">;
+defm ext_aaa : PedanticExt<"aaa is an extension">;
+defm ext_ccc : PedanticExt<"ccc is an extension">;
+
+// The order below is neither the `defm` order above nor a hash order: it is the
+// record order, which is stable across runs.
+// CHECK: aaa is an extension
+// CHECK: aaa is an extension (alt)
+// CHECK: bbb is an extension
+// CHECK: bbb is an extension (alt)
+// CHECK: ccc is an extension
+// CHECK: ccc is an extension (alt)
+// CHECK: ddd is an extension
+// CHECK: ddd is an extension (alt)
diff --git a/clang/test/TableGen/emit-diag-docs.td b/clang/test/TableGen/emit-diag-docs.td
index 2dd1a6b57af0ce..b27dbf3e37fe2f 100644
--- a/clang/test/TableGen/emit-diag-docs.td
+++ b/clang/test/TableGen/emit-diag-docs.td
@@ -8,77 +8,131 @@ 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
+// Each heading is preceded by an explicit MyST cross-reference target so that
+// intra-page links do not depend on how Sphinx slugifies heading text.
+// CHECK:      (-WMyGroupName)=
+// CHECK-EMPTY:
+// CHECK-NEXT: ### -WMyGroupName
 // CHECK: **Diagnostic text:**
 
 let Group = MyGroup in {
 
-// CHECK:      |:warning:`warning:` |nbsp| |+--------------------------------------------------------------------+|
-// CHECK-NEXT: |                           ||+------------------------------------------------------------------+||
-// CHECK-NEXT: |                           |||:placeholder:`A` |nbsp| :diagtext:`is not` |nbsp| :placeholder:`B`|||
-// CHECK-NEXT: |                           ||+------------------------------------------------------------------+||
-// CHECK-NEXT: |                           |+--------------------------------------------------------------------+|
-// CHECK-NEXT: |                           ||:diagtext:`this is my diff text`                                    ||
-// CHECK-NEXT: |                           |+--------------------------------------------------------------------+|
-// CHECK-NEXT: +---------------------------+----------------------------------------------------------------------+
+// CHECK:      :::{list-table}
+// CHECK-NEXT: :widths: auto
+// CHECK-EMPTY:
+// CHECK-NEXT: * - {warning}`warning:` 
+// CHECK-NEXT:   - :::{list-table}
+// CHECK-NEXT:     :widths: auto
+// CHECK-EMPTY:
+// CHECK-NEXT:     * - {placeholder}`A` {diagtext}`is not` {placeholder}`B`
+// CHECK-NEXT:     * - {diagtext}`this is my diff text`
+// CHECK-NEXT:     :::
+// 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: +----------------------------------------------------------------------------------------------------------+
+// CHECK:      :::{list-table}
+// CHECK-NEXT: :widths: auto
+// CHECK-EMPTY:
+// CHECK-NEXT: * - {warning}`warning:` {placeholder}`A` {diagtext}`is my modifier test` {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: +---------------------------------------------------------------------------------------------------------------+
+// CHECK:      :::{list-table}
+// CHECK-NEXT: :widths: auto
+// CHECK-EMPTY:
+// CHECK-NEXT: * - {warning}`warning:` {diagtext}`This is the` {placeholder}`A` {diagtext}`test I've written`
+// CHECK-NEXT: :::
 def CheckOrdinal : Warning<"This is the %ordinal0 test I've written">;
 
-// CHECK:      |:warning:`warning:` |nbsp| :diagtext:`I wrote` |nbsp| |+----------------+| |nbsp| :diagtext:`tests`|
-// CHECK-NEXT: |                                                      ||:diagtext:`no`  ||                         |
-// CHECK-NEXT: |                                                      |+----------------+|                         |
-// CHECK-NEXT: |                                                      ||:diagtext:`one` ||                         |
-// CHECK-NEXT: |                                                      |+----------------+|                         |
-// CHECK-NEXT: |                                                      ||:placeholder:`A`||                         |
-// CHECK-NEXT: |                                                      |+----------------+|                         |
-// CHECK-NEXT: +------------------------------------------------------+------------------+-------------------------+
+// CHECK:      :::{list-table}
+// CHECK-NEXT: :widths: auto
+// CHECK-EMPTY:
+// CHECK-NEXT: * - {warning}`warning:` {diagtext}`I wrote` 
+// CHECK-NEXT:   - :::{list-table}
+// CHECK-NEXT:     :widths: auto
+// CHECK-EMPTY:
+// CHECK-NEXT:     * - {diagtext}`no`
+// CHECK-NEXT:     * - {diagtext}`one`
+// CHECK-NEXT:     * - {placeholder}`A`
+// CHECK-NEXT:     :::
+// CHECK-NEXT:   -  {diagtext}`tests`
+// 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: +-----------------------------------------------------------------------+
+// CHECK:      :::{list-table}
+// CHECK-NEXT: :widths: auto
+// CHECK-EMPTY:
+// CHECK-NEXT: * - {warning}`warning:` {diagtext}`bad type` {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: +----------------------------------------------+---------------+---------------------------------+
+// CHECK:      :::{list-table}
+// CHECK-NEXT: :widths: auto
+// CHECK-EMPTY:
+// CHECK-NEXT: * - {warning}`warning:` {diagtext}`My test`
+// CHECK-NEXT:   - :::{list-table}
+// CHECK-NEXT:     :widths: auto
+// CHECK-EMPTY:
+// CHECK-NEXT:     * -
+// CHECK-NEXT:     * - {diagtext}`s`
+// CHECK-NEXT:     :::
+// CHECK-NEXT:   -  {diagtext}`are the best!`
+// 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: +----------------------------------------------------------------------+-----------------+
+// CHECK:      :::{list-table}
+// CHECK-NEXT: :widths: auto
+// CHECK-EMPTY:
+// CHECK-NEXT: * - {warning}`warning:` {diagtext}`this is my select test:` 
+// CHECK-NEXT:   - :::{list-table}
+// CHECK-NEXT:     :widths: auto
+// CHECK-EMPTY:
+// CHECK-NEXT:     * - {diagtext}`one`
+// CHECK-NEXT:     * - {diagtext}`two`
+// CHECK-NEXT:     :::
+// CHECK-NEXT:   -  {diagtext}`and it is` 
+// CHECK-NEXT:   - :::{list-table}
+// CHECK-NEXT:     :widths: auto
+// CHECK-EMPTY:
+// CHECK-NEXT:     * - {diagtext}`good`
+// CHECK-NEXT:     * - {diagtext}`bad`
+// 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: +-------------------------------------------------------+------------------+--------+---------------------+-------------------------------+------------------+--------------------------------------------------------+
+// CHECK:      :::{list-table}
+// CHECK-NEXT: :widths: auto
+// CHECK-EMPTY:
+// CHECK-NEXT: * - {warning}`warning:` {diagtext}`They say` 
+// CHECK-NEXT:   - :::{list-table}
+// CHECK-NEXT:     :widths: auto
+// CHECK-EMPTY:
+// CHECK-NEXT:     * - {diagtext}`good`
+// CHECK-NEXT:     * - {diagtext}`bad`
+// CHECK-NEXT:     :::
+// CHECK-NEXT:   -  
+// CHECK-NEXT:   - :::{list-table}
+// CHECK-NEXT:     :widths: auto
+// CHECK-EMPTY:
+// CHECK-NEXT:     * - {diagtext}`food`
+// CHECK-NEXT:     * - {diagtext}`forests`
+// CHECK-NEXT:     :::
+// CHECK-NEXT:   -  {diagtext}`are` 
+// CHECK-NEXT:   - :::{list-table}
+// CHECK-NEXT:     :widths: auto
+// CHECK-EMPTY:
+// CHECK-NEXT:     * - {diagtext}`good`
+// CHECK-NEXT:     * - {diagtext}`bad`
+// CHECK-NEXT:     :::
+// CHECK-NEXT:   -  {diagtext}`according to` {placeholder}`D`
+// CHECK-NEXT: :::
 def CheckSubstitution : Warning<"They say %sub{MySubNested}3,1,0">;
 
-
-// CHECK:      |:warning:`warning:` |nbsp| :diagtext:`this is my warning text`|
-// CHECK-NEXT: +--------------------------------------------------------------+
+// CHECK:      :::{list-table}
+// CHECK-NEXT: :widths: auto
+// CHECK-EMPTY:
+// CHECK-NEXT: * - {warning}`warning:` {diagtext}`this is my warning text`
+// CHECK-NEXT: :::
 def CheckText : Warning<"this is my warning text">;
 
 }
diff --git a/clang/utils/TableGen/ClangDiagnosticsEmitter.cpp b/clang/utils/TableGen/ClangDiagnosticsEmitter.cpp
index ba10be060c20a3..57909baf9d2f55 100644
--- a/clang/utils/TableGen/ClangDiagnosticsEmitter.cpp
+++ b/clang/utils/TableGen/ClangDiagnosticsEmitter.cpp
@@ -16,6 +16,7 @@
 #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"
@@ -135,12 +136,6 @@ namespace {
   };
 } // end anonymous namespace.
 
-static bool beforeThanCompare(const Record *LHS, const Record *RHS) {
-  assert(!LHS->getLoc().empty() && !RHS->getLoc().empty());
-  return
-    LHS->getLoc().front().getPointer() < RHS->getLoc().front().getPointer();
-}
-
 static bool diagGroupBeforeByName(const Record *LHS, const Record *RHS) {
   return LHS->getValueAsString("GroupName") <
          RHS->getValueAsString("GroupName");
@@ -599,8 +594,7 @@ struct DiagnosticTextBuilder {
     }
   }
 
-  std::vector<std::string> buildForDocumentation(StringRef Role,
-                                                 const Record *R);
+  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>>>>
@@ -767,150 +761,216 @@ template <class Derived> struct DiagTextVisitor {
   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 == '|' ? '+' : '-');
-}
+/// 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::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(Builder.getSubstitution(Sub), NewMappings, Pieces);
-    }
-    if (auto *MD = dyn_cast<MultiPiece>(OrigP)) {
-      for (Piece *Node : MD->Pieces)
-        gatherNodes(Node, CurrentMappings, Pieces);
-      return;
-    }
-    Pieces.push_back(std::make_pair(OrigP, CurrentMappings));
+  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;
   }
 
-  void VisitMulti(MultiPiece *P) {
-    if (P->Pieces.empty()) {
-      RST.push_back("");
-      return;
+  /// 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);
     }
+  }
 
-    if (P->Pieces.size() == 1)
-      return Visit(P->Pieces[0]);
+  /// 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);
+  }
 
-    // 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);
+  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;
+  };
 
-    std::string EmptyLinePrefix;
-    size_t Start = RST.size();
-    bool HasMultipleLines = true;
-    for (const std::pair<Piece *, ModifierMappingsType> &NodePair : Pieces) {
-      std::vector<std::string> Lines;
-      DiagTextDocPrinter Visitor{Builder, Lines};
-      Visitor.ModifierMappings = NodePair.second;
-      Visitor.Visit(NodePair.first);
+  void startTable() {
+    Result += ":::{list-table}";
+    newLine();
+    Result += ":widths: auto";
+    newLine(/*Blank=*/true);
+  }
 
-      if (Lines.empty())
+  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);
+    }
+  }
 
-      // 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;
+  void VisitMulti(MultiPiece *P) {
+    if (P->Pieces.empty())
+      return;
+    if (P->Pieces.size() == 1 && containsTablePiece(P->Pieces.front())) {
+      Visit(P->Pieces.front());
+      return;
     }
-    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);
+    startTable();
+    TableState T{/*OnePiecePerRow=*/false};
+    appendPiecesToCells(P->Pieces, T);
+    if (T.CellOpen)
+      closeCell(T, /*DropIfEmpty=*/true);
+    endTable();
   }
 
   void VisitText(TextPiece *P) {
-    RST.push_back("");
-    auto &S = RST.back();
-
-    StringRef T = P->Text;
-    while (T.consume_front(" "))
-      RST.back() += " |nbsp| ";
-
-    std::string Suffix;
-    while (T.consume_back(" "))
-      Suffix += " |nbsp| ";
-
-    if (!T.empty()) {
-      S += ':';
-      S += P->Role;
-      S += ":`";
-      escapeRST(T, S);
-      S += '`';
-    }
+    StringRef Text = P->Text;
+    while (Text.consume_front(" "))
+      Result += " ";
+
+    unsigned TrailingSpaces = 0;
+    while (Text.consume_back(" "))
+      ++TrailingSpaces;
 
-    S += Suffix;
+    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) {
-    RST.push_back(std::string(":placeholder:`") +
-                  char('A' + mapIndex(P->Index)) + "`");
+    // 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) {
-    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();
+    startTable();
+    TableState T{/*OnePiecePerRow=*/true};
+    for (Piece *Option : P->Options) {
+      openCell(T);
+      appendCellBody(Option);
+      closeCell(T, /*DropIfEmpty=*/false);
     }
-
-    makeTableRows(RST.begin() + SeparatorIndexes.front(),
-                  RST.begin() + SeparatorIndexes.back() + 1);
-    for (size_t I : SeparatorIndexes)
-      makeRowSeparator(RST[I]);
+    endTable();
   }
 
   void VisitEnumSelect(EnumSelectPiece *P) {
@@ -941,7 +1001,10 @@ struct DiagTextDocPrinter : DiagTextVisitor<DiagTextDocPrinter> {
     VisitSelect(&Select);
   }
 
-  std::vector<std::string> &RST;
+  std::string &Result;
+  /// Number of spaces every new line is indented by, one marker per enclosing
+  /// cell.
+  unsigned Indent = 0;
 };
 
 struct DiagEnumPrinter : DiagTextVisitor<DiagEnumPrinter> {
@@ -1280,9 +1343,8 @@ Piece *DiagnosticTextBuilder::DiagText::parseDiagText(StringRef &Text,
   return New<MultiPiece>(Parsed);
 }
 
-std::vector<std::string>
-DiagnosticTextBuilder::buildForDocumentation(StringRef Severity,
-                                             const Record *R) {
+std::string DiagnosticTextBuilder::buildForDocumentation(StringRef Severity,
+                                                         const Record *R) {
   EvaluatingRecordGuard Guard(&EvaluatingRecord, R);
   StringRef Text = R->getValueAsString("Summary");
 
@@ -1296,8 +1358,11 @@ DiagnosticTextBuilder::buildForDocumentation(StringRef Severity,
     D.Root = MP;
   }
   MP->Pieces.insert(MP->Pieces.begin(), Prefix);
-  std::vector<std::string> Result;
+  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;
 }
 
@@ -2260,8 +2325,14 @@ std::set<std::string> getDefaultSeverities(const Record *DiagGroup,
   return States;
 }
 
-void writeHeader(StringRef Str, raw_ostream &OS, char Kind = '-') {
-  OS << Str << "\n" << std::string(Str.size(), Kind) << "\n";
+/// 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,
@@ -2269,16 +2340,25 @@ void writeDiagnosticText(DiagnosticTextBuilder &Builder, const Record *R,
   StringRef Text = R->getValueAsString("Summary");
   if (Text == "%0")
     OS << "The text of this diagnostic is not controlled by Clang.\n\n";
-  else {
-    std::vector<std::string> Out = Builder.buildForDocumentation(Role, R);
-    for (auto &Line : Out)
-      OS << Line << "\n";
-    OS << "\n";
-  }
+  else
+    OS << Builder.buildForDocumentation(Role, R) << '\n';
 }
 
-}  // namespace
-}  // namespace docs
+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;
@@ -2309,18 +2389,16 @@ void clang::EmitClangDiagDocs(const RecordKeeper &Records, raw_ostream &OS) {
 
   // Compute the set of diagnostics that are in -Wpedantic.
   {
-    RecordSet DiagsInPedanticSet;
-    RecordSet GroupsInPedanticSet;
+    // 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(&DiagsInPedanticSet, &GroupsInPedanticSet);
+    inferPedantic.compute(&DiagsInPedantic, &GroupsInPedantic);
     auto &PedDiags = DiagsInGroup["pedantic"];
-    // Put the diagnostics into a deterministic order.
-    RecordVec DiagsInPedantic(DiagsInPedanticSet.begin(),
-                              DiagsInPedanticSet.end());
-    RecordVec GroupsInPedantic(GroupsInPedanticSet.begin(),
-                               GroupsInPedanticSet.end());
-    sort(DiagsInPedantic, beforeThanCompare);
-    sort(GroupsInPedantic, beforeThanCompare);
     PedDiags.DiagsInGroup.insert(PedDiags.DiagsInGroup.end(),
                                  DiagsInPedantic.begin(),
                                  DiagsInPedantic.end());
@@ -2333,13 +2411,13 @@ void clang::EmitClangDiagDocs(const RecordKeeper &Records, raw_ostream &OS) {
   // 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;
+    StringRef GroupName = G->getValueAsString("GroupName");
+    StringRef Prefix = IsRemarkGroup ? "-R" : "-W";
+    auto &GroupInfo = DiagsInGroup[GroupName];
+    bool IsSynonym =
+        GroupInfo.DiagsInGroup.empty() && GroupInfo.SubGroups.size() == 1;
 
-    writeHeader(((IsRemarkGroup ? "-R" : "-W") +
-                    G->getValueAsString("GroupName")).str(),
-                OS);
+    writeHeader(Prefix, GroupName, OS);
 
     if (!IsSynonym) {
       // FIXME: Ideally, all the diagnostics in a group should have the same
@@ -2349,9 +2427,8 @@ void clang::EmitClangDiagDocs(const RecordKeeper &Records, raw_ostream &OS) {
         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";
+          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) {
@@ -2370,8 +2447,10 @@ void clang::EmitClangDiagDocs(const RecordKeeper &Records, raw_ostream &OS) {
 
       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 << "`" << (IsRemarkGroup ? "-R" : "-W") << Name << "`_";
+        OS << LS << "{ref}`" << Prefix << Name << "`";
       OS << ".\n\n";
     }
 
@@ -2389,7 +2468,7 @@ void clang::EmitClangDiagDocs(const RecordKeeper &Records, raw_ostream &OS) {
 
     auto Doc = G->getValueAsString("Documentation");
     if (!Doc.empty())
-      OS << Doc;
+      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";



More information about the cfe-commits mailing list