Author: Vlad Serebrennikov
Date: 2026-03-18T02:07:01Z
New Revision: ae9b5a4bcad8d048f4b0870bfac224bd4d22e1fd

URL: 
https://github.com/llvm/llvm-project/commit/ae9b5a4bcad8d048f4b0870bfac224bd4d22e1fd
DIFF: 
https://github.com/llvm/llvm-project/commit/ae9b5a4bcad8d048f4b0870bfac224bd4d22e1fd.diff

LOG: [clang] Add `-verify-directives` cc1 flag (#179835)

Matheus once told me that the various rules I enforce in C++ DR tests
should be checked automatically. This is the patch to check some of
them.

`-verify-directives` is a cc1 flag that checks how `expected` directives
themselves are written. It enforces the following rules on top of
`-verify` mode:
1. Directives have to fully match diagnostic text (but regular
expressions are still allowed).
2. Lexical order of directives must match the order in which diagnostics
are emitted.
3. Each directive must match exactly one diagnostic.
4. Directives has to specify exact source location of the diagnostic,
i.e. wildcards (`*`) are not allowed.

The second rule (about order) is the most significant: it doesn't allow
to e.g. have `expected-note {{declared here}}` somewhere far away from
`expected-error` it attaches to. It also enforces order between notes
themselves.

(This patch comes with rather extensive documentation in the internals
manual. Check it out for more details.)

See #179813 and #179674 for impact of enforcing this mode in C++ DR
tests. Despite my best efforts, this flag uncovered a significant number
of deficiencies that I missed.

I've been already enforcing those rules in C++ DR tests, so I'm going to
roll this out there when #179813 is merged. I did my best to make UX
palatable, so that it can be used outside of C++ DR tests. My hope is
that once this is merged, reviewers can go "make this test work with
`-verify-directives`" instead of painstakingly reconstructing compiler
output from directives scattered all across the test file.

---------

Co-authored-by: Aaron Ballman <[email protected]>
Co-authored-by: Erich Keane <[email protected]>

Added: 
    clang/test/Frontend/verify-directives-full-match.cpp
    clang/test/Frontend/verify-directives-one-diag.cpp
    clang/test/Frontend/verify-directives-order.cpp
    clang/test/Frontend/verify-directives-wildcard.cpp

Modified: 
    clang/docs/InternalsManual.rst
    clang/include/clang/Basic/DiagnosticFrontendKinds.td
    clang/include/clang/Basic/DiagnosticOptions.def
    clang/include/clang/Frontend/TextDiagnosticBuffer.h
    clang/include/clang/Frontend/VerifyDiagnosticConsumer.h
    clang/include/clang/Options/Options.td
    clang/lib/Frontend/CompilerInvocation.cpp
    clang/lib/Frontend/VerifyDiagnosticConsumer.cpp

Removed: 
    


################################################################################
diff  --git a/clang/docs/InternalsManual.rst b/clang/docs/InternalsManual.rst
index 42004bcac56b2..0694bf02b4996 100644
--- a/clang/docs/InternalsManual.rst
+++ b/clang/docs/InternalsManual.rst
@@ -3451,22 +3451,39 @@ are similar.
 Testing
 -------
 All functional changes to Clang should come with test coverage demonstrating
-the change in behavior.
+the change in behavior. There are four kinds of tests:
+
+* Unit tests: such tests are placed in ``clang/test/unittest``.
+* Diagnostic tests: such tests ensures that only specific diagnostics are
+  emitted at specific source lines. Those tests are using ``-verify`` mode of
+  ``-cc1``, which is described below in
+  :ref:`"Verifying Diagnostics" <verifying-diagnostics>`. Additional
+  provisions for tests for C++ defect reports are described in
+  :ref:`"C++ Defect Report Tests" <cxx-defect-report-tests>` section.
+* AST dump tests: such tests pass printable AST output to the
+  `FileCheck <https://llvm.org/docs/CommandGuide/FileCheck.html>`_ utility,
+  which check presence of certain patterns (or lack of thereof).
+* LLVM IR tests: in such tests, the LLVM IR output of Clang is checked, which
+  is needed in cases when checking diagnostics is not sufficient (e.g. when
+  testing exception handling or object lifetime). Such tests pass LLVM IR
+  output to the
+  `FileCheck <https://llvm.org/docs/CommandGuide/FileCheck.html>`_ utility,
+  which check the presence of certain IR patterns (or lack of thereof).
 
 .. _verifying-diagnostics:
 
 Verifying Diagnostics
 ^^^^^^^^^^^^^^^^^^^^^
 Clang ``-cc1`` supports the ``-verify`` command line option as a way to
-validate diagnostic behavior. This option will use special comments within the
-test file to verify that expected diagnostics appear in the correct source
-locations. If all of the expected diagnostics match the actual output of Clang,
-then the invocation will return normally. If there are discrepancies between
-the expected and actual output, Clang will emit detailed information about
-which expected diagnostics were not seen or which unexpected diagnostics were
-seen, etc. A complete example is:
+validate diagnostic behavior. This option will use special comments, called
+directives, within the test file to verify that expected diagnostics appear in
+the correct source locations. If all of the expected diagnostics match the
+actual output of Clang, then the invocation will return normally. If there are
+discrepancies between the expected and actual output, Clang will emit detailed
+information about which expected diagnostics were not seen or which unexpected
+diagnostics were seen, etc. A complete example is:
 
-.. code-block: c++
+.. code-block:: c++
 
   // RUN: %clang_cc1 -verify %s
   int A = B; // expected-error {{use of undeclared identifier 'B'}}
@@ -3476,6 +3493,97 @@ diagnostic verifier will pass. However, if the expected 
error does not appear
 or appears in a 
diff erent location than expected, or if additional diagnostics
 appear, the diagnostic verifier will fail and emit information as to why.
 
+Directive Syntax
+~~~~~~~~~~~~~~~~
+Syntax description of the directives is the following:
+
+.. parsed-literal::
+  
+  `directive`
+      `prefix` ``-`` `diagnostic-kind` `regex-match`:sub:`opt` 
`diagnostic-loc`:sub:`opt` `quantifier`:sub:`opt` ``{{`` 
`delimiter-open`:sub:`opt` `diagnostic-text` `delimiter-close`:sub:`opt` ``}}``
+
+  `diagnostic-kind`
+      ``error``
+      ``warning``
+      ``note``
+      ``remark``
+
+  `regex-match`
+      ``-re``
+
+  `diagnostic-loc`
+      ``@+`` `number`
+      ``@-`` `number`
+      ``@`` `line`
+      ``@`` `file-path` ``:`` `line`
+      ``@*``
+      ``@*:*``
+      ``@#`` `marker-name`
+
+  `line`
+      ``*``
+      `number`
+  
+  `quantifier`
+      ``+``
+      `number` ``+``
+      `number` ``-`` `number`
+
+  `delimiter-open`
+      ``{`` `delimiter-open`:sub:`opt`
+
+  `delimiter-close`
+      ``}`` `delimiter-close`:sub:`opt`
+
+  `number`
+      `digit` `number`:sub:`opt`
+
+  `digit`
+      ``0`` ``1`` ``2`` ``3`` ``4`` ``5`` ``6`` ``7`` ``8`` ``9``
+
+Where:
+
+- ``prefix`` is "expected" or a custom string
+  (:ref:`Custom Prefixes <custom-prefixes>`).
+- ``delimiter-open`` and ``delimiter-close`` have to have the same length
+  (:ref:`Diagnostic Text <diagnostic-text>`).
+- ``file-path`` is relative or absolte path to a file
+  (:ref:`Diagnostic Location <diagnostic-location>`).
+- ``marker-name`` is name of the marker placed somewhere in the source file
+  (:ref:`Diagnostic Location <diagnostic-location>`).
+- ``diagnostic-text`` is text of the expected diagnostic
+  (:ref:`Diagnostic Text <diagnostic-text>`).
+
+Examples:
+
+- ``// expected-note {{declared here}}``
+  One note ``declared here`` has to be issued on the same line as the
+  directive.
+- ``// expected-note {{{declared here}}}``
+  Same as above, but uses additional delimiters for diagnostic text.
+- ``// expected-error@+1 0-1 {{expected identifier or '{'}}``
+  Zero or one error ``expected identifier or '{'`` has to be issued on the next
+  line after the directive zero or one time.
+- ``// cxx98-17-warning@#func-decl + {{target exception specification is not 
superset of source}}``
+  One or more warnings
+  ``target exception specification is not superset of source`` has to be issued
+  on the line that has ``// #func-decl`` comment one or more times when CLI
+  option ``-verify=cxx98-17`` is passed.
+- ``// expected-note@* 1 {{file entered}}``
+  One note ``file entered`` has to be issued somewhere in the source file.
+- ``// [email protected]:2 {{previous declaration is here}}``
+  One note ``previous declaration is here`` has to be issued at ``decl.h``
+  line 2.
+- ``// cxx11-17-error-re@-1 {{no matching constructor for initialization of 
'A' (aka '(lambda at {{.+}})')}}``
+  One error
+  ``no matching constructor for initialization of 'A' (aka '(lambda at <source 
location>)')``
+  (ignoring the exact source location) has to be issued on the previous line
+  before the directive when CLI option ``-verify=cxx11-17`` is passed.
+
+.. _custom-prefixes:
+
+Custom Prefixes
+~~~~~~~~~~~~~~~
 The ``-verify`` command optionally accepts a comma-delimited list of one or
 more verification prefixes that can be used to craft those special comments.
 Each prefix must start with a letter and contain only alphanumeric characters,
@@ -3502,8 +3610,10 @@ Multiple occurrences accumulate prefixes.  For example,
 ``-verify -verify=foo,bar -verify=baz`` is equivalent to
 ``-verify=expected,foo,bar,baz``.
 
-Specifying Diagnostics
-^^^^^^^^^^^^^^^^^^^^^^
+.. _diagnostic-location:
+
+Dianogstic Location
+^^^^^^^^^^^^^^^^^^^
 Indicating that a line expects an error or a warning is easy. Put a comment
 on the line that has the diagnostic, use
 ``expected-{error,warning,remark,note}`` to tag if it's an expected error,
@@ -3514,7 +3624,7 @@ should be included in test cases unless there is a 
compelling reason to use
 truncated text instead.)
 
 For a full description of the matching behavior, including more complex
-matching scenarios, see :ref:`matching <DiagnosticMatching>` below.
+matching scenarios, see :ref:`"Diagnostic text" <diagnostic-text>` below.
 
 Here's an example of the most commonly used way to specify expected
 diagnostics:
@@ -3565,6 +3675,8 @@ appending the marker to the diagnostic with 
``@#<marker>``, as with:
 
 The name of a marker used in a directive must be unique within the compilation.
 
+Quantifiers
+~~~~~~~~~~~
 The simple syntax above allows each specification to match exactly one
 diagnostic. You can use the extended syntax to customize this. The extended
 syntax is ``expected-<type> <n> {{diag text}}``, where ``<type>`` is one of
@@ -3601,11 +3713,10 @@ A range can also be specified by ``<n>-<m>``. For 
example:
 
 In this example, the diagnostic may appear only once, if at all.
 
-.. _DiagnosticMatching:
-
-Matching Modes
-~~~~~~~~~~~~~~
+.. _diagnostic-text:
 
+Diagnostic Text
+~~~~~~~~~~~~~~~
 The default matching mode is simple string, which looks for the expected text
 that appears between the first `{{` and `}}` pair of the comment. The string is
 interpreted just as-is, with one exception: the sequence `\n` is converted to a
@@ -3647,6 +3758,155 @@ Examples matching error: "variable has incomplete type 
'struct s'"
   // expected-error-re {{variable has type 'struct {{(.*)}}'}}
   // expected-error-re {{variable has type 'struct{{[[:space:]](.*)}}'}}
 
+Verifying the Directives
+~~~~~~~~~~~~~~~~~~~~~~~~
+Clang ``-cc1`` also has ``-verify-directives`` mode, which can be enabled
+alongside ``-verify`` to place additional restrictions on directives,
+and strives to improve readability of the expected compiler output
+for reviewers and future readers, but makes it a bit harder to write the test:
+
+- Diagnostic text specified in a directive has to be a full match.
+- Lexical order of directives has to match the order in which diagnostics are
+  emitted.
+- Each directive has to match exactly one diagnostic.
+- Wildcards (``*``) are not allowed in the diagnostic location.
+
+Most of the tests can be adapted to use this mode, but for some tests
+it's not possible. Typical problems and their solutions are listed below.
+
+- **Diagnostic is issued only sometimes.**
+  The cause of variance needs to be identified and captured. Typically it is
+  either a compiler option like language mode, in which case an additional
+  custom prefix should be sufficient, or platform, in which case a triple has
+  to be explicitly passed in command-line arguments.
+- **Template instantiations at the end of translation unit.**
+  Instantiations at the end of the TU cause associated diagnostics to appear
+  too late. Instantiations need to happen before the next directive, and when
+  it is possible, it still takes some creativity to achieve, especially in
+  C++98 mode. Typical solutions include explicit template instantiations,
+  manifestly constant-evaluated expressions, ``enable_if``, and removing
+  template parameters altogether.
+- **Analysis-based warnings.** Some warnings, like ``-Wunused``, are based
+  on CFG analysis and are emitted later than e.g. parser would emit them,
+  because some lookahead is required to collect all the necessary information
+  for such analysis. Not much can be done in this case.
+- **Lambdas.** When lambda is mentioned in diagnostic message, it is identified
+  by its source location, which is typically not salient for the test,
+  and would make it overspecified. In this case regex match can be used to skip
+  over source location.
+
+.. _cxx-defect-report-tests:
+
+C++ Defect Report Tests
+^^^^^^^^^^^^^^^^^^^^^^^
+C++ Defect Report tests are placed in ``clang/test/CXX/drs`` directory, which
+consists of two types of files:
+
+- files where tests are grouped by 100 by issue number, e.g. ``cwg15xx.cpp``
+  ("big files");
+- files with individual tests or smaller groups of tests, e.g. ``cwg177x.cpp``
+  or ``cwg787.cpp``.
+
+C++ defect report tests are run in all language modes available via ``-std=``,
+except for modes with GNU extensions and ``c++03``, as it is synonymous to
+``c++98``. Exceptions, pedantic errors, and ``-verify-directives`` are enabled.
+
+Big files are where most of the tests end up being placed to save on process
+startup cost during test runs. Big files also serve as an index of tests:
+even if test is placed in its individual file, it's still mentioned in the
+big file where this test would be placed otherwise. Big files need all tests
+to work with the same set of compiler options specified in RUN lines, but
+sometimes tests need special provisions, in which case they should be placed
+in their own file. Typical reasons for that include:
+
+- **Test is checking AST dump or LLVM IR.** We don't yet know how
+  to concatenate FileCheck directives from multiple tests into a single file
+  without avoiding test interference. 
+- **Test is not compatible with ``-verify-directives``.** For instance, some
+  tests can't be rewritten to prevent instantiations at the end of TU.
+- **Test needs C++20 modules.** Such tests require special RUN lines to compile
+  modules in the right order.
+- **Test prevents compiler from checking subsequent tests.** In some cases
+  that involve templates Clang refuses to recover from expected errors and will
+  skip over expected errors in subsequent tests.
+
+C++ defect report tests make heavy use of markers. Marker names need to
+be prefixed with ``cwgNNN-``, where NNN is number of the core issue being
+tested. This is especially needed in big files, because all markers share
+the same namespace.
+
+Some diagnostics are expected only in certain language modes. This in handled
+in two parts. First, C++ defect report tests share a common pool of custom
+prefixes:
+
+- ``expected`` for diagnostics issued in all language modes;
+- ``cxxNN`` (e.g. ``cxx98``) for diagnostics issued only in one language mode;
+- ``since-cxxNN`` (e.g. ``since-cxx11``) for diagnostics that are issued
+  in a certain language mode and all newer language modes;
+- ``cxxNN-MM`` (e.g. ``cxx98-14``) for diagnostics that appear in all language
+  modes within a certain range.
+
+Second, parts of tests that test features not universally available in all
+language modes are guarded with ``#if __cplusplus``. Prefixes of directives
+in such parts need to reflect the latest ``#if __cplusplus`` guard to make
+tests require less context to understand, even though this is not strictly
+necessary to make the test work:
+
+.. code-block:: c++
+
+  #if __cplusplus >= 201103L
+  enum : int { a };
+  enum class { b };
+  // since-cxx11-error@-1 {{scoped enumeration requires a name}}
+  #endif
+
+On top of `-verify-directives`, C++ defect report tests use the following
+conventions to make it easier to reconstruct compiler output while looking at
+directives:
+
+- Errors and warnings are placed on the next line after the line they are
+  expecting a diagnostic at, and at the same indentation.
+
+  .. code-block:: c++
+
+    namespace X::Y {}
+    // cxx98-14-error@-1 {{nested namespace definition is a C++17 extension; 
define each namespace separately}}
+    namespace X {
+      namespace X::Y {}
+      // cxx98-14-error@-1 {{nested namespace definition is a C++17 extension; 
define each namespace separately}}
+    }
+
+- Directives that are placed right after the line they are expecting
+  a diagnostic at use relative offsets (``@-1``, ``@-2``, and so on),
+  as long as it's obvious that all relative offsets point to the same line.
+  If there is a feeling readers would start counting lines to make sure
+  they know where the diagnostic is expected, markers should be used instead.
+
+  .. code-block:: c++
+
+    bool b = (void(*)(S, S))operator- < (void(*)(S, S))operator-;
+    // cxx98-17-warning@-1 {{ordered comparison of function pointers ('void 
(*)(S, S)' and 'void (*)(S, S)')}}
+    // cxx20-23-error@-2 {{expected '>'}}
+    //   cxx20-23-note@-3 {{to match this '<'}}
+
+    int *q = new int[T()]; // #cwg299-q
+    // cxx98-11-error@#cwg299-q {{ambiguous conversion of array size 
expression of type 'T' to an integral or enumeration type}}
+    //   cxx98-11-note@#cwg299-int {{conversion to integral type 'int' 
declared here}}
+    //   cxx98-11-note@#cwg299-ushort {{conversion to integral type 'unsigned 
short' declared here}}
+    // since-cxx14-error-re@#cwg299-q {{conversion from 'T' to '__size_t' (aka 
'unsigned {{long long|long|int}}') is ambiguous}}
+    //   since-cxx14-note@#cwg299-int {{candidate function}}
+    //   since-cxx14-note@#cwg299-ushort {{candidate function}}
+
+- Notes and remarks are indented by two spaces relative to the error or warning
+  they are attached to.
+
+  .. code-block:: c++
+
+    void (*p)() throw(int) = &f; // #cwg92-p
+    // since-cxx17-error@#cwg92-p {{ISO C++17 does not allow dynamic exception 
specifications}}
+    //   since-cxx17-note@#cwg92-p {{use 'noexcept(false)' instead}}
+    // cxx98-14-error@#cwg92-p {{target exception specification is not 
superset of source}}
+
 Feature Test Macros
 ===================
 Clang implements several ways to test whether a feature is supported or not.

diff  --git a/clang/include/clang/Basic/DiagnosticFrontendKinds.td 
b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
index 00db1e7ee5afa..b86caeb7714e9 100644
--- a/clang/include/clang/Basic/DiagnosticFrontendKinds.td
+++ b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
@@ -187,6 +187,16 @@ def err_verify_no_directives : Error<
     "no expected directives found: consider use of '%0-no-diagnostics'">;
 def err_verify_nonconst_addrspace : Error<
   "qualifier 'const' is needed for variables in address space '%0'">;
+def err_verify_message_partial_match : Error<
+  "diagnostic messages of '%0' severity not fully matched: %1">;
+def err_verify_directive_out_of_order : Error<
+  "all diagnostics were successfully matched, but out-of-order directives "
+  "were found: %0">;
+def err_verify_non_singular_match : Error<
+  "diagnostic verification mode disallows use of a diagnostic quantifier">;
+def err_verify_wildcard_loc : Error<
+  "diagnostic verification mode disallows use of a wildcard for diagnostic "
+  "location">;
 
 def note_fixit_applied : Note<"FIX-IT applied suggested code changes">;
 def note_fixit_in_macro : Note<

diff  --git a/clang/include/clang/Basic/DiagnosticOptions.def 
b/clang/include/clang/Basic/DiagnosticOptions.def
index 6d0c1b14acc12..17d518c2b7fdd 100644
--- a/clang/include/clang/Basic/DiagnosticOptions.def
+++ b/clang/include/clang/Basic/DiagnosticOptions.def
@@ -76,6 +76,8 @@ ENUM_DIAGOPT(VerifyIgnoreUnexpected, DiagnosticLevelMask, 4,
              DiagnosticLevelMask::None) /// Ignore unexpected diagnostics of
                                         /// the specified levels when using
                                         /// -verify.
+DIAGOPT(VerifyDirectives, 1, 0) /// Enable checks of 'expected' directives
+                                /// themselves.
 DIAGOPT(ElideType, 1, 0)         /// Elide identical types in template 
diff ing
 DIAGOPT(ShowTemplateTree, 1, 0)  /// Print a template tree when 
diff ing
 

diff  --git a/clang/include/clang/Frontend/TextDiagnosticBuffer.h 
b/clang/include/clang/Frontend/TextDiagnosticBuffer.h
index 5945caf89743a..3318d01c0aa6a 100644
--- a/clang/include/clang/Frontend/TextDiagnosticBuffer.h
+++ b/clang/include/clang/Frontend/TextDiagnosticBuffer.h
@@ -28,6 +28,8 @@ class TextDiagnosticBuffer : public DiagnosticConsumer {
   using iterator = DiagList::iterator;
   using const_iterator = DiagList::const_iterator;
 
+  using AllDiagList = std::vector<std::pair<DiagnosticsEngine::Level, size_t>>;
+
 private:
   DiagList Errors, Warnings, Remarks, Notes;
 
@@ -35,7 +37,7 @@ class TextDiagnosticBuffer : public DiagnosticConsumer {
   /// order likely doesn't correspond to user input order, but it at least
   /// keeps notes in the right places.  Each pair in the vector is a diagnostic
   /// level and an index into the corresponding DiagList above.
-  std::vector<std::pair<DiagnosticsEngine::Level, size_t>> All;
+  AllDiagList All;
 
 public:
   const_iterator err_begin() const { return Errors.begin(); }
@@ -50,6 +52,9 @@ class TextDiagnosticBuffer : public DiagnosticConsumer {
   const_iterator note_begin() const { return Notes.begin(); }
   const_iterator note_end() const { return Notes.end(); }
 
+  AllDiagList::const_iterator all_begin() const { return All.begin(); }
+  AllDiagList::const_iterator all_end() const { return All.end(); }
+
   void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
                         const Diagnostic &Info) override;
 

diff  --git a/clang/include/clang/Frontend/VerifyDiagnosticConsumer.h 
b/clang/include/clang/Frontend/VerifyDiagnosticConsumer.h
index b4c613712ed9b..dca93f5ed4c01 100644
--- a/clang/include/clang/Frontend/VerifyDiagnosticConsumer.h
+++ b/clang/include/clang/Frontend/VerifyDiagnosticConsumer.h
@@ -30,6 +30,13 @@ class LangOptions;
 class SourceManager;
 class TextDiagnosticBuffer;
 
+enum class DiagnosticMatchResult {
+  None,
+  OnlyPartial,    /// Match, but not a full match.
+  AtLeastPartial, /// Match, but we didn't check for full match.
+  Full,
+};
+
 /// VerifyDiagnosticConsumer - Create a diagnostic client which will use
 /// markers in the input source to check that all the emitted diagnostics match
 /// those expected. See clang/docs/InternalsManual.rst for details about how to
@@ -46,7 +53,7 @@ class VerifyDiagnosticConsumer: public DiagnosticConsumer,
     create(bool RegexKind, SourceLocation DirectiveLoc,
            SourceLocation DiagnosticLoc, StringRef Spelling,
            bool MatchAnyFileAndLine, bool MatchAnyLine, StringRef Text,
-           unsigned Min, unsigned Max);
+           unsigned Min, unsigned Max, bool FullMatchRequired);
 
   public:
     /// Constant representing n or more matches.
@@ -59,6 +66,7 @@ class VerifyDiagnosticConsumer: public DiagnosticConsumer,
     unsigned Min, Max;
     bool MatchAnyLine;
     bool MatchAnyFileAndLine; // `MatchAnyFileAndLine` implies `MatchAnyLine`.
+    bool FullMatchRequired;
 
     Directive(const Directive &) = delete;
     Directive &operator=(const Directive &) = delete;
@@ -69,16 +77,18 @@ class VerifyDiagnosticConsumer: public DiagnosticConsumer,
     virtual bool isValid(std::string &Error) = 0;
 
     // Returns true on match.
-    virtual bool match(StringRef S) = 0;
+    virtual DiagnosticMatchResult match(StringRef S) const = 0;
 
   protected:
     Directive(SourceLocation DirectiveLoc, SourceLocation DiagnosticLoc,
               StringRef Spelling, bool MatchAnyFileAndLine, bool MatchAnyLine,
-              StringRef Text, unsigned Min, unsigned Max)
+              StringRef Text, unsigned Min, unsigned Max,
+              bool FullMatchRequired)
         : DirectiveLoc(DirectiveLoc), DiagnosticLoc(DiagnosticLoc),
           Spelling(Spelling), Text(Text), Min(Min), Max(Max),
           MatchAnyLine(MatchAnyLine || MatchAnyFileAndLine),
-          MatchAnyFileAndLine(MatchAnyFileAndLine) {
+          MatchAnyFileAndLine(MatchAnyFileAndLine),
+          FullMatchRequired(FullMatchRequired) {
       assert(!DirectiveLoc.isInvalid() && "DirectiveLoc is invalid!");
       assert((!DiagnosticLoc.isInvalid() || MatchAnyLine) &&
              "DiagnosticLoc is invalid!");
@@ -112,6 +122,8 @@ class VerifyDiagnosticConsumer: public DiagnosticConsumer,
   struct ParsingState {
     DirectiveStatus Status;
     std::string FirstNoDiagnosticsDirective;
+    bool AllDirectivesMatchExactlyOneDiag = true;
+    bool WildcardsAreErroneouslyPresent = false;
   };
 
   class MarkerTracker;
@@ -128,6 +140,9 @@ class VerifyDiagnosticConsumer: public DiagnosticConsumer,
   unsigned ActiveSourceFiles = 0;
   ParsingState State;
   ExpectedData ED;
+  bool CheckOrderOfDirectives;
+  bool OneDiagPerDirective;
+  bool DisableWildcardInDiagLoc;
 
   void CheckDiagnostics();
 

diff  --git a/clang/include/clang/Options/Options.td 
b/clang/include/clang/Options/Options.td
index 6fc52384a6d1d..f4cecef82f805 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -8513,6 +8513,9 @@ def verify_ignore_unexpected : Flag<["-"], 
"verify-ignore-unexpected">,
   HelpText<"Ignore unexpected diagnostic messages">;
 def verify_ignore_unexpected_EQ : CommaJoined<["-"], 
"verify-ignore-unexpected=">,
   HelpText<"Ignore unexpected diagnostic messages">;
+def verify_directives
+    : Flag<["-"], "verify-directives">,
+      HelpText<"Enable additional checks on 'expected' directives themselves">;
 def Wno_rewrite_macros : Flag<["-"], "Wno-rewrite-macros">,
   HelpText<"Silence ObjC rewriting warnings">,
   MarshallingInfoFlag<DiagnosticOpts<"NoRewriteMacros">>;

diff  --git a/clang/lib/Frontend/CompilerInvocation.cpp 
b/clang/lib/Frontend/CompilerInvocation.cpp
index 13472765a4f58..748c36efefaed 100644
--- a/clang/lib/Frontend/CompilerInvocation.cpp
+++ b/clang/lib/Frontend/CompilerInvocation.cpp
@@ -2557,6 +2557,10 @@ void CompilerInvocationBase::GenerateDiagnosticArgs(
     if (Prefix != "expected")
       GenerateArg(Consumer, OPT_verify_EQ, Prefix);
 
+  if (Opts.VerifyDirectives) {
+    GenerateArg(Consumer, OPT_verify_directives);
+  }
+
   DiagnosticLevelMask VIU = Opts.getVerifyIgnoreUnexpected();
   if (VIU == DiagnosticLevelMask::None) {
     // This is the default, don't generate anything.
@@ -2655,6 +2659,7 @@ bool clang::ParseDiagnosticArgs(DiagnosticOptions &Opts, 
ArgList &Args,
   Opts.ShowColors = parseShowColorsArgs(Args, DefaultDiagColor);
 
   Opts.VerifyDiagnostics = Args.hasArg(OPT_verify) || 
Args.hasArg(OPT_verify_EQ);
+  Opts.VerifyDirectives = Args.hasArg(OPT_verify_directives);
   Opts.VerifyPrefixes = Args.getAllArgValues(OPT_verify_EQ);
   if (Args.hasArg(OPT_verify))
     Opts.VerifyPrefixes.push_back("expected");

diff  --git a/clang/lib/Frontend/VerifyDiagnosticConsumer.cpp 
b/clang/lib/Frontend/VerifyDiagnosticConsumer.cpp
index 0eea56d1e7e2c..a03607bb60583 100644
--- a/clang/lib/Frontend/VerifyDiagnosticConsumer.cpp
+++ b/clang/lib/Frontend/VerifyDiagnosticConsumer.cpp
@@ -90,16 +90,25 @@ class StandardDirective : public Directive {
   StandardDirective(SourceLocation DirectiveLoc, SourceLocation DiagnosticLoc,
                     StringRef Spelling, bool MatchAnyFileAndLine,
                     bool MatchAnyLine, StringRef Text, unsigned Min,
-                    unsigned Max)
+                    unsigned Max, bool FullMatchRequired)
       : Directive(DirectiveLoc, DiagnosticLoc, Spelling, MatchAnyFileAndLine,
-                  MatchAnyLine, Text, Min, Max) {}
+                  MatchAnyLine, Text, Min, Max, FullMatchRequired) {}
 
   bool isValid(std::string &Error) override {
     // all strings are considered valid; even empty ones
     return true;
   }
 
-  bool match(StringRef S) override { return S.contains(Text); }
+  DiagnosticMatchResult match(StringRef S) const override {
+    if (!S.contains(Text)) {
+      return DiagnosticMatchResult::None;
+    }
+    if (!FullMatchRequired) {
+      return DiagnosticMatchResult::AtLeastPartial;
+    }
+    return S.trim() == Text ? DiagnosticMatchResult::Full
+                            : DiagnosticMatchResult::OnlyPartial;
+  }
 };
 
 /// RegexDirective - Directive with regular-expression matching.
@@ -108,17 +117,30 @@ class RegexDirective : public Directive {
   RegexDirective(SourceLocation DirectiveLoc, SourceLocation DiagnosticLoc,
                  StringRef Spelling, bool MatchAnyFileAndLine,
                  bool MatchAnyLine, StringRef Text, unsigned Min, unsigned Max,
-                 StringRef RegexStr)
+                 StringRef RegexStr, bool FullMatchRequired)
       : Directive(DirectiveLoc, DiagnosticLoc, Spelling, MatchAnyFileAndLine,
-                  MatchAnyLine, Text, Min, Max),
+                  MatchAnyLine, Text, Min, Max, FullMatchRequired),
         Regex(RegexStr) {}
 
   bool isValid(std::string &Error) override {
     return Regex.isValid(Error);
   }
 
-  bool match(StringRef S) override {
-    return Regex.match(S);
+  DiagnosticMatchResult match(StringRef S) const override {
+    if (!FullMatchRequired) {
+      return Regex.match(S) ? DiagnosticMatchResult::AtLeastPartial
+                            : DiagnosticMatchResult::None;
+    }
+
+    llvm::SmallVector<StringRef, 4> Matches;
+    llvm::StringRef TrimmedText = S.trim();
+    Regex.match(TrimmedText, &Matches);
+    if (Matches.empty()) {
+      return DiagnosticMatchResult::None;
+    }
+    return Matches[0].size() == TrimmedText.size()
+               ? DiagnosticMatchResult::Full
+               : DiagnosticMatchResult::OnlyPartial;
   }
 
 private:
@@ -300,9 +322,10 @@ void attachDirective(DiagnosticsEngine &Diags, const 
UnattachedDirective &UD,
                      bool MatchAnyFileAndLine = false,
                      bool MatchAnyLine = false) {
   // Construct new directive.
-  std::unique_ptr<Directive> D = Directive::create(
-      UD.RegexKind, UD.DirectivePos, ExpectedLoc, UD.Spelling,
-      MatchAnyFileAndLine, MatchAnyLine, UD.Text, UD.Min, UD.Max);
+  std::unique_ptr<Directive> D =
+      Directive::create(UD.RegexKind, UD.DirectivePos, ExpectedLoc, 
UD.Spelling,
+                        MatchAnyFileAndLine, MatchAnyLine, UD.Text, UD.Min,
+                        UD.Max, Diags.getDiagnosticOptions().VerifyDirectives);
 
   std::string Error;
   if (!D->isValid(Error)) {
@@ -411,7 +434,9 @@ static std::string DetailedErrorString(const 
DiagnosticsEngine &Diags) {
 static bool ParseDirective(StringRef S, ExpectedData *ED, SourceManager &SM,
                            Preprocessor *PP, SourceLocation Pos,
                            VerifyDiagnosticConsumer::ParsingState &State,
-                           VerifyDiagnosticConsumer::MarkerTracker &Markers) {
+                           VerifyDiagnosticConsumer::MarkerTracker &Markers,
+                           bool OneDiagPerDirective,
+                           bool DisableWildcardInDiagLoc) {
   DiagnosticsEngine &Diags = PP ? PP->getDiagnostics() : SM.getDiagnostics();
 
   // First, scan the comment looking for markers.
@@ -551,6 +576,11 @@ static bool ParseDirective(StringRef S, ExpectedData *ED, 
SourceManager &SM,
         PH.Advance();
 
         if (Filename == "*") {
+          if (DisableWildcardInDiagLoc) {
+            Diags.Report(Pos.getLocWithOffset(PH.C - PH.Begin),
+                         diag::err_verify_wildcard_loc);
+            State.WildcardsAreErroneouslyPresent = true;
+          }
           MatchAnyFileAndLine = true;
           if (!PH.Next("*")) {
             Diags.Report(Pos.getLocWithOffset(PH.C - PH.Begin),
@@ -586,11 +616,21 @@ static bool ParseDirective(StringRef S, ExpectedData *ED, 
SourceManager &SM,
           if (PH.Next(Line) && Line > 0)
             ExpectedLoc = SM.translateLineCol(FID, Line, 1);
           else if (PH.Next("*")) {
+            if (DisableWildcardInDiagLoc) {
+              Diags.Report(Pos.getLocWithOffset(PH.C - PH.Begin),
+                           diag::err_verify_wildcard_loc);
+              State.WildcardsAreErroneouslyPresent = true;
+            }
             MatchAnyLine = true;
             ExpectedLoc = SM.translateLineCol(FID, 1, 1);
           }
         }
       } else if (PH.Next("*")) {
+        if (DisableWildcardInDiagLoc) {
+          Diags.Report(Pos.getLocWithOffset(PH.C - PH.Begin),
+                       diag::err_verify_wildcard_loc);
+          State.WildcardsAreErroneouslyPresent = true;
+        }
         MatchAnyLine = true;
         ExpectedLoc = SourceLocation();
       }
@@ -606,14 +646,22 @@ static bool ParseDirective(StringRef S, ExpectedData *ED, 
SourceManager &SM,
     // Skip optional whitespace.
     PH.SkipWhitespace();
 
+    std::optional<std::ptr
diff _t> NonSingularMatchDiagOffset;
+
     // Next optional token: positive integer or a '+'.
     if (PH.Next(D.Min)) {
+      if (OneDiagPerDirective && D.Min != 1) {
+        NonSingularMatchDiagOffset = PH.C - PH.Begin;
+      }
       PH.Advance();
       // A positive integer can be followed by a '+' meaning min
       // or more, or by a '-' meaning a range from min to max.
       if (PH.Next("+")) {
         D.Max = Directive::MaxCount;
         PH.Advance();
+        if (OneDiagPerDirective) {
+          NonSingularMatchDiagOffset = PH.C - PH.Begin;
+        }
       } else if (PH.Next("-")) {
         PH.Advance();
         if (!PH.Next(D.Max) || D.Max < D.Min) {
@@ -621,16 +669,28 @@ static bool ParseDirective(StringRef S, ExpectedData *ED, 
SourceManager &SM,
                        diag::err_verify_invalid_range) << KindStr;
           continue;
         }
+        if (OneDiagPerDirective && D.Max != 1) {
+          NonSingularMatchDiagOffset = PH.C - PH.Begin;
+        }
         PH.Advance();
       } else {
         D.Max = D.Min;
       }
     } else if (PH.Next("+")) {
       // '+' on its own means "1 or more".
+      if (OneDiagPerDirective) {
+        NonSingularMatchDiagOffset = PH.C - PH.Begin;
+      }
       D.Max = Directive::MaxCount;
       PH.Advance();
     }
 
+    if (NonSingularMatchDiagOffset) {
+      Diags.Report(Pos.getLocWithOffset(*NonSingularMatchDiagOffset),
+                   diag::err_verify_non_singular_match);
+      State.AllDirectivesMatchExactlyOneDiag = false;
+    }
+
     // Skip optional whitespace.
     PH.SkipWhitespace();
 
@@ -697,6 +757,9 @@ 
VerifyDiagnosticConsumer::VerifyDiagnosticConsumer(DiagnosticsEngine &Diags_)
       State{HasNoDirectives, {}} {
   if (Diags.hasSourceManager())
     setSourceManager(Diags.getSourceManager());
+  CheckOrderOfDirectives = Diags.getDiagnosticOptions().VerifyDirectives;
+  OneDiagPerDirective = Diags.getDiagnosticOptions().VerifyDirectives;
+  DisableWildcardInDiagLoc = Diags.getDiagnosticOptions().VerifyDirectives;
 }
 
 VerifyDiagnosticConsumer::~VerifyDiagnosticConsumer() {
@@ -810,7 +873,8 @@ bool VerifyDiagnosticConsumer::HandleComment(Preprocessor 
&PP,
   // Fold any "\<EOL>" sequences
   size_t loc = C.find('\\');
   if (loc == StringRef::npos) {
-    ParseDirective(C, &ED, SM, &PP, CommentBegin, State, *Markers);
+    ParseDirective(C, &ED, SM, &PP, CommentBegin, State, *Markers,
+                   OneDiagPerDirective, DisableWildcardInDiagLoc);
     return false;
   }
 
@@ -840,7 +904,8 @@ bool VerifyDiagnosticConsumer::HandleComment(Preprocessor 
&PP,
   }
 
   if (!C2.empty())
-    ParseDirective(C2, &ED, SM, &PP, CommentBegin, State, *Markers);
+    ParseDirective(C2, &ED, SM, &PP, CommentBegin, State, *Markers,
+                   OneDiagPerDirective, DisableWildcardInDiagLoc);
   return false;
 }
 
@@ -879,7 +944,8 @@ static bool findDirectives(SourceManager &SM, FileID FID,
 
     // Find first directive.
     if (ParseDirective(Comment, nullptr, SM, nullptr, Tok.getLocation(), State,
-                       Markers))
+                       Markers, /*OneDiagPerDirective=*/false,
+                       /*DisableWildcardInDiagLoc=*/false))
       return true;
   }
   return false;
@@ -970,6 +1036,33 @@ static bool IsFromSameFile(SourceManager &SM, 
SourceLocation DirectiveLoc,
   return (DiagFile == SM.getFileEntryForID(SM.getFileID(DirectiveLoc)));
 }
 
+/// Takes a list of diagnostics that were partially matched and prints them.
+static unsigned
+PrintPartial(DiagnosticsEngine &Diags, SourceManager &SourceMgr,
+             llvm::SmallVector<std::pair<Directive *, std::string>> &DL,
+             const char *Kind) {
+  if (DL.empty())
+    return 0;
+
+  SmallString<256> Fmt;
+  llvm::raw_svector_ostream OS(Fmt);
+  for (const auto &[D, DiagText] : DL) {
+    OS << "\n  '" << D->Spelling << "' at line "
+       << SourceMgr.getPresumedLineNumber(D->DirectiveLoc) << " in "
+       << SourceMgr.getFilename(D->DiagnosticLoc) << ": " << D->Text
+       << "\n    does not fully match diagnostic at line "
+       << SourceMgr.getPresumedLineNumber(D->DiagnosticLoc);
+    if (!IsFromSameFile(SourceMgr, D->DirectiveLoc, D->DiagnosticLoc)) {
+      OS << " in " << SourceMgr.getFilename(D->DiagnosticLoc);
+    }
+    OS << ": " << DiagText;
+  }
+
+  Diags.Report(diag::err_verify_message_partial_match).setForceEmit()
+      << Kind << OS.str();
+  return DL.size();
+}
+
 /// CheckLists - Compare expected to seen diagnostic lists and return the
 /// the 
diff erence between them.
 static unsigned CheckLists(DiagnosticsEngine &Diags, SourceManager &SourceMgr,
@@ -980,6 +1073,7 @@ static unsigned CheckLists(DiagnosticsEngine &Diags, 
SourceManager &SourceMgr,
                            bool IgnoreUnexpected) {
   std::vector<Directive *> LeftOnly;
   DiagList Right(d2_begin, d2_end);
+  llvm::SmallVector<std::pair<Directive *, std::string>> IncompleteMatches;
 
   for (auto &Owner : Left) {
     Directive &D = *Owner;
@@ -999,8 +1093,15 @@ static unsigned CheckLists(DiagnosticsEngine &Diags, 
SourceManager &SourceMgr,
           continue;
 
         const std::string &RightText = II->second;
-        if (D.match(RightText))
+        DiagnosticMatchResult MatchResult = D.match(RightText);
+        if (MatchResult == DiagnosticMatchResult::OnlyPartial) {
+          assert(D.FullMatchRequired && "Partial match was checked for full "
+                                        "match when it is not needed!");
+          IncompleteMatches.push_back({&D, II->second});
+        }
+        if (MatchResult != DiagnosticMatchResult::None) {
           break;
+        }
       }
       if (II == IE) {
         // Not found.
@@ -1016,6 +1117,7 @@ static unsigned CheckLists(DiagnosticsEngine &Diags, 
SourceManager &SourceMgr,
   unsigned num = PrintExpected(Diags, SourceMgr, LeftOnly, Label);
   if (!IgnoreUnexpected)
     num += PrintUnexpected(Diags, &SourceMgr, Right.begin(), Right.end(), 
Label);
+  num += PrintPartial(Diags, SourceMgr, IncompleteMatches, Label);
   return num;
 }
 
@@ -1058,6 +1160,141 @@ static unsigned CheckResults(DiagnosticsEngine &Diags, 
SourceManager &SourceMgr,
   return NumProblems;
 }
 
+// Checks that directives are lexically in the same order as the emitted
+// diagnostics. Assumes that:
+//   - every directive matches exactly one diagnostic,
+//   - there are no wildcards, and
+//   - CheckResults returned 0 problems, i.e. every diagnostic
+//     was matched by every directive without considering the order.
+static unsigned CheckResultsAreInOrder(DiagnosticsEngine &Diags,
+                                       SourceManager &SourceMgr,
+                                       const TextDiagnosticBuffer &Buffer,
+                                       const ExpectedData &ED) {
+  // Building a set of all directives ordered by their location.
+  auto directiveComparator = [](const Directive *LHS, const Directive *RHS) {
+    return LHS->DirectiveLoc < RHS->DirectiveLoc;
+  };
+  auto sortDirectives = [&](const DirectiveList &Unordered) {
+    std::vector<const Directive *> Ordered(Unordered.size());
+    std::transform(Unordered.cbegin(), Unordered.cend(), Ordered.begin(),
+                   [](const std::unique_ptr<Directive> &D) { return &*D; });
+    std::sort(Ordered.begin(), Ordered.end(), directiveComparator);
+    return Ordered;
+  };
+  std::vector<const Directive *> OrderedErrors = sortDirectives(ED.Errors);
+  std::vector<const Directive *> OrderedWarns = sortDirectives(ED.Warnings);
+  std::vector<const Directive *> OrderedNotes = sortDirectives(ED.Notes);
+  std::vector<const Directive *> OrderedRemarks = sortDirectives(ED.Remarks);
+
+  std::vector<const Directive *> OrderedDirectives = [&] {
+    std::vector<const Directive *> OrderedEW(OrderedErrors.size() +
+                                             OrderedWarns.size());
+    std::merge(OrderedErrors.cbegin(), OrderedErrors.cend(),
+               OrderedWarns.cbegin(), OrderedWarns.cend(), OrderedEW.begin(),
+               directiveComparator);
+
+    std::vector<const Directive *> OrderedNR(OrderedNotes.size() +
+                                             OrderedRemarks.size());
+    std::merge(OrderedNotes.cbegin(), OrderedNotes.cend(),
+               OrderedRemarks.cbegin(), OrderedRemarks.cend(),
+               OrderedNR.begin(), directiveComparator);
+
+    std::vector<const Directive *> OrderedDirectives(OrderedEW.size() +
+                                                     OrderedNR.size());
+    std::merge(OrderedEW.cbegin(), OrderedEW.cend(), OrderedNR.cbegin(),
+               OrderedNR.cend(), OrderedDirectives.begin(),
+               directiveComparator);
+    return OrderedDirectives;
+  }();
+
+  auto getLocDiagPair = [&](DiagnosticsEngine::Level DiagLevel, long DiagIndex)
+      -> const std::pair<clang::SourceLocation, std::basic_string<char>> & {
+    TextDiagnosticBuffer::const_iterator It = [&] {
+      switch (DiagLevel) {
+      case DiagnosticsEngine::Level::Fatal:
+      case DiagnosticsEngine::Level::Error:
+        assert(DiagIndex < Buffer.err_end() - Buffer.err_begin() &&
+               "DiagIndex is out of bounds!");
+        return Buffer.err_begin();
+      case DiagnosticsEngine::Level::Warning:
+        assert(DiagIndex < Buffer.warn_end() - Buffer.warn_begin() &&
+               "DiagIndex is out of bounds!");
+        return Buffer.warn_begin();
+      case DiagnosticsEngine::Level::Note:
+        assert(DiagIndex < Buffer.note_end() - Buffer.note_begin() &&
+               "DiagIndex is out of bounds!");
+        return Buffer.note_begin();
+      case DiagnosticsEngine::Level::Remark:
+        assert(DiagIndex < Buffer.remark_end() - Buffer.remark_begin() &&
+               "DiagIndex is out of bounds!");
+        return Buffer.remark_begin();
+      case DiagnosticsEngine::Level::Ignored:
+        llvm_unreachable("Unexpected diagnostic level!");
+      }
+    }();
+
+    std::advance(It, DiagIndex);
+    return *It;
+  };
+
+  using LevelDiagPairT = std::pair<DiagnosticsEngine::Level, size_t>;
+  static_assert(std::is_same_v<LevelDiagPairT,
+                               TextDiagnosticBuffer::AllDiagList::value_type>);
+  int NumProblems = 0;
+  SmallString<256> Fmt;
+  llvm::raw_svector_ostream OS(Fmt);
+  // zip_equal asserts that there're as many directives as emitted diagnostics.
+  // CheckResults has already ensured that all diagnostics were matched.
+  for (const auto [Directive, LevelDiagPair] : llvm::zip_equal(
+           OrderedDirectives,
+           llvm::iterator_range{Buffer.all_begin(), Buffer.all_end()})) {
+    assert(!Directive->MatchAnyFileAndLine && !Directive->MatchAnyLine &&
+           "Cannot compare source locations when wildcards are present");
+    const auto [DiagLevel, DiagIndex] = LevelDiagPair;
+    const auto &[DiagLoc, DiagText] = getLocDiagPair(DiagLevel, DiagIndex);
+    const SourceLocation DirLoc = Directive->DirectiveLoc;
+
+    bool LocsMatch =
+        SourceMgr.getPresumedLineNumber(DiagLoc) ==
+            SourceMgr.getPresumedLineNumber(Directive->DiagnosticLoc) &&
+        IsFromSameFile(SourceMgr, Directive->DiagnosticLoc, DiagLoc);
+    bool TextMatch = Directive->match(DiagText) == DiagnosticMatchResult::Full;
+    if (LocsMatch && TextMatch) {
+      continue;
+    }
+    ++NumProblems;
+
+    auto printFileNameIfDifferent = [&](SourceLocation DirLoc,
+                                        SourceLocation Loc) {
+      if (!IsFromSameFile(SourceMgr, DirLoc, Loc)) {
+        OS << " in " << SourceMgr.getFilename(Loc);
+      }
+    };
+
+    OS << "\n  '" << Directive->Spelling << "' at line "
+       << SourceMgr.getPresumedLineNumber(DirLoc) << " in "
+       << SourceMgr.getFilename(DirLoc) << ": " << Directive->Text
+       << "\n    matches diagnostic at line "
+       << SourceMgr.getPresumedLineNumber(Directive->DiagnosticLoc);
+    printFileNameIfDifferent(DirLoc, Directive->DiagnosticLoc);
+    if (TextMatch) {
+      OS << ", but diagnostic with the same message was first emitted at line "
+         << SourceMgr.getPresumedLineNumber(DiagLoc);
+      printFileNameIfDifferent(DirLoc, DiagLoc);
+    } else {
+      OS << ", but diagnostic at line "
+         << SourceMgr.getPresumedLineNumber(DiagLoc);
+      printFileNameIfDifferent(DirLoc, DiagLoc);
+      OS << " was emitted first:"
+         << "\n      " << DiagText;
+    }
+  }
+  if (NumProblems > 0) {
+    Diags.Report(diag::err_verify_directive_out_of_order) << OS.str();
+  }
+  return NumProblems;
+}
+
 void VerifyDiagnosticConsumer::UpdateParsedFileStatus(SourceManager &SM,
                                                       FileID FID,
                                                       ParsedStatus PS) {
@@ -1145,6 +1382,16 @@ void VerifyDiagnosticConsumer::CheckDiagnostics() {
 
     // Check that the expected diagnostics occurred.
     NumErrors += CheckResults(Diags, *SrcManager, *Buffer, ED);
+
+    // If either wildcards are present or there are directives that
+    // do not match exactly one diagnostic, we already issued
+    // errors about that. In such case we cannot check that directives
+    // are in order.
+    if (CheckOrderOfDirectives && NumErrors == 0 &&
+        State.AllDirectivesMatchExactlyOneDiag &&
+        !State.WildcardsAreErroneouslyPresent) {
+      NumErrors += CheckResultsAreInOrder(Diags, *SrcManager, *Buffer, ED);
+    }
   } else {
     const DiagnosticLevelMask DiagMask =
         ~Diags.getDiagnosticOptions().getVerifyIgnoreUnexpected();
@@ -1173,11 +1420,11 @@ std::unique_ptr<Directive>
 Directive::create(bool RegexKind, SourceLocation DirectiveLoc,
                   SourceLocation DiagnosticLoc, StringRef Spelling,
                   bool MatchAnyFileAndLine, bool MatchAnyLine, StringRef Text,
-                  unsigned Min, unsigned Max) {
+                  unsigned Min, unsigned Max, bool FullMatchRequired) {
   if (!RegexKind)
-    return std::make_unique<StandardDirective>(DirectiveLoc, DiagnosticLoc,
-                                               Spelling, MatchAnyFileAndLine,
-                                               MatchAnyLine, Text, Min, Max);
+    return std::make_unique<StandardDirective>(
+        DirectiveLoc, DiagnosticLoc, Spelling, MatchAnyFileAndLine,
+        MatchAnyLine, Text, Min, Max, FullMatchRequired);
 
   // Parse the directive into a regular expression.
   std::string RegexStr;
@@ -1201,7 +1448,7 @@ Directive::create(bool RegexKind, SourceLocation 
DirectiveLoc,
     }
   }
 
-  return std::make_unique<RegexDirective>(DirectiveLoc, DiagnosticLoc, 
Spelling,
-                                          MatchAnyFileAndLine, MatchAnyLine,
-                                          Text, Min, Max, RegexStr);
+  return std::make_unique<RegexDirective>(
+      DirectiveLoc, DiagnosticLoc, Spelling, MatchAnyFileAndLine, MatchAnyLine,
+      Text, Min, Max, RegexStr, FullMatchRequired);
 }

diff  --git a/clang/test/Frontend/verify-directives-full-match.cpp 
b/clang/test/Frontend/verify-directives-full-match.cpp
new file mode 100644
index 0000000000000..ea769d0c256bd
--- /dev/null
+++ b/clang/test/Frontend/verify-directives-full-match.cpp
@@ -0,0 +1,10 @@
+// RUN: not %clang_cc1 -verify -verify-directives %s 2>&1 | FileCheck %s
+
+void f1(A);
+// expected-error@-1 {{unknown type}}
+
+// CHECK:      error: diagnostic messages of 'error' severity not fully 
matched:
+// CHECK-NEXT:   'expected-error' at line 4 in {{.*}}: unknown type
+// CHECK-NEXT:      does not fully match diagnostic at line 3: unknown type 
name 'A'
+
+// CHECK-NEXT: 1 error generated.

diff  --git a/clang/test/Frontend/verify-directives-one-diag.cpp 
b/clang/test/Frontend/verify-directives-one-diag.cpp
new file mode 100644
index 0000000000000..661fb078c3f30
--- /dev/null
+++ b/clang/test/Frontend/verify-directives-one-diag.cpp
@@ -0,0 +1,45 @@
+// RUN: not %clang_cc1 -verify -verify-directives %s 2>&1 | FileCheck %s
+
+void f1(A, A);
+// expected-error@-1 {{unknown type name 'A'}}
+// expected-error@-2 1 {{unknown type name 'A'}}
+
+void f2(A, A);
+// expected-error@-1 2 {{unknown type name 'A'}}
+
+// CHECK:      error: 'expected-error' diagnostics seen but not expected:
+// CHECK-NEXT:   Line 8: diagnostic verification mode disallows use of a 
diagnostic quantifier
+
+void f3(A, A);
+// expected-error@-1 0-1 {{unknown type name 'A'}}
+// expected-error@-2 0-1 {{unknown type name 'A'}}
+
+// CHECK-NEXT:   Line 14: diagnostic verification mode disallows use of a 
diagnostic quantifier
+// CHECK-NEXT:   Line 15: diagnostic verification mode disallows use of a 
diagnostic quantifier
+
+void f4(A, A);
+// expected-error@-1 1-2 {{unknown type name 'A'}}
+
+// CHECK-NEXT:   Line 21: diagnostic verification mode disallows use of a 
diagnostic quantifier
+
+void f5(A);
+// expected-error@-1 0-2 {{unknown type name 'A'}}
+
+// CHECK-NEXT:   Line 26: diagnostic verification mode disallows use of a 
diagnostic quantifier
+
+void f6(A, A);
+// expected-error@-1 + {{unknown type name 'A'}}
+
+// CHECK-NEXT:   Line 31: diagnostic verification mode disallows use of a 
diagnostic quantifier
+
+void f7(A, A);
+// expected-error@-1 0+ {{unknown type name 'A'}}
+
+// CHECK-NEXT:   Line 36: diagnostic verification mode disallows use of a 
diagnostic quantifier
+
+void f8(A, A);
+// expected-error@-1 1+ {{unknown type name 'A'}}
+
+// CHECK-NEXT:   Line 41: diagnostic verification mode disallows use of a 
diagnostic quantifier
+
+// CHECK-NEXT: 8 errors generated.

diff  --git a/clang/test/Frontend/verify-directives-order.cpp 
b/clang/test/Frontend/verify-directives-order.cpp
new file mode 100644
index 0000000000000..d1cc6b8d6291b
--- /dev/null
+++ b/clang/test/Frontend/verify-directives-order.cpp
@@ -0,0 +1,14 @@
+// RUN: not %clang_cc1 -verify -verify-directives %s 2>&1 | FileCheck %s
+
+void f1(A); // #f1
+void f2(B); // #f2
+// expected-error@#f2 {{unknown type name 'B'}}
+// expected-error@#f1 {{unknown type name 'A'}}
+
+// CHECK:      error: all diagnostics were successfully matched, but 
out-of-order directives were found:
+// CHECK-NEXT:   'expected-error' at line 5 in {{.*}}: unknown type name 'B'
+// CHECK-NEXT:     matches diagnostic at line 4, but diagnostic at line 3 was 
emitted first:
+// CHECK-NEXT:       unknown type name 'A'
+// CHECK-NEXT:   'expected-error' at line 6 in {{.*}}: unknown type name 'A'
+// CHECK-NEXT:     matches diagnostic at line 3, but diagnostic at line 4 was 
emitted first:
+// CHECK-NEXT:       unknown type name 'B'

diff  --git a/clang/test/Frontend/verify-directives-wildcard.cpp 
b/clang/test/Frontend/verify-directives-wildcard.cpp
new file mode 100644
index 0000000000000..98a39cbf984e0
--- /dev/null
+++ b/clang/test/Frontend/verify-directives-wildcard.cpp
@@ -0,0 +1,14 @@
+// RUN: not %clang_cc1 -verify -verify-directives %s 2>&1 | FileCheck %s
+
+void f1(A);
+// expected-error@* {{unknown type name 'A'}}
+
+// CHECK:      error: 'expected-error' diagnostics seen but not expected:
+// CHECK-NEXT:   Line 4: diagnostic verification mode disallows use of a 
wildcard for diagnostic location
+
+void f2(A);
+// expected-error@*:* {{unknown type name 'A'}}
+
+// CHECK-NEXT:   Line 10: diagnostic verification mode disallows use of a 
wildcard for diagnostic location
+
+// CHECK-NEXT: 2 errors generated.


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to