[llvm] 138e217 - [FileCheck] Annotate search ranges with { } in -dump-input (#198138)

via llvm-commits llvm-commits at lists.llvm.org
Fri Jun 26 06:35:56 PDT 2026


Author: Joel E. Denny
Date: 2026-06-26T13:35:51Z
New Revision: 138e21730a47aea6004daa4bfd14ca39ea2250d0

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

LOG: [FileCheck] Annotate search ranges with { } in -dump-input (#198138)

This PR has an
[RFC](https://discourse.llvm.org/t/rfc-filecheck-improving-input-dump-readability/91112).
It is stacked on PR #199063.

Example
=======

```
$ cat check
CHECK: start
CHECK-NEXT: end

$ FileCheck -v -dump-input-context=2 check < input |& tail -23
<<<<<<
          1: start
check:1      ^~~~~
next:2'0         {   search range start (exclusive)
          2: foo0
          3: foo1
          .
          .
          .
         21: foo19
         22: foo20
         23: end
next:2'1     !~~   error: match on wrong line
         24: bar0
         25: bar1
          .
          .
          .
         42: bar18
         43: bar19
         44: bar20
next:2'2           } search range end (exclusive)
>>>>>>
```

Without this patch, input lines 1-3 and 42-44 are not shown. However,
lines 1-3 are where the actual problem is because that is where the
`CHECK-NEXT` directive was expected to match but did not.

Search Ranges Are Helpful
=========================

In general, this patch marks any failed pattern's search range by using
the annotation style shown above, and these annotations are filtered in
when using `-dump-input-filter=error`, which is the default filter.
Seeing the search range can be helpful for understanding the pattern's
behavior. Moreover, the cause of the pattern failure is often the input
at the start or end of the search range. For example:

- A `CHECK-NEXT` or `CHECK-SAME` match on the wrong line, as in the
  above example.
- A `CHECK-NOT` unexpected match because a neighboring directive matched
  at an unexpected point, affecting the search range.
- An unmatched `CHECK` because a subsequent `CHECK-LABEL` matched at an
  unexpected point, affecting the search range end. (In this case, the
  search range start and thus the prior directive's match is already
  revealed without this patch.)

This patch updates tests in
`llvm/test/FileCheck/dump-input/search-range-annotations`, which
demonstrate its benefit for those cases.

This patch is a replacement for D96653, which attempted to address the
above cases but in a less straight-forward and somewhat broken manner.
The idea of the current patch was discussed during that review.

Replacing `X~~`
===============

Without this patch, search ranges for unmatched patterns (whether a
success, as for `CHECK-NOT`, or a failure, as for `CHECK`) are already
marked with `X~~`. In those cases, this patch replaces those annotations
with the new annotations shown above. As described above, this patch
adds the new annotations to all other failed matched patterns as well.

`X~~` is thus no longer used by `-dump-input`. The `X~~` style is very
noisy, especially for consecutive unmatched unexpected patterns (like a
`CHECK-NOT` block), all of which mark every line of their identical
search ranges. Switching to the new style significantly reduces the
noise in such cases. That noise was discussed recently in issue #77257
and PRs linked from there, and that discussion led to the resurrection
of this patch.

Without this patch, `-dump-input-filter=error` filters in only the start
of a search range that spans multiple lines because an entire `X~~` is
one annotation. With this patch, it filters in both the start and end of
a search range because they are separate annotations (or a single
one-liner, as discussed below). With or without this patch, a different
argument to `-dump-input-filter` (and `-vv`) is required to filter in
the search range for an unmatched unexpected pattern, like `CHECK-NOT`,
because that is not an error.

One-Liners
==========

If a search range does not involve multiple input lines, this patch
keeps the `{` and `}` markers on the same output line like this:

```
<<<<<<
         1: start foo end
check:1     ^~~~~
check:3               ^~~
not:2           {     }     search range (exclusive bounds)
>>>>>>
```

Exclusive Boundaries
====================

`{` and `}` are to be interpreted as exclusive bounds. That is, the
characters at those markers are not included in the search range, but
everything in between is. To try to avoid confusion, this patch adds the
word "exclusive" in every search range annotation.

When the search range starts or ends at a line boundary, the marker
cannot be placed at the first or last character of the line because that
would exclude that character. This patch instead places the marker in
the margin of the input dump, either before the line's first character
or after the line's last character (usually a newline), rather than on
the adjacent line. I have found this makes the annotations easier to
read (more apt have a one-liner, at least), and I do not think it would
make much sense to move a start annotation to an imaginary line 0. For
example, the `{` and `}` below appear in the input dump margins, before
the first character, `s`, and after the space representing the newline:

```
<<<<<<
           1: start
check:1'0    {      } search range (exclusive bounds)
check:1'1             error: no match found in search range
>>>>>>
```

Before trying exclusive bounds, I experimented with notations involving
inclusive bounds (e.g., `[` and `]`, or `[` and `)`). Such notations
either cannot distinguish empty ranges (without a cryptic inversion like
`][`) from one-character ranges, or they cannot represent them with
one-liners (because they must occupy the same column), increasing
verbosity. I ultimately decided I prefer exclusive bounds because they
are visually and semantically symmetric while consistently concise and
unambiguous.

For comparison, match ranges (e.g., `^~~`) cannot distinguish
one-character ranges from empty ranges. However, in my experience, empty
match ranges are uncommon, and usually it is easy to distinguish them
based on the pattern or directive (e.g., `CHECK-EMPTY`). In contrast,
empty search ranges can occur repeatedly when directive matches are
adjacent and `-implicit-check-not` is used.

Added: 
    

Modified: 
    llvm/include/llvm/FileCheck/FileCheck.h
    llvm/test/FileCheck/dump-input/annotations.txt
    llvm/test/FileCheck/dump-input/context.txt
    llvm/test/FileCheck/dump-input/enable.txt
    llvm/test/FileCheck/dump-input/filter.txt
    llvm/test/FileCheck/dump-input/search-range-annotations/check-label-follows.txt
    llvm/test/FileCheck/dump-input/search-range-annotations/check-next-same.txt
    llvm/test/FileCheck/dump-input/search-range-annotations/check-not.txt
    llvm/test/FileCheck/match-time-error-propagation/invalid-excluded-pattern.txt
    llvm/test/FileCheck/match-time-error-propagation/invalid-expected-pattern.txt
    llvm/test/FileCheck/match-time-error-propagation/matched-excluded-pattern.txt
    llvm/test/FileCheck/match-time-error-propagation/matched-expected-pattern.txt
    llvm/utils/FileCheck/FileCheck.cpp

Removed: 
    


################################################################################
diff  --git a/llvm/include/llvm/FileCheck/FileCheck.h b/llvm/include/llvm/FileCheck/FileCheck.h
index b000f5b0dac73..61486567c403b 100644
--- a/llvm/include/llvm/FileCheck/FileCheck.h
+++ b/llvm/include/llvm/FileCheck/FileCheck.h
@@ -302,7 +302,7 @@ class MatchNoneDiag : public MatchResultDiag {
 /// \c MatchResultDiag.
 class MatchNoteDiag : public FileCheckDiag {
 private:
-  MatchResultDiag *MRD;
+  const MatchResultDiag *MRD;
 
 public:
   MatchNoteDiag(FileCheckDiagKind Kind) : FileCheckDiag(Kind), MRD(nullptr) {}
@@ -316,7 +316,7 @@ class MatchNoteDiag : public FileCheckDiag {
   /// Get the note's associated \c MatchResultDiag.
   const MatchResultDiag &getMatchResultDiag() const override { return *MRD; }
   /// Set the note's associated \c MatchResultDiag.
-  void setMatchResultDiag(MatchResultDiag *MRDNew) {
+  void setMatchResultDiag(const MatchResultDiag *MRDNew) {
     assert(!MRD && "expected setMatchResultDiag to be called only once");
     MRD = MRDNew;
   }

diff  --git a/llvm/test/FileCheck/dump-input/annotations.txt b/llvm/test/FileCheck/dump-input/annotations.txt
index f887e218314e8..18ece6b508c5f 100644
--- a/llvm/test/FileCheck/dump-input/annotations.txt
+++ b/llvm/test/FileCheck/dump-input/annotations.txt
@@ -132,12 +132,12 @@
 ;        CHK:<<<<<<
 ;   CHK-NEXT:              1: hello 
 ; CHK-V-NEXT:check:1          ^~~~~
-;   CHK-NEXT:check:2'0             X error: no match found
+;   CHK-NEXT:check:2'0            {   search range start (exclusive)
+;   CHK-NEXT:check:2'1                error: no match found in search range
 ;   CHK-NEXT:              2: again 
-;   CHK-NEXT:check:2'0        ~~~~~~
 ;   CHK-NEXT:              3: whirled 
-;   CHK-NEXT:check:2'0        ~~~~~~~~
-;   CHK-NEXT:check:2'1        ?        possible intended match
+;   CHK-NEXT:check:2'2        ?         possible intended match
+;   CHK-NEXT:check:2'3                } search range end (exclusive)
 ;   CHK-NEXT:>>>>>>
 ;    CHK-NOT:{{.}}
 
@@ -166,25 +166,27 @@
 ; CNT-V-NEXT:count:1'0        ^~~~
 ; CNT-V-NEXT:              2: repete 
 ; CNT-V-NEXT:count:1'1          ^~~~
-; CNT-V-NEXT:count:1'2              X error: no match found
+; CNT-V-NEXT:count:1'2             {   search range start (exclusive)
+; CNT-V-NEXT:count:1'3                 error: no match found in search range
 ; CNT-V-NEXT:              3: repeat 
-; CNT-V-NEXT:count:1'2        ~~~~~~~
-; CNT-V-NEXT:count:1'3        ?       possible intended match
+; CNT-V-NEXT:count:1'4        ?        possible intended match
+; CNT-V-NEXT:count:1'5               } search range end (exclusive)
 ; CNT-V-NEXT:>>>>>>
 ;  CNT-V-NOT:{{.}}
 
 ;      CNT-Q:<<<<<<
 ; CNT-Q-NEXT:              1: pete 
 ; CNT-Q-NEXT:              2: repete 
-; CNT-Q-NEXT:count:1'0              X error: no match found
+; CNT-Q-NEXT:count:1'0             {   search range start (exclusive)
+; CNT-Q-NEXT:count:1'1                 error: no match found in search range
 ; CNT-Q-NEXT:              3: repeat 
-; CNT-Q-NEXT:count:1'0        ~~~~~~~
-; CNT-Q-NEXT:count:1'1        ?       possible intended match
+; CNT-Q-NEXT:count:1'2        ?        possible intended match
+; CNT-Q-NEXT:count:1'3               } search range end (exclusive)
 ; CNT-Q-NEXT:>>>>>>
 ;  CNT-Q-NOT:{{.}}
 
 ;--------------------------------------------------
-; CHECK-NEXT (also: EOF search-range, wrong-line match)
+; CHECK-NEXT (also: EOF search-range, single-char search range, wrong-line match)
 ;--------------------------------------------------
 
 ; Good match and no match.
@@ -209,7 +211,8 @@
 ; NXT-V-NEXT:check:1          ^~~~~
 ;   NXT-NEXT:              2: again 
 ; NXT-V-NEXT:next:2           ^~~~~
-;   NXT-NEXT:next:3                X error: no match found
+;   NXT-NEXT:next:3'0             { } search range (exclusive bounds)
+;   NXT-NEXT:next:3'1                 error: no match found in search range
 ;   NXT-NEXT:>>>>>>
 ;    NXT-NOT:{{.}}
 
@@ -231,15 +234,16 @@
 ; NXT2-V-NEXT:check:1          ^~~~~
 ;   NXT2-NEXT:              2: again 
 ; NXT2-V-NEXT:next:2           ^~~~~
+;   NXT2-NEXT:next:3'0             {   search range start (exclusive)
 ;   NXT2-NEXT:              3: yonder 
 ;   NXT2-NEXT:              4: world 
-;   NXT2-NEXT:next:3           !~~~~  error: match on wrong line
+;   NXT2-NEXT:next:3'1         !~~~~   error: match on wrong line
+;   NXT2-NEXT:next:3'2               } search range end (exclusive)
 ;   NXT2-NEXT:>>>>>>
 ;    NXT2-NOT:{{.}}
 
 ;--------------------------------------------------
-; CHECK-SAME (also: multiple annotations per line, single-char search range,
-; wrong-line match)
+; CHECK-SAME (also: multiple annotations per line, wrong-line match)
 ;--------------------------------------------------
 
 ; Good match and no match.
@@ -262,7 +266,8 @@
 ;   SAM-NEXT:              1: hello world! 
 ; SAM-V-NEXT:check:1          ^~~~~
 ; SAM-V-NEXT:same:2                 ^~~~~
-;   SAM-NEXT:same:3                      X~ error: no match found
+;   SAM-NEXT:same:3'0                   {  } search range (exclusive bounds)
+;   SAM-NEXT:same:3'1                        error: no match found in search range
 ;   SAM-NEXT:>>>>>>
 ;    SAM-NOT:{{.}}
 
@@ -282,13 +287,15 @@
 ;   SAM2-NEXT:              1: hello world! 
 ; SAM2-V-NEXT:check:1          ^~~~~
 ; SAM2-V-NEXT:same:2                 ^~~~~
+;   SAM2-NEXT:same:3'0                   {    search range start (exclusive)
 ;   SAM2-NEXT:              2: again 
-;   SAM2-NEXT:same:3           !~~~~  error: match on wrong line
+;   SAM2-NEXT:same:3'1         !~~~~   error: match on wrong line
+;   SAM2-NEXT:same:3'2               } search range end (exclusive)
 ;   SAM2-NEXT:>>>>>>
 ;    SAM2-NOT:{{.}}
 
 ;--------------------------------------------------
-; CHECK-EMPTY (also: search range ends at label, single-char match, wrong-line
+; CHECK-EMPTY (also: search range ends at label, empty match range, wrong-line
 ; match)
 ;--------------------------------------------------
 
@@ -322,12 +329,12 @@
 ; EMP-V-NEXT:check:1          ^~~~~
 ;   EMP-NEXT:              2:  
 ; EMP-V-NEXT:empty:2          ^
-;   EMP-NEXT:empty:3          X error: no match found
+;   EMP-NEXT:empty:3'0       {   search range start (exclusive)
+;   EMP-NEXT:empty:3'1           error: no match found in search range
 ;   EMP-NEXT:              3: world 
-;   EMP-NEXT:empty:3          ~~~~~~
 ;   EMP-NEXT:              4: label 
 ; EMP-V-NEXT:label:4          ^~~~~
-;   EMP-NEXT:empty:3          ~~~~~
+;   EMP-NEXT:empty:3'2             }  search range end (exclusive)
 ;   EMP-NEXT:>>>>>>
 ;    EMP-NOT:{{.}}
 
@@ -347,12 +354,24 @@
 ; Verbose diagnostics are suppressed but not errors.
 ; EMP2:{{.*}}error:{{.*}}
 
+; TODO: The search range of CHECK-EMPTY logically ends at the last character of
+; the file, as show below.  However, it can actually match the empty string
+; immediately after its search range, as shown below.  That is, -dump-input
+; seems to be doing the right thing: it makes no sense to show the search range
+; including a non-existent character.  If it's worth the effort to change
+; anything here, then CHECK-EMPTY should probably report any match past the end
+; of the file as a failure to match at all.  That would also include cases that
+; are currently successful matches, but that's a strange enough case that it
+; probably wouldn't be backward-incompatible for real test suites.
+
 ;        EMP2:<<<<<<
 ;   EMP2-NEXT:              1: hello 
 ; EMP2-V-NEXT:check:1          ^~~~~
+;   EMP2-NEXT:empty:2'0            {   search range start (exclusive)
 ;   EMP2-NEXT:              2: world 
+;   EMP2-NEXT:empty:2'2              } search range end (exclusive)
 ;   EMP2-NEXT:              3: 
-;   EMP2-NEXT:empty:2          ! error: match on wrong line
+;   EMP2-NEXT:empty:2'1        ! error: match on wrong line
 ;   EMP2-NEXT:>>>>>>
 ;    EMP2-NOT:{{.}}
 
@@ -379,12 +398,13 @@
 
 ;         NOT:<<<<<<
 ;    NOT-NEXT:              1: hello 
-; NOT-VV-NEXT:not:1            X~~~~~
+; NOT-VV-NEXT:not:1'0         {        search range start (exclusive)
+;    NOT-NEXT:not:2'0         {        search range start (exclusive)
 ;    NOT-NEXT:              2: world 
-; NOT-VV-NEXT:not:1            ~~~~~~
-;    NOT-NEXT:not:2            !~~~~  error: no match expected
+;    NOT-NEXT:not:2'1          !~~~~   error: no match expected
 ;    NOT-NEXT:              3: again 
-; NOT-VV-NEXT:not:1            ~~~~~~
+; NOT-VV-NEXT:not:1'1                } search range end (exclusive)
+;    NOT-NEXT:not:2'2                } search range end (exclusive)
 ; NOT-VV-NEXT:              4: 
 ; NOT-VV-NEXT:eof:2            ^
 ;    NOT-NEXT:>>>>>>
@@ -404,13 +424,14 @@
 
 ;         NOT2:<<<<<<
 ;    NOT2-NEXT:              1: hello 
-; NOT2-VV-NEXT:not:1            X~~~~~
+; NOT2-VV-NEXT:not:1'0         {        search range start (exclusive)
+;    NOT2-NEXT:not:2'0         {        search range start (exclusive)
 ;    NOT2-NEXT:              2: world 
-; NOT2-VV-NEXT:not:1            ~~~~~~
-;    NOT2-NEXT:not:2            !~~~~  error: no match expected
+;    NOT2-NEXT:not:2'1          !~~~~   error: no match expected
 ;    NOT2-NEXT:              3: again 
 ;  NOT2-V-NEXT:check:3            ^~~
-; NOT2-VV-NEXT:not:1            ~~
+; NOT2-VV-NEXT:not:1'1            }     search range end (exclusive)
+;    NOT2-NEXT:not:2'2            }     search range end (exclusive)
 ;    NOT2-NEXT:>>>>>>
 ;     NOT2-NOT:{{.}}
 
@@ -440,14 +461,15 @@
 ;      DAG-VV:<<<<<<
 ; DAG-VV-NEXT:              1: abc 
 ; DAG-VV-NEXT:dag:2            ^~~
-; DAG-VV-NEXT:dag:3'0          !~~  discard: overlaps earlier match
+; DAG-VV-NEXT:dag:3'0          !~~   discard: overlaps earlier match
 ; DAG-VV-NEXT:              2: def 
 ; DAG-VV-NEXT:dag:1            ^~~
-; DAG-VV-NEXT:dag:4'0          !~~  discard: overlaps earlier match
-; DAG-VV-NEXT:dag:4'1             X error: no match found
+; DAG-VV-NEXT:dag:4'0          !~~   discard: overlaps earlier match
+; DAG-VV-NEXT:dag:4'1            {   search range start (exclusive)
+; DAG-VV-NEXT:dag:4'2                error: no match found in search range
 ; DAG-VV-NEXT:              3: abc 
 ; DAG-VV-NEXT:dag:3'1          ^~~
-; DAG-VV-NEXT:dag:4'1          ~~~~
+; DAG-VV-NEXT:dag:4'3              } search range end (exclusive)
 ; DAG-VV-NEXT:>>>>>>
 ;  DAG-VV-NOT:{{.}}
 
@@ -456,19 +478,21 @@
 ; DAG-VQ-NEXT:dag:2            ^~~
 ; DAG-VQ-NEXT:              2: def 
 ; DAG-VQ-NEXT:dag:1            ^~~
-; DAG-VQ-NEXT:dag:4               X error: no match found
+; DAG-VQ-NEXT:dag:4'0            {   search range start (exclusive)
+; DAG-VQ-NEXT:dag:4'1                error: no match found in search range
 ; DAG-VQ-NEXT:              3: abc 
 ; DAG-VQ-NEXT:dag:3            ^~~
-; DAG-VQ-NEXT:dag:4            ~~~~
+; DAG-VQ-NEXT:dag:4'2              } search range end (exclusive)
 ; DAG-VQ-NEXT:>>>>>>
 ;  DAG-VQ-NOT:{{.}}
 
 ;      DAG-Q:<<<<<<
 ; DAG-Q-NEXT:              1: abc 
 ; DAG-Q-NEXT:              2: def 
-; DAG-Q-NEXT:dag:4               X error: no match found
+; DAG-Q-NEXT:dag:4'0            {   search range start (exclusive)
+; DAG-Q-NEXT:dag:4'1                error: no match found in search range
 ; DAG-Q-NEXT:              3: abc 
-; DAG-Q-NEXT:dag:4            ~~~~
+; DAG-Q-NEXT:dag:4'2              } search range end (exclusive)
 ; DAG-Q-NEXT:>>>>>>
 ;  DAG-Q-NOT:{{.}}
 
@@ -497,13 +521,14 @@
 ; DAG1L-VV-NEXT:              1: abc def abc def 
 ; DAG1L-VV-NEXT:dag:1                ^~~
 ; DAG1L-VV-NEXT:dag:2             ^~
-; DAG1L-VV-NEXT:dag:3'0          !~~              discard: overlaps earlier match
+; DAG1L-VV-NEXT:dag:3'0          !~~               discard: overlaps earlier match
 ; DAG1L-VV-NEXT:dag:3'1                  ^~~
-; DAG1L-VV-NEXT:dag:4'0              !~           discard: overlaps earlier match
+; DAG1L-VV-NEXT:dag:4'0              !~            discard: overlaps earlier match
 ; DAG1L-VV-NEXT:dag:4'1                      ^~
-; DAG1L-VV-NEXT:dag:5'0              !~~          discard: overlaps earlier match
-; DAG1L-VV-NEXT:dag:5'1                      !~~  discard: overlaps earlier match
-; DAG1L-VV-NEXT:dag:5'2                        X~ error: no match found
+; DAG1L-VV-NEXT:dag:5'0              !~~           discard: overlaps earlier match
+; DAG1L-VV-NEXT:dag:5'1                      !~~   discard: overlaps earlier match
+; DAG1L-VV-NEXT:dag:5'2                       {  } search range (exclusive bounds)
+; DAG1L-VV-NEXT:dag:5'3                            error: no match found in search range
 ; DAG1L-VV-NEXT:>>>>>>
 ;  DAG1L-VV-NOT:{{.}}
 
@@ -513,13 +538,15 @@
 ; DAG1L-VQ-NEXT:dag:2             ^~
 ; DAG1L-VQ-NEXT:dag:3                    ^~~
 ; DAG1L-VQ-NEXT:dag:4                        ^~
-; DAG1L-VQ-NEXT:dag:5                          X~ error: no match found
+; DAG1L-VQ-NEXT:dag:5'0                       {  } search range (exclusive bounds)
+; DAG1L-VQ-NEXT:dag:5'1                            error: no match found in search range
 ; DAG1L-VQ-NEXT:>>>>>>
 ;  DAG1L-VQ-NOT:{{.}}
 
 ;      DAG1L-Q:<<<<<<
 ; DAG1L-Q-NEXT:              1: abc def abc def 
-; DAG1L-Q-NEXT:dag:5                          X~ error: no match found
+; DAG1L-Q-NEXT:dag:5'0                       {  } search range (exclusive bounds)
+; DAG1L-Q-NEXT:dag:5'1                            error: no match found in search range
 ; DAG1L-Q-NEXT:>>>>>>
 ;  DAG1L-Q-NOT:{{.}}
 
@@ -585,31 +612,34 @@
 ;  LAB-V-NEXT:              2: labelA 
 ;  LAB-V-NEXT:label:2'0        ^~~~~~
 ;  LAB-V-NEXT:label:2'1        ^~~~~~
-;  LAB-V-NEXT:check:3                X error: no match found
+;  LAB-V-NEXT:check:3'0             {   search range start (exclusive)
+;  LAB-V-NEXT:check:3'1                 error: no match found in search range
 ;  LAB-V-NEXT:              3: textA 
-;  LAB-V-NEXT:check:3          ~~~~~~
 ;  LAB-V-NEXT:              4: labelB 
 ;  LAB-V-NEXT:label:4          ^~~~~~
-;  LAB-V-NEXT:check:3          ~~~~~~
+;  LAB-V-NEXT:check:3'2              }  search range end (exclusive)
 ;  LAB-V-NEXT:              5: textB 
 ;  LAB-V-NEXT:              6: labelC 
 ;  LAB-V-NEXT:label:6'0        ^~~~~~
 ;  LAB-V-NEXT:check:5          ^~~~~~
-;  LAB-V-NEXT:label:6'1              X error: no match found
-; LAB-VV-NEXT:not:7                  X
+;  LAB-V-NEXT:label:6'1             {}  search range (exclusive bounds)
+;  LAB-V-NEXT:label:6'2                 error: no match found in search range
+; LAB-VV-NEXT:not:7'0               {   search range start (exclusive)
 ;  LAB-V-NEXT:              7: textC 
-; LAB-VV-NEXT:not:7            ~~~~~~
+; LAB-VV-NEXT:not:7'1                } search range end (exclusive)
 ;  LAB-V-NEXT:              8: labelD 
 ;  LAB-V-NEXT:label:8'0        ^~~~~~
 ;  LAB-V-NEXT:label:8'1        ^~~~~~
+;  LAB-V-NEXT:not:9'0               {   search range start (exclusive)
 ;  LAB-V-NEXT:              9: textD 
-;  LAB-V-NEXT:not:9            !~~~~  error: no match expected
+;  LAB-V-NEXT:not:9'1          !~~~~   error: no match expected
+;  LAB-V-NEXT:not:9'2                } search range end (exclusive)
 ;  LAB-V-NEXT:             10: labelE 
 ;  LAB-V-NEXT:label:10'0       ^~~~~~
 ;  LAB-V-NEXT:label:10'1       ^~~~~~
-; LAB-VV-NEXT:not:11                 X
+; LAB-VV-NEXT:not:11'0              {   search range start (exclusive)
 ;  LAB-V-NEXT:             11: textE 
-; LAB-VV-NEXT:not:11           ~~~~~~
+; LAB-VV-NEXT:not:11'1               } search range end (exclusive)
 ;  LAB-V-NEXT:             12: labelF 
 ;  LAB-V-NEXT:label:12'0       ^~~~~~
 ;  LAB-V-NEXT:label:12'1       ^~~~~~
@@ -619,18 +649,21 @@
 ;      LAB-Q:<<<<<<
 ; LAB-Q-NEXT:              1: text 
 ; LAB-Q-NEXT:              2: labelA 
-; LAB-Q-NEXT:check:3                X error: no match found
+; LAB-Q-NEXT:check:3'0             {   search range start (exclusive)
+; LAB-Q-NEXT:check:3'1                 error: no match found in search range
 ; LAB-Q-NEXT:              3: textA 
-; LAB-Q-NEXT:check:3          ~~~~~~
 ; LAB-Q-NEXT:              4: labelB 
-; LAB-Q-NEXT:check:3          ~~~~~~
+; LAB-Q-NEXT:check:3'2              }  search range end (exclusive)
 ; LAB-Q-NEXT:              5: textB 
 ; LAB-Q-NEXT:              6: labelC 
-; LAB-Q-NEXT:label:6                X error: no match found
+; LAB-Q-NEXT:label:6'0             {}  search range (exclusive bounds)
+; LAB-Q-NEXT:label:6'1                 error: no match found in search range
 ; LAB-Q-NEXT:              7: textC 
 ; LAB-Q-NEXT:              8: labelD 
+; LAB-Q-NEXT:not:9'0               {   search range start (exclusive)
 ; LAB-Q-NEXT:              9: textD 
-; LAB-Q-NEXT:not:9            !~~~~  error: no match expected
+; LAB-Q-NEXT:not:9'1          !~~~~   error: no match expected
+; LAB-Q-NEXT:not:9'2                } search range end (exclusive)
 ; LAB-Q-NEXT:             10: labelE 
 ; LAB-Q-NEXT:             11: textE 
 ; LAB-Q-NEXT:             12: labelF 
@@ -668,17 +701,18 @@
 ;      IMPNOT-VV:<<<<<<
 ; IMPNOT-VV-NEXT:              1: hello world again! 
 ; IMPNOT-VV-NEXT:check:1          ^~~
-; IMPNOT-VV-NEXT:not:imp1'0       X
-; IMPNOT-VV-NEXT:not:imp2'0       X
-; IMPNOT-VV-NEXT:not:imp3'0       X
+; IMPNOT-VV-NEXT:not:imp1'0      {}                    search range (exclusive bounds)
+; IMPNOT-VV-NEXT:not:imp2'0      {}                    search range (exclusive bounds)
+; IMPNOT-VV-NEXT:not:imp3'0      {}                    search range (exclusive bounds)
 ; IMPNOT-VV-NEXT:check:2                ^~~
-; IMPNOT-VV-NEXT:not:imp1'1          X~~
-; IMPNOT-VV-NEXT:not:imp2'1          X~~
-; IMPNOT-VV-NEXT:not:imp3'1          X~~
+; IMPNOT-VV-NEXT:not:imp1'1         {   }              search range (exclusive bounds)
+; IMPNOT-VV-NEXT:not:imp2'1         {   }              search range (exclusive bounds)
+; IMPNOT-VV-NEXT:not:imp3'1         {   }              search range (exclusive bounds)
 ; IMPNOT-VV-NEXT:check:3                           ^
-; IMPNOT-VV-NEXT:not:imp1'2                X~~~~~~~
-; IMPNOT-VV-NEXT:not:imp2'2                X~~~~~~~
-; IMPNOT-VV-NEXT:not:imp3'2                   !~~~~   error: no match expected
+; IMPNOT-VV-NEXT:not:imp1'2               {        }   search range (exclusive bounds)
+; IMPNOT-VV-NEXT:not:imp2'2               {        }   search range (exclusive bounds)
+; IMPNOT-VV-NEXT:not:imp3'2               {        }   search range (exclusive bounds)
+; IMPNOT-VV-NEXT:not:imp3'3                   !~~~~    error: no match expected
 ; IMPNOT-VV-NEXT:>>>>>>
 ;  IMPNOT-VV-NOT:{{.}}
 
@@ -687,13 +721,15 @@
 ; IMPNOT-VQ-NEXT:check:1          ^~~
 ; IMPNOT-VQ-NEXT:check:2                ^~~
 ; IMPNOT-VQ-NEXT:check:3                           ^
-; IMPNOT-VQ-NEXT:not:imp3                     !~~~~   error: no match expected
+; IMPNOT-VQ-NEXT:not:imp3'0               {        }   search range (exclusive bounds)
+; IMPNOT-VQ-NEXT:not:imp3'1                   !~~~~    error: no match expected
 ; IMPNOT-VQ-NEXT:>>>>>>
 ;  IMPNOT-VQ-NOT:{{.}}
 
 ;      IMPNOT-Q:<<<<<<
 ; IMPNOT-Q-NEXT:              1: hello world again! 
-; IMPNOT-Q-NEXT:not:imp3                     !~~~~   error: no match expected
+; IMPNOT-Q-NEXT:not:imp3'0               {        }   search range (exclusive bounds)
+; IMPNOT-Q-NEXT:not:imp3'1                   !~~~~    error: no match expected
 ; IMPNOT-Q-NEXT:>>>>>>
 ;  IMPNOT-Q-NOT:{{.}}
 
@@ -723,15 +759,16 @@
 ;        SUBST-POS:<<<<<<
 ;   SUBST-POS-NEXT:              1: def-match1 def-match2 
 ; SUBST-POS-V-NEXT:check:1'0        ^~~~~~~~~~~~~~~~~~~~~
-; SUBST-POS-V-NEXT:check:1'1                               with "DEF_MATCH1" equal to "def-match1"
-; SUBST-POS-V-NEXT:check:1'2                               with "DEF_MATCH2" equal to "def-match2"
-;   SUBST-POS-NEXT:check:2'0                             X error: match failed for invalid pattern
-;   SUBST-POS-NEXT:check:2'1                               undefined variable: UNDEF
-;   SUBST-POS-NEXT:check:2'2                               with "DEF_MATCH1" equal to "def-match1"
-;   SUBST-POS-NEXT:check:2'3                               with "DEF_NOMATCH" equal to "foobar"
+; SUBST-POS-V-NEXT:check:1'1                                with "DEF_MATCH1" equal to "def-match1"
+; SUBST-POS-V-NEXT:check:1'2                                with "DEF_MATCH2" equal to "def-match2"
+;   SUBST-POS-NEXT:check:2'0                            {   search range start (exclusive)
+;   SUBST-POS-NEXT:check:2'1                                error: match failed for invalid pattern
+;   SUBST-POS-NEXT:check:2'2                                undefined variable: UNDEF
+;   SUBST-POS-NEXT:check:2'3                                with "DEF_MATCH1" equal to "def-match1"
+;   SUBST-POS-NEXT:check:2'4                                with "DEF_NOMATCH" equal to "foobar"
 ;   SUBST-POS-NEXT:              2: def-match1 def-nomatch 
-;   SUBST-POS-NEXT:check:2'0        ~~~~~~~~~~~~~~~~~~~~~~~
-;   SUBST-POS-NEXT:check:2'4                 ?              possible intended match
+;   SUBST-POS-NEXT:check:2'5                 ?               possible intended match
+;   SUBST-POS-NEXT:check:2'6                               } search range end (exclusive)
 ;   SUBST-POS-NEXT:>>>>>>
 
 ;--------------------------------------------------
@@ -760,15 +797,18 @@
 
 ;        SUBST-NEG:<<<<<<
 ;   SUBST-NEG-NEXT:              1: def-match1 def-nomatch 
-;   SUBST-NEG-NEXT:not:1'0          X~~~~~~~~~~~~~~~~~~~~~~ error: match failed for invalid pattern
-;   SUBST-NEG-NEXT:not:1'1                                  undefined variable: UNDEF
-;   SUBST-NEG-NEXT:not:1'2                                  with "DEF_MATCH1" equal to "def-match1"
-;   SUBST-NEG-NEXT:not:1'3                                  with "DEF_NOMATCH" equal to "foobar"
+;   SUBST-NEG-NEXT:not:1'0         {                         search range start (exclusive)
+;   SUBST-NEG-NEXT:not:1'1                                   error: match failed for invalid pattern
+;   SUBST-NEG-NEXT:not:1'2                                   undefined variable: UNDEF
+;   SUBST-NEG-NEXT:not:1'3                                   with "DEF_MATCH1" equal to "def-match1"
+;   SUBST-NEG-NEXT:not:1'4                                   with "DEF_NOMATCH" equal to "foobar"
+;   SUBST-NEG-NEXT:not:2'0         {                         search range start (exclusive)
 ;   SUBST-NEG-NEXT:              2: def-match1 def-match2 
-;   SUBST-NEG-NEXT:not:1'0          ~~~~~~~~~~~~~~~~~~~~~~
-;   SUBST-NEG-NEXT:not:2'0          !~~~~~~~~~~~~~~~~~~~~  error: no match expected
-;   SUBST-NEG-NEXT:not:2'1                                 with "DEF_MATCH1" equal to "def-match1"
-;   SUBST-NEG-NEXT:not:2'2                                 with "DEF_MATCH2" equal to "def-match2"
+;   SUBST-NEG-NEXT:not:1'5                                } search range end (exclusive)
+;   SUBST-NEG-NEXT:not:2'1          !~~~~~~~~~~~~~~~~~~~~   error: no match expected
+;   SUBST-NEG-NEXT:not:2'2                                  with "DEF_MATCH1" equal to "def-match1"
+;   SUBST-NEG-NEXT:not:2'3                                  with "DEF_MATCH2" equal to "def-match2"
+;   SUBST-NEG-NEXT:not:2'4                                } search range end (exclusive)
 ;   SUBST-NEG-NEXT:              3: END 
 ; SUBST-NEG-V-NEXT:check:3          ^~~
 ;   SUBST-NEG-NEXT:>>>>>>
@@ -807,23 +847,25 @@
 ;        CAPTURE-NEG:<<<<<<
 ;   CAPTURE-NEG-NEXT:              1: strvar: foo 
 ; CAPTURE-NEG-V-NEXT:check:1'0        ^~~~~~~~~~~
-  CAPTURE-NEG-V-NEXT:check:1'1                ^~~  captured var "STRVAR"
+  CAPTURE-NEG-V-NEXT:check:1'1                ^~~   captured var "STRVAR"
 ;   CAPTURE-NEG-NEXT:              2: numvar no expr: 51 
 ; CAPTURE-NEG-V-NEXT:check:2'0        ^~~~~~~~~~~~~~~~~~
-; CAPTURE-NEG-V-NEXT:check:2'1                        ^~  captured var "NUMVAR_NO_EXPR"
+; CAPTURE-NEG-V-NEXT:check:2'1                        ^~   captured var "NUMVAR_NO_EXPR"
 ;   CAPTURE-NEG-NEXT:              3: numvar expr: -49 
 ; CAPTURE-NEG-V-NEXT:check:3'0        ^~~~~~~~~~~~~~~~
-; CAPTURE-NEG-V-NEXT:check:3'1                          with "%d,NUMVAR_EXPR:2-NUMVAR_NO_EXPR" equal to "-49"
-; CAPTURE-NEG-V-NEXT:check:3'2                     ^~~  captured var "NUMVAR_EXPR"
+; CAPTURE-NEG-V-NEXT:check:3'1                           with "%d,NUMVAR_EXPR:2-NUMVAR_NO_EXPR" equal to "-49"
+; CAPTURE-NEG-V-NEXT:check:3'2                     ^~~   captured var "NUMVAR_EXPR"
 ;   CAPTURE-NEG-NEXT:              4: many: foo 100 8 bar 
 ; CAPTURE-NEG-V-NEXT:check:4'0        ^~~~~~~~~~~~~~~~~~~
-; CAPTURE-NEG-V-NEXT:check:4'1              ^~~            captured var "VAR1"
-; CAPTURE-NEG-V-NEXT:check:4'2                  ^~~        captured var "VAR3"
-; CAPTURE-NEG-V-NEXT:check:4'3                      ^      captured var "VAR2"
-; CAPTURE-NEG-V-NEXT:check:4'4                        ^~~  captured var "VAR4"
+; CAPTURE-NEG-V-NEXT:check:4'1              ^~~             captured var "VAR1"
+; CAPTURE-NEG-V-NEXT:check:4'2                  ^~~         captured var "VAR3"
+; CAPTURE-NEG-V-NEXT:check:4'3                      ^       captured var "VAR2"
+; CAPTURE-NEG-V-NEXT:check:4'4                        ^~~   captured var "VAR4"
+;   CAPTURE-NEG-NEXT:not:5'0                            {   search range start (exclusive)
 ;   CAPTURE-NEG-NEXT:              5: var in neg match: foo 
-;   CAPTURE-NEG-NEXT:not:5'0          !~~~~~~~~~~~~~~~~~~~~  error: no match expected
-;   CAPTURE-NEG-NEXT:not:5'1                            !~~  captured var "VAR"
+;   CAPTURE-NEG-NEXT:not:5'1          !~~~~~~~~~~~~~~~~~~~~   error: no match expected
+;   CAPTURE-NEG-NEXT:not:5'2                            !~~   captured var "VAR"
+;   CAPTURE-NEG-NEXT:not:5'3                                } search range end (exclusive)
 ;   CAPTURE-NEG-NEXT:              6: END 
 ; CAPTURE-NEG-V-NEXT:check:6          ^~~
 ;   CAPTURE-NEG-NEXT:>>>>>>
@@ -857,17 +899,13 @@
 ; Verbose diagnostics are suppressed but not errors.
 ; SUBST_NEXT:{{.*}}error:{{.*}}
 
-;      SUBST_NEXT-V:<<<<<<
-; SUBST_NEXT-V-NEXT:              1: pre var 
+;        SUBST_NEXT:<<<<<<
+;   SUBST_NEXT-NEXT:              1: pre var 
 ; SUBST_NEXT-V-NEXT:check:1          ^~~
-; SUBST_NEXT-V-NEXT:next:2'0             !~~  error: match on wrong line
-; SUBST_NEXT-V-NEXT:next:2'1                  with "VAR" equal to "var"
-; SUBST_NEXT-V-NEXT:>>>>>>
-
-;      SUBST_NEXT-Q:<<<<<<
-; SUBST_NEXT-Q-NEXT:              1: pre var 
-; SUBST_NEXT-Q-NEXT:next:2               !~~  error: match on wrong line
-; SUBST_NEXT-Q-NEXT:>>>>>>
+;   SUBST_NEXT-NEXT:next:2'0           {     } search range (exclusive bounds)
+;   SUBST_NEXT-NEXT:next:2'1             !~~   error: match on wrong line
+; SUBST_NEXT-V-NEXT:next:2'2                   with "VAR" equal to "var"
+;   SUBST_NEXT-NEXT:>>>>>>
 
 ;- - - - - - - - - - - - - - - - - - - - - - - - -
 ; CHECK-SAME.
@@ -892,15 +930,19 @@
 ;      SUBST_SAME-V:<<<<<<
 ; SUBST_SAME-V-NEXT:              1: pre 
 ; SUBST_SAME-V-NEXT:check:1          ^~~
+; SUBST_SAME-V-NEXT:same:2'0           {   search range start (exclusive)
 ; SUBST_SAME-V-NEXT:              2: var 
-; SUBST_SAME-V-NEXT:same:2'0         !~~  error: match on wrong line
-; SUBST_SAME-V-NEXT:same:2'1              with "VAR" equal to "var"
+; SUBST_SAME-V-NEXT:same:2'1         !~~   error: match on wrong line
+; SUBST_SAME-V-NEXT:same:2'2               with "VAR" equal to "var"
+; SUBST_SAME-V-NEXT:same:2'3             } search range end (exclusive)
 ; SUBST_SAME-V-NEXT:>>>>>>
 
 ;      SUBST_SAME-Q:<<<<<<
 ; SUBST_SAME-Q-NEXT:              1: pre 
+; SUBST_SAME-Q-NEXT:same:2'0           {   search range start (exclusive)
 ; SUBST_SAME-Q-NEXT:              2: var 
-; SUBST_SAME-Q-NEXT:same:2           !~~  error: match on wrong line
+; SUBST_SAME-Q-NEXT:same:2'1         !~~   error: match on wrong line
+; SUBST_SAME-Q-NEXT:same:2'2             } search range end (exclusive)
 ; SUBST_SAME-Q-NEXT:>>>>>>
 
 ;- - - - - - - - - - - - - - - - - - - - - - - - -
@@ -925,11 +967,11 @@
 ;      SUBST_DAG-VV:<<<<<<
 ; SUBST_DAG-VV-NEXT:              1: var 
 ; SUBST_DAG-VV-NEXT:dag:1            ^~~
-; SUBST_DAG-VV-NEXT:dag:2'0          !~~  discard: overlaps earlier match
-; SUBST_DAG-VV-NEXT:dag:2'1               with "VAR" equal to "var"
+; SUBST_DAG-VV-NEXT:dag:2'0          !~~   discard: overlaps earlier match
+; SUBST_DAG-VV-NEXT:dag:2'1                with "VAR" equal to "var"
 ; SUBST_DAG-VV-NEXT:              2: var 
 ; SUBST_DAG-VV-NEXT:dag:2'2          ^~~
-; SUBST_DAG-VV-NEXT:dag:2'3               with "VAR" equal to "var"
+; SUBST_DAG-VV-NEXT:dag:2'3                with "VAR" equal to "var"
 ; SUBST_DAG-VV-NEXT:              3: END 
 ; SUBST_DAG-VV-NEXT:check:3          ^~~
 ; SUBST_DAG-VV-NEXT:>>>>>>
@@ -939,7 +981,7 @@
 ; SUBST_DAG-VQ-NEXT:dag:1            ^~~
 ; SUBST_DAG-VQ-NEXT:              2: var 
 ; SUBST_DAG-VQ-NEXT:dag:2'0          ^~~
-; SUBST_DAG-VQ-NEXT:dag:2'1               with "VAR" equal to "var"
+; SUBST_DAG-VQ-NEXT:dag:2'1                with "VAR" equal to "var"
 ; SUBST_DAG-VQ-NEXT:              3: END 
 ; SUBST_DAG-VQ-NEXT:check:3          ^~~
 ; SUBST_DAG-VQ-NEXT:>>>>>>
@@ -949,3 +991,212 @@
 ; SUBST_DAG-Q-NEXT:              2: var 
 ; SUBST_DAG-Q-NEXT:              3: END 
 ; SUBST_DAG-Q-NEXT:>>>>>>
+
+;--------------------------------------------------
+; Check multiline marker where start/end columns vary across lines.
+;--------------------------------------------------
+
+; RUN: echo '<start' >  %t.in
+; RUN: echo ''       >> %t.in
+; RUN: echo '1'      >> %t.in
+; RUN: echo '12'     >> %t.in
+; RUN: echo 'end>'   >> %t.in
+
+; RUN: echo 'CHECK: start{{(.|[[:space:]])*}}end' >  %t.chk
+
+; REDEFINE: %{pre} = MULTILINE-MARKER
+; RUN: %{run-vv}
+; RUN: %{run-v}
+; RUN: %{run}
+
+;        MULTILINE-MARKER:<<<<<<
+;   MULTILINE-MARKER-NEXT:              1: <start 
+; MULTILINE-MARKER-V-NEXT:check:1           ^~~~~~
+;   MULTILINE-MARKER-NEXT:              2:  
+; MULTILINE-MARKER-V-NEXT:check:1          ~
+;   MULTILINE-MARKER-NEXT:              3: 1 
+; MULTILINE-MARKER-V-NEXT:check:1          ~~
+;   MULTILINE-MARKER-NEXT:              4: 12 
+; MULTILINE-MARKER-V-NEXT:check:1          ~~~
+;   MULTILINE-MARKER-NEXT:              5: end> 
+; MULTILINE-MARKER-V-NEXT:check:1          ~~~
+;   MULTILINE-MARKER-NEXT:>>>>>>
+
+;--------------------------------------------------
+; Check empty and single-character match ranges.
+;--------------------------------------------------
+
+; RUN: echo '11' >  %t.in
+
+; RUN: echo 'empty  at    bof: CHECK: {{0?}}'           >  %t.chk
+; RUN: echo 'single at    bof: CHECK: {{1?}}'           >> %t.chk
+; RUN: echo 'empty  in middle: CHECK: {{0?}}'           >> %t.chk
+; RUN: echo 'single in middle: CHECK: {{1?}}'           >> %t.chk
+; RUN: echo 'single at    eof: CHECK: {{[[:space:]]?}}' >> %t.chk
+; RUN: echo 'empty  at    eof: CHECK: {{0?}}'           >> %t.chk
+
+; REDEFINE: %{pre} = EMPTY-SINGLE-MATCH
+; RUN: %{run-vv}
+; RUN: %{run-v}
+; RUN: %{run}
+
+;        EMPTY-SINGLE-MATCH:<<<<<<
+;   EMPTY-SINGLE-MATCH-NEXT:              1: 11 
+; EMPTY-SINGLE-MATCH-V-NEXT:check:1          ^
+; EMPTY-SINGLE-MATCH-V-NEXT:check:2          ^
+; EMPTY-SINGLE-MATCH-V-NEXT:check:3           ^
+; EMPTY-SINGLE-MATCH-V-NEXT:check:4           ^
+; EMPTY-SINGLE-MATCH-V-NEXT:check:5            ^
+; EMPTY-SINGLE-MATCH-V-NEXT:              2: 
+; EMPTY-SINGLE-MATCH-V-NEXT:check:6          ^
+;   EMPTY-SINGLE-MATCH-NEXT:>>>>>>
+
+;--------------------------------------------------
+; Check search range for empty file and thus empty search range at start/end of
+; file.
+;--------------------------------------------------
+
+; RUN: echo 'CHECK-NOT: foo' > %t.chk
+; REDEFINE: %{opts} = -input-file=/dev/null -allow-empty
+
+; REDEFINE: %{pre} = NEG-SEARCH-EMPTY-FILE
+; RUN: %{run-vv}
+; RUN: %{run-v}
+; RUN: %{run}
+
+;         NEG-SEARCH-EMPTY-FILE:<<<<<<
+; NEG-SEARCH-EMPTY-FILE-VV-NEXT:              1: 
+; NEG-SEARCH-EMPTY-FILE-VV-NEXT:eof:1            ^
+; NEG-SEARCH-EMPTY-FILE-VV-NEXT:not:1           {} search range (exclusive bounds)
+;    NEG-SEARCH-EMPTY-FILE-NEXT:>>>>>>
+
+; RUN: echo 'CHECK: foo' > %t.chk
+
+; REDEFINE: %{pre} = POS-SEARCH-EMPTY-FILE
+; RUN: not %{run-vv}
+; RUN: not %{run-v}
+; RUN: not %{run}
+
+; Verbose diagnostics are suppressed but not errors.
+; POS-SEARCH-EMPTY-FILE:{{.*}}error:{{.*}}
+
+;      POS-SEARCH-EMPTY-FILE:<<<<<<
+; POS-SEARCH-EMPTY-FILE-NEXT:              1: 
+; POS-SEARCH-EMPTY-FILE-NEXT:check:1'0       {} search range (exclusive bounds)
+; POS-SEARCH-EMPTY-FILE-NEXT:check:1'1          error: no match found in search range
+; POS-SEARCH-EMPTY-FILE-NEXT:>>>>>>
+
+; REDEFINE: %{opts} =
+
+;--------------------------------------------------
+; Check non-empty search range at start/end of file.
+;--------------------------------------------------
+
+; RUN: echo 'one line' > %t.in
+
+; RUN: echo 'CHECK-NOT: foo' > %t.chk
+
+; REDEFINE: %{pre} = SEARCH-BOF-EOF-ONE-LINE
+; RUN: %{run-vv}
+; RUN: %{run-v}
+; RUN: %{run}
+
+;         SEARCH-BOF-EOF-ONE-LINE:<<<<<<
+;    SEARCH-BOF-EOF-ONE-LINE-NEXT:              1: one line 
+; SEARCH-BOF-EOF-ONE-LINE-VV-NEXT:not:1           {         } search range (exclusive bounds)
+; SEARCH-BOF-EOF-ONE-LINE-VV-NEXT:              2: 
+; SEARCH-BOF-EOF-ONE-LINE-VV-NEXT:eof:1            ^
+;    SEARCH-BOF-EOF-ONE-LINE-NEXT:>>>>>>
+
+; RUN: echo 'line 1' >  %t.in
+; RUN: echo 'line 2' >> %t.in
+
+; RUN: echo 'CHECK: foo' > %t.chk
+
+; REDEFINE: %{pre} = SEARCH-BOF-EOF-MULTI-LINE
+; RUN: not %{run-vv}
+; RUN: not %{run-v}
+; RUN: not %{run}
+
+; Verbose diagnostics are suppressed but not errors.
+; SEARCH-BOF-EOF-MULTI-LINE:{{.*}}error:{{.*}}
+
+;      SEARCH-BOF-EOF-MULTI-LINE:<<<<<<
+; SEARCH-BOF-EOF-MULTI-LINE-NEXT:              1: line 1 
+; SEARCH-BOF-EOF-MULTI-LINE-NEXT:check:1'0       {         search range start (exclusive)
+; SEARCH-BOF-EOF-MULTI-LINE-NEXT:check:1'1                 error: no match found in search range
+; SEARCH-BOF-EOF-MULTI-LINE-NEXT:              2: line 2 
+; SEARCH-BOF-EOF-MULTI-LINE-NEXT:check:1'2               } search range end (exclusive)
+; SEARCH-BOF-EOF-MULTI-LINE-NEXT:>>>>>>
+
+;--------------------------------------------------
+; Check search ranges at line boundaries but not file boundaries.  Also check
+; empty search ranges (for non-empty file) and one-character search ranges.
+;--------------------------------------------------
+
+; RUN: echo 'start'      >  %t.in
+; RUN: echo 'empty'      >> %t.in
+; RUN: echo 'next'       >> %t.in
+; RUN: echo 'one char'   >> %t.in
+; RUN: echo 'next'       >> %t.in
+; RUN: echo 'one line'   >> %t.in
+; RUN: echo 'next'       >> %t.in
+; RUN: echo 'multi'      >> %t.in
+; RUN: echo 'line'       >> %t.in
+; RUN: echo 'next'       >> %t.in
+; RUN: echo 'off by one' >> %t.in
+; RUN: echo ' end'       >> %t.in
+
+; RUN: echo ' 1: CHECK: start{{[[:space:]]}}'               >  %t.chk
+; RUN: echo ' 2: CHECK-NOT: foo'                            >> %t.chk
+; RUN: echo ' 3: CHECK: empty'                              >> %t.chk
+; RUN: echo ' 4: CHECK-NOT: foo'                            >> %t.chk
+; RUN: echo ' 5: CHECK: {{[[:space:]]}}next{{[[:space:]]}}' >> %t.chk
+; RUN: echo ' 6: CHECK-NOT: foo'                            >> %t.chk
+; RUN: echo ' 7: CHECK: ne char'                            >> %t.chk
+; RUN: echo ' 8: CHECK-NOT: foo'                            >> %t.chk
+; RUN: echo ' 9: CHECK: next{{[[:space:]]}}'                >> %t.chk
+; RUN: echo '10: CHECK-NOT: foo'                            >> %t.chk
+; RUN: echo '11: CHECK: next{{[[:space:]]}}'                >> %t.chk
+; RUN: echo '12: CHECK-NOT: foo'                            >> %t.chk
+; RUN: echo '13: CHECK: next'                               >> %t.chk
+; RUN: echo '14: CHECK-NOT: foo'                            >> %t.chk
+; RUN: echo '15: CHECK: end'                                >> %t.chk
+
+; REDEFINE: %{pre} = SEARCH-LINE-BOUNDS
+; RUN: %{run-vv}
+; RUN: %{run-v}
+; RUN: %{run}
+
+;         SEARCH-LINE-BOUNDS:<<<<<<
+;    SEARCH-LINE-BOUNDS-NEXT:              1: start 
+;  SEARCH-LINE-BOUNDS-V-NEXT:check:1          ^~~~~~
+;    SEARCH-LINE-BOUNDS-NEXT:              2: empty 
+;  SEARCH-LINE-BOUNDS-V-NEXT:check:3          ^~~~~
+; SEARCH-LINE-BOUNDS-VV-NEXT:not:2           {}       search range (exclusive bounds)
+;  SEARCH-LINE-BOUNDS-V-NEXT:check:5               ^
+; SEARCH-LINE-BOUNDS-VV-NEXT:not:4                {}  search range (exclusive bounds)
+;    SEARCH-LINE-BOUNDS-NEXT:              3: next 
+;  SEARCH-LINE-BOUNDS-V-NEXT:check:5          ~~~~~
+;    SEARCH-LINE-BOUNDS-NEXT:              4: one char 
+;  SEARCH-LINE-BOUNDS-V-NEXT:check:7           ^~~~~~~
+; SEARCH-LINE-BOUNDS-VV-NEXT:not:6           { }         search range (exclusive bounds)
+; SEARCH-LINE-BOUNDS-VV-NEXT:not:8                   { } search range (exclusive bounds)
+;    SEARCH-LINE-BOUNDS-NEXT:              5: next 
+;  SEARCH-LINE-BOUNDS-V-NEXT:check:9          ^~~~~
+;    SEARCH-LINE-BOUNDS-NEXT:              6: one line 
+; SEARCH-LINE-BOUNDS-VV-NEXT:not:10          {         } search range (exclusive bounds)
+;    SEARCH-LINE-BOUNDS-NEXT:              7: next 
+;  SEARCH-LINE-BOUNDS-V-NEXT:check:11         ^~~~~
+;    SEARCH-LINE-BOUNDS-NEXT:              8: multi 
+; SEARCH-LINE-BOUNDS-VV-NEXT:not:12'0        {        search range start (exclusive)
+;    SEARCH-LINE-BOUNDS-NEXT:              9: line 
+; SEARCH-LINE-BOUNDS-VV-NEXT:not:12'1              } search range end (exclusive)
+;    SEARCH-LINE-BOUNDS-NEXT:             10: next 
+;  SEARCH-LINE-BOUNDS-V-NEXT:check:13         ^~~~
+; SEARCH-LINE-BOUNDS-VV-NEXT:not:14'0            {   search range start (exclusive)
+;    SEARCH-LINE-BOUNDS-NEXT:             11: off by one 
+;    SEARCH-LINE-BOUNDS-NEXT:             12:  end 
+;  SEARCH-LINE-BOUNDS-V-NEXT:check:15          ^~~
+; SEARCH-LINE-BOUNDS-VV-NEXT:not:14'1          }     search range end (exclusive)
+;    SEARCH-LINE-BOUNDS-NEXT:>>>>>>

diff  --git a/llvm/test/FileCheck/dump-input/context.txt b/llvm/test/FileCheck/dump-input/context.txt
index 78092a909dbd8..f5bfc78f656af 100644
--- a/llvm/test/FileCheck/dump-input/context.txt
+++ b/llvm/test/FileCheck/dump-input/context.txt
@@ -52,111 +52,113 @@
 ; RUN: echo 'CHECK-LABEL: lab1'  >  %t.chk
 ; RUN: echo ' CHECK-NEXT: hello' >> %t.chk
 ; RUN: echo 'CHECK-LABEL: lab2'  >> %t.chk
-; RUN: echo ' CHECK-NEXT: world' >> %t.chk
-
-;      C0: <<<<<<
-; CS-NEXT:            .
-; CS-NEXT:            .
-; CS-NEXT:            .
-; C5-NEXT:            1: foo8 
-; C5-NEXT:            2: foo7 
-; C5-NEXT:            3: foo6 
-; C5-NEXT:            4: foo5 
-; C4-NEXT:            5: foo4 
-; C3-NEXT:            6: foo3 
-; C2-NEXT:            7: foo2 
-; C1-NEXT:            8: foo1 
-; C0-NEXT:            9: lab1 hello 
-; C0-NEXT: label:1'0     ^~~~
-; C0-NEXT: label:1'1     ^~~~
-; C0-NEXT: next:2             !~~~~  error: match on wrong line
-; C1-NEXT:           10: foo1 
-; C2-NEXT:           11: foo2 
-; C3-NEXT:           12: foo3 
-; C4-NEXT:           13: foo4 
-; C5-NEXT:           14: foo5 
-; C6-NEXT:           15: foo6 
-; C6-NEXT:           16: foo7 
-; CM-NEXT:            .
-; CM-NEXT:            .
-; CM-NEXT:            .
-; C6-NEXT:           17: foo7 
-; C6-NEXT:           18: foo6 
-; C5-NEXT:           19: foo5 
-; C4-NEXT:           20: foo4 
-; C3-NEXT:           21: foo3 
-; C2-NEXT:           22: foo2 
-; C1-NEXT:           23: foo1 
-; C0-NEXT:           24: lab2 world 
-; C0-NEXT: label:3       ^~~~
-; C0-NEXT: next:4             !~~~~  error: match on wrong line
-; C1-NEXT:           25: foo1 
-; C2-NEXT:           26: foo2 
-; C3-NEXT:           27: foo3 
-; C4-NEXT:           28: foo4 
-; C5-NEXT:           29: foo5 
-; C6-NEXT:           30: foo6 
-; C7-NEXT:           31: foo7 
-; C7-NEXT:           32: foo8 
-; C7-NEXT:           33: foo9 
-; C7-NEXT:           34: foo0 
-; CE-NEXT:            .
-; CE-NEXT:            .
-; CE-NEXT:            .
-; C0-NEXT: >>>>>>
+
+;      C0:<<<<<<
+; CS-NEXT:           .
+; CS-NEXT:           .
+; CS-NEXT:           .
+; C5-NEXT:           1: foo8 
+; C5-NEXT:           2: foo7 
+; C5-NEXT:           3: foo6 
+; C5-NEXT:           4: foo5 
+; C4-NEXT:           5: foo4 
+; C3-NEXT:           6: foo3 
+; C2-NEXT:           7: foo2 
+; C1-NEXT:           8: foo1 
+; C0-NEXT:           9: lab1 hello 
+; C0-NEXT:label:1'0     ^~~~
+; C0-NEXT:label:1'1     ^~~~
+; C0-NEXT:next:2'0         {         search range start (exclusive)
+; C0-NEXT:next:2'1           !~~~~   error: match on wrong line
+; C1-NEXT:          10: foo1 
+; C2-NEXT:          11: foo2 
+; C3-NEXT:          12: foo3 
+; C4-NEXT:          13: foo4 
+; C5-NEXT:          14: foo5 
+; C6-NEXT:          15: foo6 
+; C6-NEXT:          16: foo7 
+; CM-NEXT:           .
+; CM-NEXT:           .
+; CM-NEXT:           .
+; C6-NEXT:          17: foo7 
+; C6-NEXT:          18: foo6 
+; C5-NEXT:          19: foo5 
+; C4-NEXT:          20: foo4 
+; C3-NEXT:          21: foo3 
+; C2-NEXT:          22: foo2 
+; C1-NEXT:          23: foo1 
+; C0-NEXT:          24: lab2 world 
+; C0-NEXT:label:3       ^~~~
+; C0-NEXT:next:2'2          }        search range end (exclusive)
+; C1-NEXT:          25: foo1 
+; C2-NEXT:          26: foo2 
+; C3-NEXT:          27: foo3 
+; C4-NEXT:          28: foo4 
+; C5-NEXT:          29: foo5 
+; C6-NEXT:          30: foo6 
+; C7-NEXT:          31: foo7 
+; C7-NEXT:          32: foo8 
+; C7-NEXT:          33: foo9 
+; C7-NEXT:          34: foo0 
+; CE-NEXT:           .
+; CE-NEXT:           .
+; CE-NEXT:           .
+; C0-NEXT:>>>>>>
 
 ; Now build an alternate set of checks where input lines that might be elided by
 ; ellipses have annotations.
 
 ; RUN: cp %t.in %t.wide.in
-; RUN: echo 'CHECK-LABEL: lab1'    >  %t.wide.chk
-; RUN: echo '      CHECK: hello'   >> %t.wide.chk
-; RUN: echo '      CHECK: goodbye' >> %t.wide.chk
-; RUN: echo 'CHECK-LABEL: lab2'    >> %t.wide.chk
-; RUN: echo ' CHECK-NEXT: world'   >> %t.wide.chk
-
-;      W5: <<<<<<
-;      W5:            9: lab1 hello 
-; W5-NEXT: label:1'0     ^~~~
-; W5-NEXT: label:1'1     ^~~~
-; W5-NEXT: check:2            ^~~~~
-; W5-NEXT: check:3'0               X error: no match found
-; W5-NEXT:           10: foo1 
-; W5-NEXT: check:3'0     ~~~~~
-; W5-NEXT: check:3'1     ?     possible intended match
-; W5-NEXT:           11: foo2 
-; W5-NEXT: check:3'0     ~~~~~
-; W5-NEXT:           12: foo3 
-; W5-NEXT: check:3'0     ~~~~~
-; W5-NEXT:           13: foo4 
-; W5-NEXT: check:3'0     ~~~~~
-; W5-NEXT:           14: foo5 
-; W5-NEXT: check:3'0     ~~~~~
-; W5-NEXT:           15: foo6 
-; W5-NEXT: check:3'0     ~~~~~
-; W6-NEXT:           16: foo7 
-; W6-NEXT: check:3'0     ~~~~~
-; WM-NEXT:            .
-; WM-NEXT:            .
-; WM-NEXT:            .
-; W6-NEXT:           17: foo7 
-; W6-NEXT: check:3'0     ~~~~~
-; W6-NEXT:           18: foo6 
-; W6-NEXT: check:3'0     ~~~~~
-; W5-NEXT:           19: foo5 
-; W5-NEXT: check:3'0     ~~~~~
-; W5-NEXT:           20: foo4 
-; W5-NEXT: check:3'0     ~~~~~
-; W5-NEXT:           21: foo3 
-; W5-NEXT: check:3'0     ~~~~~
-; W5-NEXT:           22: foo2 
-; W5-NEXT: check:3'0     ~~~~~
-; W5-NEXT:           23: foo1 
-; W5-NEXT: check:3'0     ~~~~~
-; W5-NEXT:           24: lab2 world 
-; W5-NEXT: label:4       ^~~~
-; W5-NEXT: check:3'0     ~~~~
-; W5-NEXT: next:5             !~~~~  error: match on wrong line
+; RUN: echo 'CHECK-LABEL: lab1'                          >  %t.wide.chk
+; RUN: echo '  CHECK-DAG: hello{{([foo0-9[:space:]])*}}' >> %t.wide.chk
+; RUN: echo '  CHECK-DAG: goodbye'                       >> %t.wide.chk
+; RUN: echo 'CHECK-LABEL: lab2'                          >> %t.wide.chk
+; RUN: echo ' CHECK-NEXT: world'                         >> %t.wide.chk
+
+;      W5:<<<<<<
+;      W5:           9: lab1 hello 
+; W5-NEXT:label:1'0     ^~~~
+; W5-NEXT:label:1'1     ^~~~
+; W5-NEXT:dag:2              ^~~~~~
+; W5-NEXT:dag:3'0          {         search range start (exclusive)
+; W5-NEXT:dag:3'1                    error: no match found in search range
+; W5-NEXT:          10: foo1 
+; W5-NEXT:dag:2         ~~~~~
+; W5-NEXT:dag:3'2       ?      possible intended match
+; W5-NEXT:          11: foo2 
+; W5-NEXT:dag:2         ~~~~~
+; W5-NEXT:          12: foo3 
+; W5-NEXT:dag:2         ~~~~~
+; W5-NEXT:          13: foo4 
+; W5-NEXT:dag:2         ~~~~~
+; W5-NEXT:          14: foo5 
+; W5-NEXT:dag:2         ~~~~~
+; W5-NEXT:          15: foo6 
+; W5-NEXT:dag:2         ~~~~~
+; W6-NEXT:          16: foo7 
+; W6-NEXT:dag:2         ~~~~~
+; WM-NEXT:           .
+; WM-NEXT:           .
+; WM-NEXT:           .
+; W6-NEXT:          17: foo7 
+; W6-NEXT:dag:2         ~~~~~
+; W6-NEXT:          18: foo6 
+; W6-NEXT:dag:2         ~~~~~
+; W5-NEXT:          19: foo5 
+; W5-NEXT:dag:2         ~~~~~
+; W5-NEXT:          20: foo4 
+; W5-NEXT:dag:2         ~~~~~
+; W5-NEXT:          21: foo3 
+; W5-NEXT:dag:2         ~~~~~
+; W5-NEXT:          22: foo2 
+; W5-NEXT:dag:2         ~~~~~
+; W5-NEXT:          23: foo1 
+; W5-NEXT:dag:2         ~~~~~
+; W5-NEXT:          24: lab2 world 
+; W5-NEXT:label:4       ^~~~
+; W5-NEXT:dag:3'3           }        search range end (exclusive)
+; W5-NEXT:next:5'0         {         search range start (exclusive)
+; W5-NEXT:next:5'1           !~~~~   error: match on wrong line
 
 ;--------------------------------------------------
 ; Check -dump-input-context=<bad value>.
@@ -172,7 +174,7 @@
 ; RUN:               -dump-input-context=foobar \
 ; RUN: | FileCheck %s -match-full-lines -check-prefix=BADVAL -DVAL=foobar
 
-BADVAL: {{F|f}}ile{{C|c}}heck{{.*}}: for the --dump-input-context option: '[[VAL]]' value invalid for uint argument!
+BADVAL:{{F|f}}ile{{C|c}}heck{{.*}}: for the --dump-input-context option: '[[VAL]]' value invalid for uint argument!
 
 ;--------------------------------------------------
 ; Check -dump-input-context explicit values.

diff  --git a/llvm/test/FileCheck/dump-input/enable.txt b/llvm/test/FileCheck/dump-input/enable.txt
index 89b56960885e3..3708ef94a4f9d 100644
--- a/llvm/test/FileCheck/dump-input/enable.txt
+++ b/llvm/test/FileCheck/dump-input/enable.txt
@@ -27,7 +27,7 @@
 ; RUN: %ProtectFileCheckOutput not FileCheck -dump-input=foobar 2>&1 \
 ; RUN: | FileCheck %s -match-full-lines -check-prefix=BADVAL
 
-BADVAL: {{F|f}}ile{{C|c}}heck{{.*}}: for the --dump-input option: Cannot find option named 'foobar'!
+BADVAL:{{F|f}}ile{{C|c}}heck{{.*}}: for the --dump-input option: Cannot find option named 'foobar'!
 
 ;--------------------------------------------------
 ; Check -dump-input=help.
@@ -228,28 +228,29 @@ BADVAL: {{F|f}}ile{{C|c}}heck{{.*}}: for the --dump-input option: Cannot find op
 ; HELP-NOT: {{.}}
 
 ; Trace is sometimes suppressed.
-; TRACE:       {{.*}}remark:{{.*}}
-; NOTRACE-NOT: remark:
+;       TRACE:{{.*}}remark:{{.*}}
+; NOTRACE-NOT:remark:
 
 ; Error diagnostics are never suppressed.
-; ERR: {{.*}}error:{{.*}}
-
-; NODUMP-NOT: <<<<<<
-
-; DUMP-OK:      Input was:
-; DUMP-OK-NEXT: <<<<<<
-; DUMP-OK-NEXT:          1: hello 
-; DUMP-OK-NEXT: check:1     ^~~~~
-; DUMP-OK-NEXT:          2: world 
-; DUMP-OK-NEXT: next:2      ^~~~~
-; DUMP-OK-NEXT: >>>>>>
-
-; DUMP-ERR:        Input was:
-; DUMP-ERR-NEXT:   <<<<<<
-; DUMP-ERR-NEXT:            1: hello 
-; DUMP-ERR-V-NEXT: check:1     ^~~~~
-; DUMP-ERR-NEXT:   next:2'0         X error: no match found
-; DUMP-ERR-NEXT:            2: whirled 
-; DUMP-ERR-NEXT:   next:2'0    ~~~~~~~~
-; DUMP-ERR-NEXT:   next:2'1    ?        possible intended match
-; DUMP-ERR-NEXT:   >>>>>>
+; ERR:{{.*}}error:{{.*}}
+
+; NODUMP-NOT:<<<<<<
+
+;      DUMP-OK:Input was:
+; DUMP-OK-NEXT:<<<<<<
+; DUMP-OK-NEXT:         1: hello 
+; DUMP-OK-NEXT:check:1     ^~~~~
+; DUMP-OK-NEXT:         2: world 
+; DUMP-OK-NEXT:next:2      ^~~~~
+; DUMP-OK-NEXT:>>>>>>
+
+;        DUMP-ERR:Input was:
+;   DUMP-ERR-NEXT:<<<<<<
+;   DUMP-ERR-NEXT:          1: hello 
+; DUMP-ERR-V-NEXT:check:1      ^~~~~
+;   DUMP-ERR-NEXT:next:2'0         {   search range start (exclusive)
+;   DUMP-ERR-NEXT:next:2'1             error: no match found in search range
+;   DUMP-ERR-NEXT:          2: whirled 
+;   DUMP-ERR-NEXT:next:2'2     ?         possible intended match
+;   DUMP-ERR-NEXT:next:2'3             } search range end (exclusive)
+;   DUMP-ERR-NEXT:>>>>>>

diff  --git a/llvm/test/FileCheck/dump-input/filter.txt b/llvm/test/FileCheck/dump-input/filter.txt
index 6ea858d2126e6..5d8696830a6e5 100644
--- a/llvm/test/FileCheck/dump-input/filter.txt
+++ b/llvm/test/FileCheck/dump-input/filter.txt
@@ -30,7 +30,7 @@
 ; RUN: echo foo8  >> %t.in
 ; RUN: echo foo9  >> %t.in
 ; line 23
-; RUN: echo word  >> %t.in
+; RUN: echo world >> %t.in
 ; RUN: echo foo0  >> %t.in
 ; RUN: echo foo1  >> %t.in
 ; RUN: echo foo2  >> %t.in
@@ -42,207 +42,290 @@
 ; RUN: echo foo8  >> %t.in
 ; RUN: echo foo9  >> %t.in
 ; line 34
-; RUN: echo end   >> %t.in
-
-; RUN: echo 'CHECK: start' >  %t.chk
-; RUN: echo 'CHECK: hello' >> %t.chk
-; RUN: echo 'CHECK: world' >> %t.chk
-; RUN: echo 'CHECK: end'   >> %t.chk
+; RUN: echo bye  >> %t.in
+; RUN: echo foo0 >> %t.in
+; RUN: echo foo1 >> %t.in
+; RUN: echo foo2 >> %t.in
+; RUN: echo foo3 >> %t.in
+; RUN: echo foo4 >> %t.in
+; RUN: echo foo5 >> %t.in
+; RUN: echo foo6 >> %t.in
+; RUN: echo foo7 >> %t.in
+; RUN: echo foo8 >> %t.in
+; RUN: echo foo9 >> %t.in
+; line 45
+; RUN: echo sleep  >> %t.in
+; RUN: echo foo0   >> %t.in
+; RUN: echo foo1   >> %t.in
+; RUN: echo foo2   >> %t.in
+; RUN: echo foo3   >> %t.in
+; RUN: echo foo4   >> %t.in
+; RUN: echo foo5   >> %t.in
+; RUN: echo foo6   >> %t.in
+; RUN: echo foo7   >> %t.in
+; RUN: echo foo8   >> %t.in
+; RUN: echo foo9   >> %t.in
+; RUN: echo end    >> %t.in
+
+; RUN: echo 'CHECK: start'                            >  %t.chk
+; RUN: echo 'CHECK: hello{{(.|[[:space:]])*}}world'   >> %t.chk
+; RUN: echo 'CHECK-NOT: bye{{(.|[[:space:]])*}}sleep' >> %t.chk
+; RUN: echo 'CHECK: end'                              >> %t.chk
 
 ;--------------------------------------------------
 ; Directives for checking the dump.
 ;--------------------------------------------------
 
-;      ALL: <<<<<<
-; ALL-NEXT:            1: start 
-; ALL-NEXT: check:1       ^~~~~
-; ALL-NEXT:            2: foo0 
-; ALL-NEXT:            3: foo1 
-; ALL-NEXT:            4: foo2 
-; ALL-NEXT:            5: foo3 
-; ALL-NEXT:            6: foo4 
-; ALL-NEXT:            7: foo5 
-; ALL-NEXT:            8: foo6 
-; ALL-NEXT:            9: foo7 
-; ALL-NEXT:           10: foo8 
-; ALL-NEXT:           11: foo9 
-; ALL-NEXT:           12: hello 
-; ALL-NEXT: check:2       ^~~~~
-; ALL-NEXT: check:3'0          X error: no match found
-; ALL-NEXT:           13: foo0 
-; ALL-NEXT: check:3'0     ~~~~~
-; ALL-NEXT:           14: foo1 
-; ALL-NEXT: check:3'0     ~~~~~
-; ALL-NEXT:           15: foo2 
-; ALL-NEXT: check:3'0     ~~~~~
-; ALL-NEXT:           16: foo3 
-; ALL-NEXT: check:3'0     ~~~~~
-; ALL-NEXT:           17: foo4 
-; ALL-NEXT: check:3'0     ~~~~~
-; ALL-NEXT:           18: foo5 
-; ALL-NEXT: check:3'0     ~~~~~
-; ALL-NEXT:           19: foo6 
-; ALL-NEXT: check:3'0     ~~~~~
-; ALL-NEXT:           20: foo7 
-; ALL-NEXT: check:3'0     ~~~~~
-; ALL-NEXT:           21: foo8 
-; ALL-NEXT: check:3'0     ~~~~~
-; ALL-NEXT:           22: foo9 
-; ALL-NEXT: check:3'0     ~~~~~
-; ALL-NEXT:           23: word 
-; ALL-NEXT: check:3'0     ~~~~~
-; ALL-NEXT: check:3'1     ?     possible intended match
-; ALL-NEXT:           24: foo0 
-; ALL-NEXT: check:3'0     ~~~~~
-; ALL-NEXT:           25: foo1 
-; ALL-NEXT: check:3'0     ~~~~~
-; ALL-NEXT:           26: foo2 
-; ALL-NEXT: check:3'0     ~~~~~
-; ALL-NEXT:           27: foo3 
-; ALL-NEXT: check:3'0     ~~~~~
-; ALL-NEXT:           28: foo4 
-; ALL-NEXT: check:3'0     ~~~~~
-; ALL-NEXT:           29: foo5 
-; ALL-NEXT: check:3'0     ~~~~~
-; ALL-NEXT:           30: foo6 
-; ALL-NEXT: check:3'0     ~~~~~
-; ALL-NEXT:           31: foo7 
-; ALL-NEXT: check:3'0     ~~~~~
-; ALL-NEXT:           32: foo8 
-; ALL-NEXT: check:3'0     ~~~~~
-; ALL-NEXT:           33: foo9 
-; ALL-NEXT: check:3'0     ~~~~~
-; ALL-NEXT:           34: end 
-; ALL-NEXT: check:3'0     ~~~~
-; ALL-NEXT: >>>>>>
-
-;      ANNOTATION-FULL: <<<<<<
-; ANNOTATION-FULL-NEXT:            1: start 
-; ANNOTATION-FULL-NEXT: check:1       ^~~~~
-; ANNOTATION-FULL-NEXT:            2: foo0 
-; ANNOTATION-FULL-NEXT:            3: foo1 
-; ANNOTATION-FULL-NEXT:            .
-; ANNOTATION-FULL-NEXT:            .
-; ANNOTATION-FULL-NEXT:            .
-; ANNOTATION-FULL-NEXT:           10: foo8 
-; ANNOTATION-FULL-NEXT:           11: foo9 
-; ANNOTATION-FULL-NEXT:           12: hello 
-; ANNOTATION-FULL-NEXT: check:2       ^~~~~
-; ANNOTATION-FULL-NEXT: check:3'0          X error: no match found
-; ANNOTATION-FULL-NEXT:           13: foo0 
-; ANNOTATION-FULL-NEXT: check:3'0     ~~~~~
-; ANNOTATION-FULL-NEXT:           14: foo1 
-; ANNOTATION-FULL-NEXT: check:3'0     ~~~~~
-; ANNOTATION-FULL-NEXT:           15: foo2 
-; ANNOTATION-FULL-NEXT: check:3'0     ~~~~~
-; ANNOTATION-FULL-NEXT:           16: foo3 
-; ANNOTATION-FULL-NEXT: check:3'0     ~~~~~
-; ANNOTATION-FULL-NEXT:           17: foo4 
-; ANNOTATION-FULL-NEXT: check:3'0     ~~~~~
-; ANNOTATION-FULL-NEXT:           18: foo5 
-; ANNOTATION-FULL-NEXT: check:3'0     ~~~~~
-; ANNOTATION-FULL-NEXT:           19: foo6 
-; ANNOTATION-FULL-NEXT: check:3'0     ~~~~~
-; ANNOTATION-FULL-NEXT:           20: foo7 
-; ANNOTATION-FULL-NEXT: check:3'0     ~~~~~
-; ANNOTATION-FULL-NEXT:           21: foo8 
-; ANNOTATION-FULL-NEXT: check:3'0     ~~~~~
-; ANNOTATION-FULL-NEXT:           22: foo9 
-; ANNOTATION-FULL-NEXT: check:3'0     ~~~~~
-; ANNOTATION-FULL-NEXT:           23: word 
-; ANNOTATION-FULL-NEXT: check:3'0     ~~~~~
-; ANNOTATION-FULL-NEXT: check:3'1     ?     possible intended match
-; ANNOTATION-FULL-NEXT:           24: foo0 
-; ANNOTATION-FULL-NEXT: check:3'0     ~~~~~
-; ANNOTATION-FULL-NEXT:           25: foo1 
-; ANNOTATION-FULL-NEXT: check:3'0     ~~~~~
-; ANNOTATION-FULL-NEXT:           26: foo2 
-; ANNOTATION-FULL-NEXT: check:3'0     ~~~~~
-; ANNOTATION-FULL-NEXT:           27: foo3 
-; ANNOTATION-FULL-NEXT: check:3'0     ~~~~~
-; ANNOTATION-FULL-NEXT:           28: foo4 
-; ANNOTATION-FULL-NEXT: check:3'0     ~~~~~
-; ANNOTATION-FULL-NEXT:           29: foo5 
-; ANNOTATION-FULL-NEXT: check:3'0     ~~~~~
-; ANNOTATION-FULL-NEXT:           30: foo6 
-; ANNOTATION-FULL-NEXT: check:3'0     ~~~~~
-; ANNOTATION-FULL-NEXT:           31: foo7 
-; ANNOTATION-FULL-NEXT: check:3'0     ~~~~~
-; ANNOTATION-FULL-NEXT:           32: foo8 
-; ANNOTATION-FULL-NEXT: check:3'0     ~~~~~
-; ANNOTATION-FULL-NEXT:           33: foo9 
-; ANNOTATION-FULL-NEXT: check:3'0     ~~~~~
-; ANNOTATION-FULL-NEXT:           34: end 
-; ANNOTATION-FULL-NEXT: check:3'0     ~~~~
-; ANNOTATION-FULL-NEXT: >>>>>>
-
-;      ANNOTATION: <<<<<<
-; ANNOTATION-NEXT:            1: start 
-; ANNOTATION-NEXT: check:1       ^~~~~
-; ANNOTATION-NEXT:            2: foo0 
-; ANNOTATION-NEXT:            3: foo1 
-; ANNOTATION-NEXT:            .
-; ANNOTATION-NEXT:            .
-; ANNOTATION-NEXT:            .
-; ANNOTATION-NEXT:           10: foo8 
-; ANNOTATION-NEXT:           11: foo9 
-; ANNOTATION-NEXT:           12: hello 
-; ANNOTATION-NEXT: check:2       ^~~~~
-; ANNOTATION-NEXT: check:3'0          X error: no match found
-; ANNOTATION-NEXT:           13: foo0 
-; ANNOTATION-NEXT: check:3'0     ~~~~~
-; ANNOTATION-NEXT:           14: foo1 
-; ANNOTATION-NEXT: check:3'0     ~~~~~
-; ANNOTATION-NEXT:            .
-; ANNOTATION-NEXT:            .
-; ANNOTATION-NEXT:            .
-; ANNOTATION-NEXT:           21: foo8 
-; ANNOTATION-NEXT: check:3'0     ~~~~~
-; ANNOTATION-NEXT:           22: foo9 
-; ANNOTATION-NEXT: check:3'0     ~~~~~
-; ANNOTATION-NEXT:           23: word 
-; ANNOTATION-NEXT: check:3'0     ~~~~~
-; ANNOTATION-NEXT: check:3'1     ?     possible intended match
-; ANNOTATION-NEXT:           24: foo0 
-; ANNOTATION-NEXT: check:3'0     ~~~~~
-; ANNOTATION-NEXT:           25: foo1 
-; ANNOTATION-NEXT: check:3'0     ~~~~~
-; ANNOTATION-NEXT:            .
-; ANNOTATION-NEXT:            .
-; ANNOTATION-NEXT:            .
-; ANNOTATION-NEXT: >>>>>>
-
-;      ERROR: <<<<<<
-; ERROR-NEXT:            .
-; ERROR-NEXT:            .
-; ERROR-NEXT:            .
-; ERROR-NEXT:           10: foo8 
-; ERROR-NEXT:           11: foo9 
-; ERROR-NEXT:           12: hello 
-; ERROR-NEXT: check:2       ^~~~~
-; ERROR-NEXT: check:3'0          X error: no match found
-; ERROR-NEXT:           13: foo0 
-; ERROR-NEXT: check:3'0     ~~~~~
-; ERROR-NEXT:           14: foo1 
-; ERROR-NEXT: check:3'0     ~~~~~
-; ERROR-NEXT:            .
-; ERROR-NEXT:            .
-; ERROR-NEXT:            .
-; ERROR-NEXT:           21: foo8 
-; ERROR-NEXT: check:3'0     ~~~~~
-; ERROR-NEXT:           22: foo9 
-; ERROR-NEXT: check:3'0     ~~~~~
-; ERROR-NEXT:           23: word 
-; ERROR-NEXT: check:3'0     ~~~~~
-; ERROR-NEXT: check:3'1     ?     possible intended match
-; ERROR-NEXT:           24: foo0 
-; ERROR-NEXT: check:3'0     ~~~~~
-; ERROR-NEXT:           25: foo1 
-; ERROR-NEXT: check:3'0     ~~~~~
-; ERROR-NEXT:            .
-; ERROR-NEXT:            .
-; ERROR-NEXT:            .
-; ERROR-NEXT: >>>>>>
+;      ALL:<<<<<<
+; ALL-NEXT:         1: start 
+; ALL-NEXT:check:1     ^~~~~
+; ALL-NEXT:         2: foo0 
+; ALL-NEXT:         3: foo1 
+; ALL-NEXT:         4: foo2 
+; ALL-NEXT:         5: foo3 
+; ALL-NEXT:         6: foo4 
+; ALL-NEXT:         7: foo5 
+; ALL-NEXT:         8: foo6 
+; ALL-NEXT:         9: foo7 
+; ALL-NEXT:        10: foo8 
+; ALL-NEXT:        11: foo9 
+; ALL-NEXT:        12: hello 
+; ALL-NEXT:check:2     ^~~~~~
+; ALL-NEXT:        13: foo0 
+; ALL-NEXT:check:2     ~~~~~
+; ALL-NEXT:        14: foo1 
+; ALL-NEXT:check:2     ~~~~~
+; ALL-NEXT:        15: foo2 
+; ALL-NEXT:check:2     ~~~~~
+; ALL-NEXT:        16: foo3 
+; ALL-NEXT:check:2     ~~~~~
+; ALL-NEXT:        17: foo4 
+; ALL-NEXT:check:2     ~~~~~
+; ALL-NEXT:        18: foo5 
+; ALL-NEXT:check:2     ~~~~~
+; ALL-NEXT:        19: foo6 
+; ALL-NEXT:check:2     ~~~~~
+; ALL-NEXT:        20: foo7 
+; ALL-NEXT:check:2     ~~~~~
+; ALL-NEXT:        21: foo8 
+; ALL-NEXT:check:2     ~~~~~
+; ALL-NEXT:        22: foo9 
+; ALL-NEXT:check:2     ~~~~~
+; ALL-NEXT:        23: world 
+; ALL-NEXT:check:2     ~~~~~
+; ALL-NEXT:not:3'0         {   search range start (exclusive)
+; ALL-NEXT:        24: foo0 
+; ALL-NEXT:        25: foo1 
+; ALL-NEXT:        26: foo2 
+; ALL-NEXT:        27: foo3 
+; ALL-NEXT:        28: foo4 
+; ALL-NEXT:        29: foo5 
+; ALL-NEXT:        30: foo6 
+; ALL-NEXT:        31: foo7 
+; ALL-NEXT:        32: foo8 
+; ALL-NEXT:        33: foo9 
+; ALL-NEXT:        34: bye 
+; ALL-NEXT:not:3'1     !~~~  error: no match expected
+; ALL-NEXT:        35: foo0 
+; ALL-NEXT:not:3'1     ~~~~~
+; ALL-NEXT:        36: foo1 
+; ALL-NEXT:not:3'1     ~~~~~
+; ALL-NEXT:        37: foo2 
+; ALL-NEXT:not:3'1     ~~~~~
+; ALL-NEXT:        38: foo3 
+; ALL-NEXT:not:3'1     ~~~~~
+; ALL-NEXT:        39: foo4 
+; ALL-NEXT:not:3'1     ~~~~~
+; ALL-NEXT:        40: foo5 
+; ALL-NEXT:not:3'1     ~~~~~
+; ALL-NEXT:        41: foo6 
+; ALL-NEXT:not:3'1     ~~~~~
+; ALL-NEXT:        42: foo7 
+; ALL-NEXT:not:3'1     ~~~~~
+; ALL-NEXT:        43: foo8 
+; ALL-NEXT:not:3'1     ~~~~~
+; ALL-NEXT:        44: foo9 
+; ALL-NEXT:not:3'1     ~~~~~
+; ALL-NEXT:        45: sleep 
+; ALL-NEXT:not:3'1     ~~~~~
+; ALL-NEXT:        46: foo0 
+; ALL-NEXT:        47: foo1 
+; ALL-NEXT:        48: foo2 
+; ALL-NEXT:        49: foo3 
+; ALL-NEXT:        50: foo4 
+; ALL-NEXT:        51: foo5 
+; ALL-NEXT:        52: foo6 
+; ALL-NEXT:        53: foo7 
+; ALL-NEXT:        54: foo8 
+; ALL-NEXT:        55: foo9 
+; ALL-NEXT:not:3'2          } search range end (exclusive)
+; ALL-NEXT:        56: end 
+; ALL-NEXT:check:4     ^~~
+; ALL-NEXT:>>>>>>
+
+;      ANNOTATION-FULL:<<<<<<
+; ANNOTATION-FULL-NEXT:         1: start 
+; ANNOTATION-FULL-NEXT:check:1     ^~~~~
+; ANNOTATION-FULL-NEXT:         2: foo0 
+; ANNOTATION-FULL-NEXT:         3: foo1 
+; ANNOTATION-FULL-NEXT:         .
+; ANNOTATION-FULL-NEXT:         .
+; ANNOTATION-FULL-NEXT:         .
+; ANNOTATION-FULL-NEXT:        10: foo8 
+; ANNOTATION-FULL-NEXT:        11: foo9 
+; ANNOTATION-FULL-NEXT:        12: hello 
+; ANNOTATION-FULL-NEXT:check:2     ^~~~~~
+; ANNOTATION-FULL-NEXT:        13: foo0 
+; ANNOTATION-FULL-NEXT:check:2     ~~~~~
+; ANNOTATION-FULL-NEXT:        14: foo1 
+; ANNOTATION-FULL-NEXT:check:2     ~~~~~
+; ANNOTATION-FULL-NEXT:        15: foo2 
+; ANNOTATION-FULL-NEXT:check:2     ~~~~~
+; ANNOTATION-FULL-NEXT:        16: foo3 
+; ANNOTATION-FULL-NEXT:check:2     ~~~~~
+; ANNOTATION-FULL-NEXT:        17: foo4 
+; ANNOTATION-FULL-NEXT:check:2     ~~~~~
+; ANNOTATION-FULL-NEXT:        18: foo5 
+; ANNOTATION-FULL-NEXT:check:2     ~~~~~
+; ANNOTATION-FULL-NEXT:        19: foo6 
+; ANNOTATION-FULL-NEXT:check:2     ~~~~~
+; ANNOTATION-FULL-NEXT:        20: foo7 
+; ANNOTATION-FULL-NEXT:check:2     ~~~~~
+; ANNOTATION-FULL-NEXT:        21: foo8 
+; ANNOTATION-FULL-NEXT:check:2     ~~~~~
+; ANNOTATION-FULL-NEXT:        22: foo9 
+; ANNOTATION-FULL-NEXT:check:2     ~~~~~
+; ANNOTATION-FULL-NEXT:        23: world 
+; ANNOTATION-FULL-NEXT:check:2     ~~~~~
+; ANNOTATION-FULL-NEXT:not:3'0         {   search range start (exclusive)
+; ANNOTATION-FULL-NEXT:        24: foo0 
+; ANNOTATION-FULL-NEXT:        25: foo1 
+; ANNOTATION-FULL-NEXT:         .
+; ANNOTATION-FULL-NEXT:         .
+; ANNOTATION-FULL-NEXT:         .
+; ANNOTATION-FULL-NEXT:        32: foo8 
+; ANNOTATION-FULL-NEXT:        33: foo9 
+; ANNOTATION-FULL-NEXT:        34: bye 
+; ANNOTATION-FULL-NEXT:not:3'1     !~~~  error: no match expected
+; ANNOTATION-FULL-NEXT:        35: foo0 
+; ANNOTATION-FULL-NEXT:not:3'1     ~~~~~
+; ANNOTATION-FULL-NEXT:        36: foo1 
+; ANNOTATION-FULL-NEXT:not:3'1     ~~~~~
+; ANNOTATION-FULL-NEXT:        37: foo2 
+; ANNOTATION-FULL-NEXT:not:3'1     ~~~~~
+; ANNOTATION-FULL-NEXT:        38: foo3 
+; ANNOTATION-FULL-NEXT:not:3'1     ~~~~~
+; ANNOTATION-FULL-NEXT:        39: foo4 
+; ANNOTATION-FULL-NEXT:not:3'1     ~~~~~
+; ANNOTATION-FULL-NEXT:        40: foo5 
+; ANNOTATION-FULL-NEXT:not:3'1     ~~~~~
+; ANNOTATION-FULL-NEXT:        41: foo6 
+; ANNOTATION-FULL-NEXT:not:3'1     ~~~~~
+; ANNOTATION-FULL-NEXT:        42: foo7 
+; ANNOTATION-FULL-NEXT:not:3'1     ~~~~~
+; ANNOTATION-FULL-NEXT:        43: foo8 
+; ANNOTATION-FULL-NEXT:not:3'1     ~~~~~
+; ANNOTATION-FULL-NEXT:        44: foo9 
+; ANNOTATION-FULL-NEXT:not:3'1     ~~~~~
+; ANNOTATION-FULL-NEXT:        45: sleep 
+; ANNOTATION-FULL-NEXT:not:3'1     ~~~~~
+; ANNOTATION-FULL-NEXT:        46: foo0 
+; ANNOTATION-FULL-NEXT:        47: foo1 
+; ANNOTATION-FULL-NEXT:         .
+; ANNOTATION-FULL-NEXT:         .
+; ANNOTATION-FULL-NEXT:         .
+; ANNOTATION-FULL-NEXT:        53: foo7 
+; ANNOTATION-FULL-NEXT:        54: foo8 
+; ANNOTATION-FULL-NEXT:        55: foo9 
+; ANNOTATION-FULL-NEXT:not:3'2          } search range end (exclusive)
+; ANNOTATION-FULL-NEXT:        56: end 
+; ANNOTATION-FULL-NEXT:check:4     ^~~
+; ANNOTATION-FULL-NEXT:>>>>>>
+
+;      ANNOTATION:<<<<<<
+; ANNOTATION-NEXT:         1: start 
+; ANNOTATION-NEXT:check:1     ^~~~~
+; ANNOTATION-NEXT:         2: foo0 
+; ANNOTATION-NEXT:         3: foo1 
+; ANNOTATION-NEXT:         .
+; ANNOTATION-NEXT:         .
+; ANNOTATION-NEXT:         .
+; ANNOTATION-NEXT:        10: foo8 
+; ANNOTATION-NEXT:        11: foo9 
+; ANNOTATION-NEXT:        12: hello 
+; ANNOTATION-NEXT:check:2     ^~~~~~
+; ANNOTATION-NEXT:        13: foo0 
+; ANNOTATION-NEXT:check:2     ~~~~~
+; ANNOTATION-NEXT:        14: foo1 
+; ANNOTATION-NEXT:check:2     ~~~~~
+; ANNOTATION-NEXT:         .
+; ANNOTATION-NEXT:         .
+; ANNOTATION-NEXT:         .
+; ANNOTATION-NEXT:        21: foo8 
+; ANNOTATION-NEXT:check:2     ~~~~~
+; ANNOTATION-NEXT:        22: foo9 
+; ANNOTATION-NEXT:check:2     ~~~~~
+; ANNOTATION-NEXT:        23: world 
+; ANNOTATION-NEXT:check:2     ~~~~~
+; ANNOTATION-NEXT:not:3'0         {   search range start (exclusive)
+; ANNOTATION-NEXT:        24: foo0 
+; ANNOTATION-NEXT:        25: foo1 
+; ANNOTATION-NEXT:         .
+; ANNOTATION-NEXT:         .
+; ANNOTATION-NEXT:         .
+; ANNOTATION-NEXT:        32: foo8 
+; ANNOTATION-NEXT:        33: foo9 
+; ANNOTATION-NEXT:        34: bye 
+; ANNOTATION-NEXT:not:3'1     !~~~  error: no match expected
+; ANNOTATION-NEXT:        35: foo0 
+; ANNOTATION-NEXT:not:3'1     ~~~~~
+; ANNOTATION-NEXT:        36: foo1 
+; ANNOTATION-NEXT:not:3'1     ~~~~~
+; ANNOTATION-NEXT:         .
+; ANNOTATION-NEXT:         .
+; ANNOTATION-NEXT:         .
+; ANNOTATION-NEXT:        53: foo7 
+; ANNOTATION-NEXT:        54: foo8 
+; ANNOTATION-NEXT:        55: foo9 
+; ANNOTATION-NEXT:not:3'2          } search range end (exclusive)
+; ANNOTATION-NEXT:        56: end 
+; ANNOTATION-NEXT:check:4     ^~~
+; ANNOTATION-NEXT:>>>>>>
+
+;      ERROR:<<<<<<
+; ERROR-NEXT:         .
+; ERROR-NEXT:         .
+; ERROR-NEXT:         .
+; ERROR-NEXT:        21: foo8 
+; ERROR-NEXT:check:2     ~~~~~
+; ERROR-NEXT:        22: foo9 
+; ERROR-NEXT:check:2     ~~~~~
+; ERROR-NEXT:        23: world 
+; ERROR-NEXT:check:2     ~~~~~
+; ERROR-NEXT:not:3'0         {   search range start (exclusive)
+; ERROR-NEXT:        24: foo0 
+; ERROR-NEXT:        25: foo1 
+; ERROR-NEXT:         .
+; ERROR-NEXT:         .
+; ERROR-NEXT:         .
+; ERROR-NEXT:        32: foo8 
+; ERROR-NEXT:        33: foo9 
+; ERROR-NEXT:        34: bye 
+; ERROR-NEXT:not:3'1     !~~~  error: no match expected
+; ERROR-NEXT:        35: foo0 
+; ERROR-NEXT:not:3'1     ~~~~~
+; ERROR-NEXT:        36: foo1 
+; ERROR-NEXT:not:3'1     ~~~~~
+; ERROR-NEXT:         .
+; ERROR-NEXT:         .
+; ERROR-NEXT:         .
+; ERROR-NEXT:        53: foo7 
+; ERROR-NEXT:        54: foo8 
+; ERROR-NEXT:        55: foo9 
+; ERROR-NEXT:not:3'2          } search range end (exclusive)
+; ERROR-NEXT:        56: end 
+; ERROR-NEXT:check:4     ^~~
+; ERROR-NEXT:>>>>>>
 
 ;--------------------------------------------------
 ; Check -dump-input-filter=<bad value>.
@@ -253,7 +336,7 @@
 ; RUN:               -dump-input-filter=foobar \
 ; RUN: | FileCheck %s -match-full-lines -check-prefix=BADVAL
 
-BADVAL: {{F|f}}ile{{C|c}}heck{{.*}}: for the --dump-input-filter option: Cannot find option named 'foobar'!
+BADVAL:{{F|f}}ile{{C|c}}heck{{.*}}: for the --dump-input-filter option: Cannot find option named 'foobar'!
 
 ;--------------------------------------------------
 ; Check -dump-input-filter explicit values.
@@ -380,47 +463,123 @@ BADVAL: {{F|f}}ile{{C|c}}heck{{.*}}: for the --dump-input-filter option: Cannot
 ; RUN:           < %t.in 2>&1 \
 ; RUN: | FileCheck %s -match-full-lines -check-prefixes=EMPTY
 
-;      EMPTY: <<<<<<
-; EMPTY-NEXT:          .
-; EMPTY-NEXT:          .
-; EMPTY-NEXT:          .
-; EMPTY-NEXT: >>>>>>
+;      EMPTY:<<<<<<
+; EMPTY-NEXT:         .
+; EMPTY-NEXT:         .
+; EMPTY-NEXT:         .
+; EMPTY-NEXT:>>>>>>
 
 ;--------------------------------------------------
 ; Check that other kinds of errors are included by -dump-input-filter=error.
 ;
-; "error: no match found" and "possible intended match" are checked above.
+; "error: no match expected" is checked above.
 ;--------------------------------------------------
 
 ;- - - - - - - - - - - - - - - - - - - - - - - - -
-; error: no match expected.
+; error: no match found in search range.
+; possible intended match
 ;- - - - - - - - - - - - - - - - - - - - - - - - -
 
-; RUN: echo 'foo'            > %t.not-err.in
-; RUN: echo 'CHECK-NOT: foo' > %t.not-err.chk
+; RUN: echo 'start' > %t.chk-err.in
+; RUN: echo 'foo0'  >> %t.chk-err.in
+; RUN: echo 'foo1'  >> %t.chk-err.in
+; RUN: echo 'foo2'  >> %t.chk-err.in
+; RUN: echo 'foo3'  >> %t.chk-err.in
+; RUN: echo 'hello' >> %t.chk-err.in
+; RUN: echo 'foo0'  >> %t.chk-err.in
+; RUN: echo 'foo1'  >> %t.chk-err.in
+; RUN: echo 'foo2'  >> %t.chk-err.in
+; RUN: echo 'foo3'  >> %t.chk-err.in
+; RUN: echo 'end'   >> %t.chk-err.in
+
+; RUN: echo 'CHECK: jello' > %t.chk-err.chk
 
 ; RUN: %ProtectFileCheckOutput \
 ; RUN: not FileCheck -dump-input-context=0 -dump-input-filter=error \
-; RUN:               %t.not-err.chk < %t.not-err.in 2>&1 \
-; RUN: | FileCheck %s -match-full-lines -check-prefixes=NOT-ERR
-
-;      NOT-ERR:        1: foo 
-; NOT-ERR-NEXT: not:1     !~~ error: no match expected
+; RUN:               %t.chk-err.chk < %t.chk-err.in 2>&1 \
+; RUN: | FileCheck %s -match-full-lines -check-prefixes=CHK-ERR
+
+;      CHK-ERR:<<<<<<
+; CHK-ERR-NEXT:           1: start 
+; CHK-ERR-NEXT:check:1'0    {        search range start (exclusive)
+; CHK-ERR-NEXT:check:1'1             error: no match found in search range
+; CHK-ERR-NEXT:           .
+; CHK-ERR-NEXT:           .
+; CHK-ERR-NEXT:           .
+; CHK-ERR-NEXT:           6: hello 
+; CHK-ERR-NEXT:check:1'2     ?       possible intended match
+; CHK-ERR-NEXT:           .
+; CHK-ERR-NEXT:           .
+; CHK-ERR-NEXT:           .
+; CHK-ERR-NEXT:          11: end 
+; CHK-ERR-NEXT:check:1'3         } search range end (exclusive)
+; CHK-ERR-NEXT:>>>>>>
 
 ;- - - - - - - - - - - - - - - - - - - - - - - - -
 ; error: match on wrong line.
 ;- - - - - - - - - - - - - - - - - - - - - - - - -
 
-; RUN: echo 'foo'             >  %t.next-err.in
-; RUN: echo 'foo'             >> %t.next-err.in
-; RUN: echo 'bar'             >> %t.next-err.in
-; RUN: echo 'CHECK: foo'      >  %t.next-err.chk
-; RUN: echo 'CHECK-NEXT: bar' >> %t.next-err.chk
+; RUN: echo 'start' >  %t.next-err.in
+; RUN: echo 'foo0'  >> %t.next-err.in
+; RUN: echo 'foo1'  >> %t.next-err.in
+; RUN: echo 'foo2'  >> %t.next-err.in
+; RUN: echo 'foo3'  >> %t.next-err.in
+; RUN: echo 'hello' >> %t.next-err.in
+; RUN: echo 'foo0'  >> %t.next-err.in
+; RUN: echo 'foo1'  >> %t.next-err.in
+; RUN: echo 'foo2'  >> %t.next-err.in
+; RUN: echo 'foo3'  >> %t.next-err.in
+; RUN: echo 'end'   >> %t.next-err.in
+
+; RUN: echo 'CHECK: start'      >  %t.next-err.chk
+; RUN: echo 'CHECK-NEXT: hello' >> %t.next-err.chk
 
 ; RUN: %ProtectFileCheckOutput \
 ; RUN: not FileCheck -dump-input-context=0 -dump-input-filter=error \
 ; RUN:               %t.next-err.chk < %t.next-err.in 2>&1 \
 ; RUN: | FileCheck %s -match-full-lines -check-prefixes=NEXT-ERR
 
-;      NEXT-ERR:         3: bar 
-; NEXT-ERR-NEXT: next:2     !~~ error: match on wrong line
+;      NEXT-ERR:<<<<<<
+; NEXT-ERR-NEXT:          1: start 
+; NEXT-ERR-NEXT:next:2'0         {   search range start (exclusive)
+; NEXT-ERR-NEXT:          .
+; NEXT-ERR-NEXT:          .
+; NEXT-ERR-NEXT:          .
+; NEXT-ERR-NEXT:          6: hello 
+; NEXT-ERR-NEXT:next:2'1     !~~~~   error: match on wrong line
+; NEXT-ERR-NEXT:          .
+; NEXT-ERR-NEXT:          .
+; NEXT-ERR-NEXT:          .
+; NEXT-ERR-NEXT:         11: end 
+; NEXT-ERR-NEXT:next:2'2         } search range end (exclusive)
+; NEXT-ERR-NEXT:>>>>>>
+
+;- - - - - - - - - - - - - - - - - - - - - - - - -
+; error: match failed for invalid pattern.
+;- - - - - - - - - - - - - - - - - - - - - - - - -
+
+; RUN: echo 'start' >  %t.invalid.in
+; RUN: echo 'foo0'  >> %t.invalid.in
+; RUN: echo 'foo1'  >> %t.invalid.in
+; RUN: echo 'foo2'  >> %t.invalid.in
+; RUN: echo 'foo3'  >> %t.invalid.in
+; RUN: echo 'end'   >> %t.invalid.in
+
+; RUN: echo 'CHECK: [[#0x0 - 0x1]]' > %t.invalid.chk
+
+; RUN: %ProtectFileCheckOutput \
+; RUN: not FileCheck -dump-input-context=0 -dump-input-filter=error \
+; RUN:               %t.invalid.chk < %t.invalid.in 2>&1 \
+; RUN: | FileCheck %s -match-full-lines -check-prefixes=INVALID -strict-whitespace
+
+;      INVALID:<<<<<<
+; INVALID-NEXT:           1: start 
+; INVALID-NEXT:check:1'0    {        search range start (exclusive)
+; INVALID-NEXT:check:1'1             error: match failed for invalid pattern
+; INVALID-NEXT:check:1'2             unable to substitute variable or numeric expression: overflow error
+; INVALID-NEXT:           .
+; INVALID-NEXT:           .
+; INVALID-NEXT:           .
+; INVALID-NEXT:           6: end 
+; INVALID-NEXT:check:1'3         } search range end (exclusive)
+; INVALID-NEXT:>>>>>>

diff  --git a/llvm/test/FileCheck/dump-input/search-range-annotations/check-label-follows.txt b/llvm/test/FileCheck/dump-input/search-range-annotations/check-label-follows.txt
index 08b266a37aa37..098c63765c0ca 100644
--- a/llvm/test/FileCheck/dump-input/search-range-annotations/check-label-follows.txt
+++ b/llvm/test/FileCheck/dump-input/search-range-annotations/check-label-follows.txt
@@ -3,8 +3,7 @@
 ; surrounding lines.  Those lines are where the actual problem is: the "host
 ; triples:" header is off by one.  Sometimes the key to understanding a
 ; directive's failure is where the following CHECK-LABEL matched because it
-; limits the search range.  FIXME: Of course, those lines will not be revealed
-; until braced search ranges are actually implemented.
+; limits the search range.
 
 RUN: rm -rf %t
 RUN: split-file %s %t
@@ -18,14 +17,22 @@ RUN:   FileCheck %s -match-full-lines -check-prefix=DMP
 DMP-NEXT:           1: offload triples: 
 DMP-NEXT:label:1'0     ^~~~~~~~~~~~~~~~
 DMP-NEXT:label:1'1     ^~~~~~~~~~~~~~~~
-DMP-NEXT:check:2'0                     X  error: no match found
+DMP-NEXT:check:2'0                    {   search range start (exclusive)
+DMP-NEXT:check:2'1                        error: no match found in search range
 DMP-NEXT:           2: - x86_64-linux-gnu 
-DMP-NEXT:check:2'0     ~~~~~~~~~~~~~~~~~~~
-DMP-NEXT:check:2'1       ?                  possible intended match
+DMP-NEXT:check:2'2       ?                  possible intended match
 DMP-NEXT:           3: - x86_64-unknown-linux-gnu 
-DMP-NEXT:check:2'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~
 DMP-NEXT:           4: - x86_64-pc-linux-gnu 
-DMP-NEXT:check:2'0     ~~~~~~~~~~~~~~~~~~~~~~
+DMP-NEXT:           .
+DMP-NEXT:           .
+DMP-NEXT:           .
+DMP-NEXT:          13: - x86_64-linux-android 
+DMP-NEXT:          14: - amdgcn-amd-amdhsa
+DMP-NEXT:          15: host triples: 
+DMP-NEXT:label:3       ^~~~~~~~~~~~~
+DMP-NEXT:check:2'3                  }  search range end (exclusive)
+DMP-NEXT:          16: - nvptx64-nvidia-cuda 
+DMP-NEXT:          17: - x86_64-linux-gnu 
 DMP-NEXT:           .
 DMP-NEXT:           .
 DMP-NEXT:           .

diff  --git a/llvm/test/FileCheck/dump-input/search-range-annotations/check-next-same.txt b/llvm/test/FileCheck/dump-input/search-range-annotations/check-next-same.txt
index bfb4a8fa5305b..0767d4b734590 100644
--- a/llvm/test/FileCheck/dump-input/search-range-annotations/check-next-same.txt
+++ b/llvm/test/FileCheck/dump-input/search-range-annotations/check-next-same.txt
@@ -3,8 +3,7 @@
 ; 2-3.  Those lines are where the actual problem is because that is where the
 ; CHECK-NEXT/CHECK-SAME directive was expected to match but did not.  The line
 ; where CHECK-NEXT/CHECK-SAME actually matched, line 13, is typically less
-; useful information.  FIXME: Of course, the former lines will not be revealed
-; until braced search ranges are actually implemented.
+; useful information.
 
 RUN: rm -rf %t
 RUN: split-file %s %t
@@ -20,14 +19,22 @@ RUN:               -dump-input-filter=error 2>&1 | \
 RUN:   FileCheck %s -match-full-lines -check-prefixes=DMP,DMP_SAME
 
           DMP:<<<<<<
-     DMP-NEXT:        .
-     DMP-NEXT:        .
-     DMP-NEXT:        .
-     DMP-NEXT:       11: foo9 
-     DMP-NEXT:       12: foo10 
-     DMP-NEXT:       13: end 
-DMP_NEXT-NEXT:next:2     !~~   error: match on wrong line
-DMP_SAME-NEXT:same:2     !~~   error: match on wrong line
+     DMP-NEXT:          1: start 
+     DMP-NEXT:check:1      ^~~~~
+DMP_NEXT-NEXT:next:2'0         {   search range start (exclusive)
+DMP_SAME-NEXT:same:2'0         {   search range start (exclusive)
+     DMP-NEXT:          2: foo0 
+     DMP-NEXT:          3: foo1 
+     DMP-NEXT:          .
+     DMP-NEXT:          .
+     DMP-NEXT:          .
+     DMP-NEXT:         11: foo9 
+     DMP-NEXT:         12: foo10 
+     DMP-NEXT:         13: end 
+DMP_NEXT-NEXT:next:2'1     !~~   error: match on wrong line
+DMP_SAME-NEXT:same:2'1     !~~   error: match on wrong line
+DMP_NEXT-NEXT:next:2'2         } search range end (exclusive)
+DMP_SAME-NEXT:same:2'2         } search range end (exclusive)
      DMP-NEXT:>>>>>>
 
 ;--- next.txt

diff  --git a/llvm/test/FileCheck/dump-input/search-range-annotations/check-not.txt b/llvm/test/FileCheck/dump-input/search-range-annotations/check-not.txt
index 96f8705e71a7b..7cae1b85bb47a 100644
--- a/llvm/test/FileCheck/dump-input/search-range-annotations/check-not.txt
+++ b/llvm/test/FileCheck/dump-input/search-range-annotations/check-not.txt
@@ -4,8 +4,7 @@
 ; includes a GPU triple.  Line 62 and its context reveals we have an
 ; accidentally nested list.  Often the key to understanding a CHECK-NOT failure
 ; is where the directives before and after it matched because those establish
-; the search range.  FIXME: Of course, those lines will not be revealed until
-; braced search ranges are actually implemented.
+; the search range.
 
 RUN: rm -rf %t
 RUN: split-file %s %t
@@ -16,18 +15,30 @@ RUN:               -dump-input-filter=error 2>&1 | \
 RUN:   FileCheck %s -match-full-lines -check-prefix=DMP
 
      DMP:<<<<<<
-DMP-NEXT:       .
-DMP-NEXT:       .
-DMP-NEXT:       .
-DMP-NEXT:      42:  x86_64-amazon-linux 
-DMP-NEXT:      43:  x86_64-linux-android 
-DMP-NEXT:      44:  nvptx64-nvidia-cuda 
-DMP-NEXT:not:2      !~~~~~~~~~~~~~~~~~~   error: no match expected
-DMP-NEXT:      45:  i686-linux-gnu 
-DMP-NEXT:      46:  i686-pc-linux-gnu 
-DMP-NEXT:       .
-DMP-NEXT:       .
-DMP-NEXT:       .
+DMP-NEXT:         1: <triples kind="host"> 
+DMP-NEXT:check:1     ^~~~~~~~~~~~~~~~~~~~~
+DMP-NEXT:not:2'0                         {   search range start (exclusive)
+DMP-NEXT:         2:  x86_64-linux-gnu 
+DMP-NEXT:         3:  x86_64-unknown-linux-gnu 
+DMP-NEXT:         .
+DMP-NEXT:         .
+DMP-NEXT:         .
+DMP-NEXT:        42:  x86_64-amazon-linux 
+DMP-NEXT:        43:  x86_64-linux-android 
+DMP-NEXT:        44:  nvptx64-nvidia-cuda 
+DMP-NEXT:not:2'1      !~~~~~~~~~~~~~~~~~~   error: no match expected
+DMP-NEXT:        45:  i686-linux-gnu 
+DMP-NEXT:        46:  i686-pc-linux-gnu 
+DMP-NEXT:         .
+DMP-NEXT:         .
+DMP-NEXT:         .
+DMP-NEXT:        59:  i486-gnu 
+DMP-NEXT:        60:  i586-gnu 
+DMP-NEXT:        61:  i686-gnu 
+DMP-NEXT:not:2'2               } search range end (exclusive)
+DMP-NEXT:        62: </triples> 
+DMP-NEXT:check:3     ^~~~~~~~~~
+DMP-NEXT:        63: </triples> 
 DMP-NEXT:>>>>>>
 
 ;--- check.txt

diff  --git a/llvm/test/FileCheck/match-time-error-propagation/invalid-excluded-pattern.txt b/llvm/test/FileCheck/match-time-error-propagation/invalid-excluded-pattern.txt
index c8e621eb31796..001f74c0c5a3d 100644
--- a/llvm/test/FileCheck/match-time-error-propagation/invalid-excluded-pattern.txt
+++ b/llvm/test/FileCheck/match-time-error-propagation/invalid-excluded-pattern.txt
@@ -28,9 +28,10 @@ ERR-VV-EMPTY:
 
         DUMP:<<<<<<
    DUMP-NEXT:         1: -1 
-   DUMP-NEXT:not:1'0     X~~ error: match failed for invalid pattern
-   DUMP-NEXT:not:1'1         unable to substitute variable or numeric expression: overflow error
-   DUMP-NEXT:not:1'2         undefined variable: UNDEFVAR
+   DUMP-NEXT:not:1'0    {   } search range (exclusive bounds)
+   DUMP-NEXT:not:1'1          error: match failed for invalid pattern
+   DUMP-NEXT:not:1'2          unable to substitute variable or numeric expression: overflow error
+   DUMP-NEXT:not:1'3          undefined variable: UNDEFVAR
 DUMP-VV-NEXT:         2: 
 DUMP-VV-NEXT:eof:1       ^
    DUMP-NEXT:>>>>>>

diff  --git a/llvm/test/FileCheck/match-time-error-propagation/invalid-expected-pattern.txt b/llvm/test/FileCheck/match-time-error-propagation/invalid-expected-pattern.txt
index 6aada8b0c7587..c633a98b76c9a 100644
--- a/llvm/test/FileCheck/match-time-error-propagation/invalid-expected-pattern.txt
+++ b/llvm/test/FileCheck/match-time-error-propagation/invalid-expected-pattern.txt
@@ -17,9 +17,10 @@ RUN: echo > %t.in '-1'
 
      DUMP:<<<<<<
 DUMP-NEXT:           1: -1 
-DUMP-NEXT:check:1'0     X~~ error: match failed for invalid pattern
-DUMP-NEXT:check:1'1         unable to substitute variable or numeric expression: overflow error
-DUMP-NEXT:check:1'2         undefined variable: UNDEFVAR
+DUMP-NEXT:check:1'0    {   } search range (exclusive bounds)
+DUMP-NEXT:check:1'1          error: match failed for invalid pattern
+DUMP-NEXT:check:1'2          unable to substitute variable or numeric expression: overflow error
+DUMP-NEXT:check:1'3          undefined variable: UNDEFVAR
 DUMP-NEXT:>>>>>>
 
 ;--------------------------------------------------

diff  --git a/llvm/test/FileCheck/match-time-error-propagation/matched-excluded-pattern.txt b/llvm/test/FileCheck/match-time-error-propagation/matched-excluded-pattern.txt
index b76ff5ed66aa6..7c7a3e44b77d2 100644
--- a/llvm/test/FileCheck/match-time-error-propagation/matched-excluded-pattern.txt
+++ b/llvm/test/FileCheck/match-time-error-propagation/matched-excluded-pattern.txt
@@ -23,9 +23,10 @@ ERR-VV-EMPTY:
 
         DUMP:<<<<<<
    DUMP-NEXT:         1: 123 abc -1 
-   DUMP-NEXT:not:1'0     X~~~~~~~~~~ error: match failed for invalid pattern
-   DUMP-NEXT:not:1'1                 unable to substitute variable or numeric expression: overflow error
-   DUMP-NEXT:not:1'2                 with "122+1" equal to "123"
+   DUMP-NEXT:not:1'0    {           } search range (exclusive bounds)
+   DUMP-NEXT:not:1'1                  error: match failed for invalid pattern
+   DUMP-NEXT:not:1'2                  unable to substitute variable or numeric expression: overflow error
+   DUMP-NEXT:not:1'3                  with "122+1" equal to "123"
 DUMP-VV-NEXT:         2: 
 DUMP-VV-NEXT:eof:1       ^
    DUMP-NEXT:>>>>>>

diff  --git a/llvm/test/FileCheck/match-time-error-propagation/matched-expected-pattern.txt b/llvm/test/FileCheck/match-time-error-propagation/matched-expected-pattern.txt
index 66304b0368fd9..2aa79662a02ca 100644
--- a/llvm/test/FileCheck/match-time-error-propagation/matched-expected-pattern.txt
+++ b/llvm/test/FileCheck/match-time-error-propagation/matched-expected-pattern.txt
@@ -19,10 +19,11 @@ RUN: echo > %t.in '123 abc -1'
 
      DUMP:<<<<<<
 DUMP-NEXT:           1: 123 abc -1 
-DUMP-NEXT:check:1'0     X~~~~~~~~~~ error: match failed for invalid pattern
-DUMP-NEXT:check:1'1                 unable to substitute variable or numeric expression: overflow error
-DUMP-NEXT:check:1'2                 with "122+1" equal to "123"
-DUMP-NEXT:check:1'3       ?         possible intended match
+DUMP-NEXT:check:1'0    {           } search range (exclusive bounds)
+DUMP-NEXT:check:1'1                  error: match failed for invalid pattern
+DUMP-NEXT:check:1'2                  unable to substitute variable or numeric expression: overflow error
+DUMP-NEXT:check:1'3                  with "122+1" equal to "123"
+DUMP-NEXT:check:1'4       ?          possible intended match
 DUMP-NEXT:>>>>>>
 
 ;--------------------------------------------------

diff  --git a/llvm/utils/FileCheck/FileCheck.cpp b/llvm/utils/FileCheck/FileCheck.cpp
index 8c760db50a375..8668ea86a6ef9 100644
--- a/llvm/utils/FileCheck/FileCheck.cpp
+++ b/llvm/utils/FileCheck/FileCheck.cpp
@@ -217,9 +217,8 @@ static MarkerStyle getMarker(const FileCheckDiag &Diag) {
   // don't have markers.  For example, a marker for the MatchNoteDiag
   // 'with "VAR" equal to "5"' would seem to indicate where "VAR" matches, but
   // we don't actually have that location.  Instead, we just place the note
-  // after the start of the associated MatchResultDiag.  This decision is
-  // overriden below for the case of MatchNoneDiag because the search range is
-  // used instead.
+  // after the start of the associated MatchResultDiag.  Search ranges are
+  // indicated separately.
   MarkerStyle Res;
   bool IsError = Diag.isError() || Diag.getMatchResultDiag().isError();
   if (Diag.getMatchRange()) {
@@ -231,7 +230,7 @@ static MarkerStyle getMarker(const FileCheckDiag &Diag) {
   Res.Color = IsError ? raw_ostream::RED : raw_ostream::GREEN;
   Res.FiltersAsError = IsError;
 
-  // Add Note.  Override the default Lead and Color for some diagnostic kinds.
+  // Add Note.  Override the default Head and Color for some diagnostic kinds.
   switch (Diag.getKind()) {
   case FileCheckDiag::MatchFoundDiag:
     switch (cast<MatchFoundDiag>(Diag).getStatus()) {
@@ -251,8 +250,6 @@ static MarkerStyle getMarker(const FileCheckDiag &Diag) {
     }
     break;
   case FileCheckDiag::MatchNoneDiag:
-    Res.Head = 'X';
-    Res.Mid = Res.Tail = '~';
     switch (cast<MatchNoneDiag>(Diag).getStatus()) {
     case MatchNoneDiag::Success:
       break;
@@ -260,7 +257,7 @@ static MarkerStyle getMarker(const FileCheckDiag &Diag) {
       Res.Note = "match failed for invalid pattern";
       break;
     case MatchNoneDiag::Expected:
-      Res.Note = "no match found";
+      Res.Note = "no match found in search range";
       break;
     }
     break;
@@ -334,8 +331,11 @@ static void DumpInputAnnotationHelp(raw_ostream &OS) {
      << "           - CHECK-DAG overlapping match (discarded, reported if "
      << "-vv)\n"
      << "  - ";
-  WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "X~~";
-  OS << "    marks search range when no match is found, such as:\n"
+  WithColor(OS, raw_ostream::SAVEDCOLOR, true) << "{ }";
+  OS << "    encloses search range (exclusive bounds) when no match is found "
+     << "or\n"
+     << "           there is an error, such as:\n"
+     << "           - the errors mentioned above\n"
      << "           - CHECK-NEXT not found (error)\n"
      << "           - CHECK-NOT not found (success, reported if -vv)\n"
      << "           - CHECK-DAG not found after discarded matches (error)\n"
@@ -379,9 +379,11 @@ struct InputAnnotation {
   /// be 
diff erent from the starting line of the original diagnostic if
   /// !IsFirstLine.
   unsigned InputLine;
-  /// The column range (one-origin indexing, inclusive boundaries) in which to
-  /// mark the input line.  If \c InputLastCol is \c UINT_MAX, the rest of the
-  /// input line should be marked.
+  /// The column range (inclusive boundaries) in which to mark the input line.
+  /// A value of one indicates the first column of the actual input, and a
+  /// value of zero indicates the left margin.  If \c InputLastCol is
+  /// \c UINT_MAX, the rest of the input line should be marked, and another
+  /// \c InputAnnotation will continue it on the next line.
   unsigned InputFirstCol, InputLastCol;
   /// The marker to use.
   MarkerStyle Marker;
@@ -505,7 +507,6 @@ struct MarkerRange {
   Loc First;
   /// Location of the last marked character.
   Loc Last;
-  MarkerRange(Loc First, Loc Last) : First(First), Last(Last) {}
 
 public:
   /// Make an invalid range to be overwritten before being used.
@@ -513,14 +514,55 @@ struct MarkerRange {
   /// \p Range specifies the \a logical input range to be depicted by annotation
   /// markers \a drawn at the resulting \c MarkerRange.
   ///
-  /// If \p Range is an empty range, then the resulting \c MarkerRange is
-  /// expanded to a single character.  This avoids a missing marker for an empty
-  /// range, but it means the markers for a single-character range are
-  /// indistinguishable from markers for an empty range.
-  MarkerRange(const SourceMgr &SM, SMRange Range) {
-    // Range has an inclusive start as MarkerRange requires.
+  /// \a how that drawing depicts that logical input range is determined by
+  /// \p ShowExclusive.  The drawing specifies either:
+  /// - The \a inclusive start and end bounds of the logical input range if
+  ///   \p !ShowExclusive.  In this case:
+  ///   - If the logical input range is empty, then the resulting \c MarkerRange
+  ///     is expanded to a single character.  This avoids a missing marker, but
+  ///     it means the markers for a single-character range are
+  ///     indistinguishable from markers for an empty range.
+  ///   - The first and last location of the \c MarkerRange are always real
+  ///     locations in the input (never, for example, column 0).
+  /// - The \a exclusive start and end bounds of the logical input range if
+  ///   \p ShowExclusive.  In this case:
+  ///   - The \c MarkerRange length is then always at least two because
+  ///     exclusive boundaries never occupy the same location.
+  ///   - If a \p Range boundary is an input line boundary, the corresponding
+  ///     \c MarkerRange column might be in the line's margin (e.g., column 0)
+  ///     to avoid placing a marker on an adjacent line.  That decision can make
+  ///     input annotations more concise (more line-liners) and easier to read.
+  ///     It also avoids non-existent adjacent lines (e.g., line 0) that are not
+  ///     depicted in the input dump.
+  MarkerRange(const SourceMgr &SM, SMRange Range, bool ShowExclusive = false) {
+    // Given an SMRange representing the range of text "range of text", the
+    // following example compares how it and the resulting MarkerRange encode
+    // the same start (s) and end (e) bounds:
+    //
+    //     ....range of text....
+    //         s            e    SMRange
+    //         s           e     MarkerRange with ShowExclusive=false
+    //        s             e    MarkerRange with ShowExclusive=true
+    if (ShowExclusive) {
+      // Range has inclusive start, but ShowExclusive requires exclusive start.
+      First = SM.getLineAndColumn(Range.Start);
+      --First.Col;
+      // Range has an exclusive end as ShowExclusive requires.  If it is at a
+      // line boundary, it is at the start of the next line, so normally move it
+      // to the end of the previous line.  For an empty range, do not do that as
+      // we do not want an end marker on the line before the start marker.
+      if (Range.Start == Range.End) {
+        Last = SM.getLineAndColumn(Range.End);
+      } else {
+        SMLoc EndLoc = SMLoc::getFromPointer(Range.End.getPointer() - 1);
+        Last = SM.getLineAndColumn(EndLoc);
+        ++Last.Col;
+      }
+      return;
+    }
+    // Range has an inclusive start as !ShowExclusive requires.
     First = SM.getLineAndColumn(Range.Start);
-    // Range has an exclusive end, but MarkerRange requires an inclusive end.
+    // Range has an exclusive end, but !ShowExclusive requires an inclusive end.
     if (Range.Start == Range.End) {
       // Convert the empty range to a one-character range.
       Last = First;
@@ -532,14 +574,133 @@ struct MarkerRange {
       Last = SM.getLineAndColumn(EndLoc);
     }
   }
+  /// \p Loc specifies a single input character to be marked by a single
+  /// annotation marker character.
+  MarkerRange(Loc OneChar) : First(OneChar), Last(OneChar) {}
   /// Is the marker range contained on a single line?
   bool isSingleLine() const { return First.Line == Last.Line; }
   /// Get the location of the first marked character.
   Loc getFirstLoc() const { return First; }
   /// Get the location of the last marked character.
   Loc getLastLoc() const { return Last; }
-  /// Return a range marking only the first character.
-  MarkerRange truncate() const { return {First, First}; }
+};
+
+/// Emits search range annotations for each \c MatchResultDiag as it is
+/// encountered.
+///
+/// In some cases, it emits a single, one-line annotation.  Otherwise, it emits
+/// separate annotations for the start and end of the search range.  The logic
+/// for making this determination is encapsulated in static member functions.
+class SearchRangeAnnotator {
+private:
+  const SourceMgr &SM;
+  /// Where to append search range annotations.
+  std::vector<InputAnnotation> &Annotations;
+  /// A globally unique index for this annotation.
+  unsigned &LabelIndexGlobal;
+  /// The most recent \c MatchResultDiag, or \c nullptr if all search range
+  /// annotations have been added already for the most recent
+  /// \c MatchResultDiag.
+  const MatchResultDiag *MRD;
+  /// The labeler for \c MRD.  Stored by value as the original labeler might be
+  /// destroyed by the time we call \c endDiags here.
+  InputAnnotationLabeler Labeler;
+  /// Would a \c SearchRangeAnnotator make any search range annotations for
+  /// \p MRD?
+  static bool makesAnnotationsFor(const MatchResultDiag &MRD) {
+    return !MRD.getMatchRange() || MRD.isError();
+  }
+  /// Assuming \c makesAnnotationsFor(MRD), would a \c SearchRangeAnnotator make
+  /// a one-line search range annotation for \p MRD?  Either way, the search
+  /// range computed for \p MRD is stored in \p SearchRange.
+  static bool makesOneLinerFor(const SourceMgr &SM, const MatchResultDiag &MRD,
+                               MarkerRange &SearchRange) {
+    assert(makesAnnotationsFor(MRD) &&
+           "expected makesAnnotationsFor to be checked first");
+    SearchRange = {SM, MRD.getSearchRange(), /*ShowExclusive=*/true};
+    return SearchRange.isSingleLine();
+  }
+  /// Make the next annotation for the current \c MatchResultDiag.
+  void makeAnnotation(bool Start) {
+    InputAnnotation &A = Annotations.emplace_back();
+    A.LabelIndexGlobal = LabelIndexGlobal++;
+    Labeler.generateLabel(A.Label);
+    A.IsFirstLine = true;
+    A.FoundAndExpectedMatch = false;
+    MarkerRange SearchRange;
+    if (makesOneLinerFor(SM, *MRD, SearchRange)) {
+      assert(Start && "expected no search range end annotation for one-liner");
+      A.InputLine = SearchRange.getFirstLoc().Line;
+      A.InputFirstCol = SearchRange.getFirstLoc().Col;
+      A.InputLastCol = SearchRange.getLastLoc().Col;
+      MatchCustomNoteDiag NoteDiag("search range (exclusive bounds)");
+      NoteDiag.setMatchResultDiag(MRD);
+      A.Marker = getMarker(NoteDiag);
+      A.Marker.Head = '{';
+      A.Marker.Mid = ' ';
+      A.Marker.Tail = '}';
+      MRD = nullptr;
+      return;
+    }
+    // We have separate annotations for start and end.
+    MarkerRange::Loc Loc =
+        Start ? SearchRange.getFirstLoc() : SearchRange.getLastLoc();
+    A.InputLine = Loc.Line;
+    A.InputFirstCol = A.InputLastCol = Loc.Col;
+    MatchCustomNoteDiag NoteDiag(std::string("search range ") +
+                                 (Start ? "start" : "end") + " (exclusive)");
+    NoteDiag.setMatchResultDiag(MRD);
+    A.Marker = getMarker(NoteDiag);
+    A.Marker.Head = Start ? '{' : '}';
+  }
+
+public:
+  /// How many search range annotations would a \c SearchRangeAnnotator generate
+  /// for \c MRD?
+  static unsigned countAnnotationsFor(const SourceMgr &SM,
+                                      const MatchResultDiag &MRD) {
+    if (!makesAnnotationsFor(MRD))
+      return 0;
+    MarkerRange SearchRange;
+    return makesOneLinerFor(SM, MRD, SearchRange) ? 1 : 2;
+  }
+  /// Are the search range annotations generated by a \c SearchRangeAnnotator
+  /// sufficient for \p MRD?  Otherwise, \p MRD needs to be rendered as a
+  /// separate annotation.
+  static bool sufficesFor(const MatchResultDiag &MRD) {
+    return makesAnnotationsFor(MRD) && getMarker(MRD).Note.empty();
+  }
+  /// \p Annotations is where this annotator should append search range
+  /// annotations.  \p LabelIndexGlobal is the globally unique index of the next
+  /// annotation label to be generated.  This annotator will increment it when
+  /// generating a new label for a search range annotation.
+  SearchRangeAnnotator(const SourceMgr &SM,
+                       std::vector<InputAnnotation> &Annotations,
+                       unsigned &LabelIndexGlobal)
+      : SM(SM), Annotations(Annotations), LabelIndexGlobal(LabelIndexGlobal),
+        MRD(nullptr) {}
+  /// Emit any search range start annotation or one-line search range annotation
+  /// for \p MRDNew using its labeler \p LabelerNew.  This annotator will emit
+  /// any search range end annotation at the next call to \c newMatchResultDiag
+  /// or \c endDiags.
+  void newMatchResultDiag(const MatchResultDiag &MRDNew,
+                          InputAnnotationLabeler LabelerNew) {
+    if (MRD) {
+      makeAnnotation(/*Start=*/false);
+      MRD = nullptr;
+    }
+    if (makesAnnotationsFor(MRDNew)) {
+      MRD = &MRDNew;
+      Labeler = LabelerNew;
+      makeAnnotation(/*Start=*/true);
+    }
+  }
+  /// Emit any search range end annotation for the final \c MatchResultDiag
+  /// passed to \c newMatchResultDiag.
+  void endDiags() {
+    if (MRD)
+      makeAnnotation(/*Start=*/false);
+  }
 };
 } // namespace
 
@@ -560,12 +721,22 @@ buildInputAnnotations(const SourceMgr &SM, unsigned CheckFileBufferID,
   // series of zero or more MatchNoteDiag's.  Each such MatchResultDiag and its
   // MatchNoteDiag series can require multiple labels.
   std::map<SMLoc, unsigned, CompareSMLoc> LabelCountPerPattern;
-  for (const FileCheckDiag &Diag : Diags)
-    ++LabelCountPerPattern[Diag.getMatchResultDiag().getCheckLoc()];
+  for (const FileCheckDiag &Diag : Diags) {
+    unsigned &C = LabelCountPerPattern[Diag.getMatchResultDiag().getCheckLoc()];
+    if (const MatchResultDiag *MRD = dyn_cast<MatchResultDiag>(&Diag)) {
+      C += SearchRangeAnnotator::countAnnotationsFor(SM, *MRD);
+      if (!SearchRangeAnnotator::sufficesFor(*MRD))
+        ++C;
+    } else {
+      ++C;
+    }
+  }
   // How many labels have we generated so far per check pattern?
   std::map<SMLoc, unsigned, CompareSMLoc> LabelIndexPerPattern;
   // How many total labels have we generated so far?
   unsigned LabelIndexGlobal = 0;
+  SearchRangeAnnotator TheSearchRangeAnnotator(SM, Annotations,
+                                               LabelIndexGlobal);
   // What's the widest label we've generated so far?
   LabelWidthGlobal = 0;
   // The labeler for the current MatchResultDiag and its MatchNoteDiag series.
@@ -578,6 +749,9 @@ buildInputAnnotations(const SourceMgr &SM, unsigned CheckFileBufferID,
           LabelCountPerPattern[MRD->getCheckLoc()] > 1
               ? &LabelIndexPerPattern[MRD->getCheckLoc()]
               : nullptr);
+      TheSearchRangeAnnotator.newMatchResultDiag(*MRD, CurLabeler);
+      if (SearchRangeAnnotator::sufficesFor(*MRD))
+        continue;
     }
 
     // Build label that is unique for this input annotation before it is
@@ -596,22 +770,17 @@ buildInputAnnotations(const SourceMgr &SM, unsigned CheckFileBufferID,
         A.FoundAndExpectedMatch = true;
     }
 
-    // If Diag has a match range, position the marker there.  If it is a
-    // MatchNoneDiag, position the marker at its search range.  Otherwise,
+    // If Diag has a match range, position the marker there.  Otherwise,
     // position the marker at the start of the most recent MatchResultDiag, with
     // which it is associated.
     MarkerRange InputRange;
     if (Diag.getMatchRange()) {
       InputRange = MarkerRange(SM, *Diag.getMatchRange());
-    } else if (const MatchNoneDiag *MND = dyn_cast<MatchNoneDiag>(&Diag)) {
-      InputRange = MarkerRange(SM, MND->getSearchRange());
     } else {
-      assert(isa<MatchNoteDiag>(Diag) &&
-             "expected only MatchNoteDiag to have no input range");
       const MatchResultDiag &MRD = Diag.getMatchResultDiag();
       InputRange = MRD.getMatchRange() ? MarkerRange(SM, *MRD.getMatchRange())
                                        : MarkerRange(SM, MRD.getSearchRange());
-      InputRange = InputRange.truncate();
+      InputRange = MarkerRange(InputRange.getFirstLoc());
       assert(A.Marker.Head == ' ' && "expected no marker for no match range");
     }
 
@@ -647,6 +816,7 @@ buildInputAnnotations(const SourceMgr &SM, unsigned CheckFileBufferID,
       }
     }
   }
+  TheSearchRangeAnnotator.endDiags();
 }
 
 static unsigned FindInputLineInFilter(
@@ -864,17 +1034,20 @@ static void DumpAnnotatedInput(raw_ostream &OS, const FileCheckRequest &Req,
            AnnotationItr->InputLine == Line) {
       WithColor COS(*LineOS, AnnotationItr->Marker.Color, /*Bold=*/true,
                     /*BG=*/false, TheColorMode);
-      // The two spaces below are where the ": " appears on input lines.
-      COS << left_justify(AnnotationItr->Label, LabelWidthGlobal) << "  ";
+      // The space below aligns with the ":" on the input line.
+      COS << left_justify(AnnotationItr->Label, LabelWidthGlobal) << " ";
       unsigned Col;
-      for (Col = 1; Col < AnnotationItr->InputFirstCol; ++Col)
+      // A search range annotation at the beginning of the line starts at column
+      // 0 because it is an exclusive boundary.
+      for (Col = 0; Col < AnnotationItr->InputFirstCol; ++Col)
         COS << ' ';
       COS << AnnotationItr->Marker.Head;
       // If InputLastCol==UINT_MAX, stop at InputLineWidth.
       for (++Col; Col < AnnotationItr->InputLastCol && Col <= InputLineWidth;
            ++Col)
         COS << AnnotationItr->Marker.Mid;
-      if (Col <= AnnotationItr->InputLastCol && Col <= InputLineWidth) {
+      if (Col <= AnnotationItr->InputLastCol &&
+          AnnotationItr->InputLastCol != UINT_MAX) {
         COS << AnnotationItr->Marker.Tail;
         ++Col;
       }
@@ -884,7 +1057,7 @@ static void DumpAnnotatedInput(raw_ostream &OS, const FileCheckRequest &Req,
         // put the note right after the marker, subsequent annotations for the
         // same input line might appear to mark this note instead of the input
         // line.
-        for (; Col <= InputLineWidth; ++Col)
+        for (; Col <= InputLineWidth + 1; ++Col)
           COS << ' ';
         COS << ' ' << Note;
       }


        


More information about the llvm-commits mailing list