Skip to content

[Clang] Wide delimiters ('{{{') for expect strings #77326

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 32 additions & 2 deletions clang/docs/InternalsManual.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3364,7 +3364,7 @@ Multiple occurrences accumulate prefixes. For example,

Specifying Diagnostics
^^^^^^^^^^^^^^^^^^^^^^
Indicating that a line expects an error or a warning is simple. Put a comment
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,
warning, remark, or note (respectively), and place the expected text between
Expand All @@ -3373,6 +3373,9 @@ enough to ensure that the correct diagnostic was emitted. (Note: full text
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.

Here's an example of the most commonly used way to specify expected
diagnostics:

Expand Down Expand Up @@ -3458,8 +3461,33 @@ 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
~~~~~~~~~~~~~~

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
single newline character. This mode matches the emitted diagnostic when the
text appears as a substring at any position of the emitted message.

To enable matching against desired strings that contain `}}` or `{{`, the
string-mode parser accepts opening delimiters of more than two curly braces,
like `{{{`. It then looks for a closing delimiter of equal "width" (i.e `}}}`).
For example:

.. code-block:: c++

// expected-note {{{evaluates to '{{2, 3, 4}} == {0, 3, 4}'}}}

The intent is to allow the delimeter to be wider than the longest `{` or `}`
brace sequence in the content, so that if your expected text contains `{{{`
(three braces) it may be delimited with `{{{{` (four braces), and so on.

Regex matching mode may be selected by appending ``-re`` to the diagnostic type
and including regexes wrapped in double curly braces in the directive, such as:
and including regexes wrapped in double curly braces (`{{` and `}}`) in the
directive, such as:

.. code-block:: text

Expand All @@ -3471,6 +3499,8 @@ Examples matching error: "variable has incomplete type 'struct s'"

// expected-error {{variable has incomplete type 'struct s'}}
// expected-error {{variable has incomplete type}}
// expected-error {{{variable has incomplete type}}}
// expected-error {{{{variable has incomplete type}}}}

// expected-error-re {{variable has type 'struct {{.}}'}}
// expected-error-re {{variable has type 'struct {{.*}}'}}
Expand Down
2 changes: 1 addition & 1 deletion clang/include/clang/Basic/DiagnosticFrontendKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ def err_verify_no_such_marker : Error<
def err_verify_missing_start : Error<
"cannot find start ('{{') of expected %0">;
def err_verify_missing_end : Error<
"cannot find end ('}}') of expected %0">;
"cannot find end ('%1') of expected %0">;
def err_verify_invalid_content : Error<
"invalid expected %0: %1">;
def err_verify_missing_regex : Error<
Expand Down
15 changes: 11 additions & 4 deletions clang/lib/Frontend/VerifyDiagnosticConsumer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -611,12 +611,19 @@ static bool ParseDirective(StringRef S, ExpectedData *ED, SourceManager &SM,
diag::err_verify_missing_start) << KindStr;
continue;
}
llvm::SmallString<8> CloseBrace("}}");
const char *const DelimBegin = PH.C;
PH.Advance();
// Count the number of opening braces for `string` kinds
for (; !D.RegexKind && PH.Next("{"); PH.Advance())
CloseBrace += '}';
const char* const ContentBegin = PH.C; // mark content begin
// Search for token: }}
if (!PH.SearchClosingBrace("{{", "}}")) {
Diags.Report(Pos.getLocWithOffset(PH.C-PH.Begin),
diag::err_verify_missing_end) << KindStr;
// Search for closing brace
StringRef OpenBrace(DelimBegin, ContentBegin - DelimBegin);
if (!PH.SearchClosingBrace(OpenBrace, CloseBrace)) {
Diags.Report(Pos.getLocWithOffset(PH.C - PH.Begin),
diag::err_verify_missing_end)
<< KindStr << CloseBrace;
continue;
}
const char* const ContentEnd = PH.P; // mark content end
Expand Down
30 changes: 30 additions & 0 deletions clang/test/Frontend/verify.c
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,33 @@ unexpected b; // expected-error@33 1-1 {{unknown type}}
// what-error {{huh?}}
// CHECK9: error: 'what-error' diagnostics expected but not seen:
#endif

#ifdef TEST_WIDE_DELIM
// RUN: not %clang_cc1 -DTEST_WIDE_DELIM -verify %s 2>&1 | FileCheck -check-prefix=CHECK-WIDE-DELIM %s

// expected-error {{{some message with {{}} in it}}}
// expected-error {{{some message with {}} in it}}}
// expected-error {{{some message with {{} in it}}}

// expected-error-re {{{some {{.*}} regex with double braces}}}
// expected-error-re {{{some message with {{} in it}}}

// expected-error {{{mismatched delim}}
// expected-error-re {{{mismatched re {{.*} }}}
// expected-error-re {{{no regex}}}

#if 0
// CHECK-WIDE-DELIM: error: 'expected-error' diagnostics expected but not seen:
// CHECK-WIDE-DELIM-NEXT: verify.c Line 164: some message with {{[{]{}[}]}} in it
// CHECK-WIDE-DELIM-NEXT: verify.c Line 165: some message with {}} in it
// CHECK-WIDE-DELIM-NEXT: verify.c Line 166: some message with {{[{]{[}]}} in it
// CHECK-WIDE-DELIM-NEXT: verify.c Line 168: {some {{.*}} regex with double braces
// CHECK-WIDE-DELIM-NEXT: error: 'expected-error' diagnostics seen but not expected:
// CHECK-WIDE-DELIM-NEXT: verify.c Line 169: cannot find end ('}}') of expected regex
// CHECK-WIDE-DELIM-NEXT: verify.c Line 171: cannot find end ('}}}') of expected string
// CHECK-WIDE-DELIM-NEXT: verify.c Line 172: cannot find end ('}}') of expected regex
// CHECK-WIDE-DELIM-NEXT: verify.c Line 173: cannot find start of regex ('{{[{][{]}}') in {no regex
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably confusing; it's {no regex because the delimiter is always {{ in regex-mode, so the string is parsed like:

// expected-error-re {{{no regex}}}
                       |---------|^ trailing }

// CHECK-WIDE-DELIM-NEXT: 8 errors generated.
#endif

#endif