diff --git a/llvm/test/FileCheck/dump-input-annotations.txt b/llvm/test/FileCheck/dump-input-annotations.txt --- a/llvm/test/FileCheck/dump-input-annotations.txt +++ b/llvm/test/FileCheck/dump-input-annotations.txt @@ -280,8 +280,8 @@ ; EMP-NEXT: 3: world ; EMP-NEXT: empty:3 X~~~~ error: no match found ; EMP-NEXT: 4: label -; EMP-NEXT: empty:3 ~~~~~ ; EMP-V-NEXT: label:4 ^~~~~ +; EMP-NEXT: empty:3 ~~~~~ ; EMP-NEXT: >>>>>> ; EMP-NOT: {{.}} @@ -387,8 +387,8 @@ ; NOT2-VV-NEXT: not:1 ~~~~~ ; NOT2-NEXT: not:2 !~~~~ error: no match expected ; NOT2-NEXT: 3: again -; NOT2-VV-NEXT: not:1 ~~ ; NOT2-V-NEXT: check:3 ^~~ +; NOT2-VV-NEXT: not:1 ~~ ; NOT2-NEXT: >>>>>> ; NOT2-NOT: {{.}} @@ -446,6 +446,53 @@ ; DAG-NEXT: >>>>>> ; DAG-NOT: {{.}} +; Check sorting of annotations when the order of diagnostics across an input +; line is different than the order of the associated directives in the check +; file. Try cases when diagnostics' input ranges overlap but are not +; identical to check how that affects sorting. + +; RUN: echo 'abc def abc def' > %t.in + +; RUN: echo 'CHECK-DAG: def' > %t.chk +; RUN: echo 'CHECK-DAG: bc' >> %t.chk +; RUN: echo 'CHECK-DAG: abc' >> %t.chk +; RUN: echo 'CHECK-DAG: de' >> %t.chk +; RUN: echo 'CHECK-DAG: def' >> %t.chk + +; RUN: %ProtectFileCheckOutput \ +; RUN: not FileCheck -dump-input=always -input-file %t.in %t.chk 2>&1 \ +; RUN: | FileCheck -match-full-lines %s -check-prefixes=DAG1L,DAG1L-Q \ +; RUN: -implicit-check-not='remark:' +; RUN: %ProtectFileCheckOutput \ +; RUN: not FileCheck -dump-input=always -input-file %t.in %t.chk -v 2>&1 \ +; RUN: | FileCheck -match-full-lines %s -check-prefixes=DAG1L,DAG1L-V,DAG1L-VQ \ +; RUN: -implicit-check-not='remark:' +; RUN: %ProtectFileCheckOutput \ +; RUN: not FileCheck -dump-input=always -input-file %t.in %t.chk -vv 2>&1 \ +; RUN: | FileCheck -match-full-lines %s -check-prefixes=DAG1L,DAG1L-V,DAG1L-VV \ +; RUN: -implicit-check-not='remark:' + +; Verbose diagnostics are suppressed but not errors. +; DAG1L:{{.*}}error:{{.*}} + +; DAG1L:<<<<<< +; DAG1L-NEXT: 1: abc def abc def +; DAG1L-V-NEXT:dag:1 ^~~ +; DAG1L-V-NEXT:dag:2 ^~ +; DAG1L-VV-NEXT:dag:3'0 !~~ discard: overlaps earlier match +; DAG1L-VQ-NEXT:dag:3 ^~~ +; DAG1L-VV-NEXT:dag:3'1 ^~~ +; DAG1L-VV-NEXT:dag:4'0 !~ discard: overlaps earlier match +; DAG1L-VQ-NEXT:dag:4 ^~ +; 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-Q-NEXT:dag:5 X error: no match found +; DAG1L-VQ-NEXT:dag:5 X error: no match found +; DAG1L-VV-NEXT:dag:5'2 X error: no match found +; DAG1L-NEXT:>>>>>> +; DAG1L-NOT:{{.}} + ;-------------------------------------------------- ; CHECK-LABEL ; @@ -536,22 +583,19 @@ ; Verbose diagnostics are suppressed but not errors. ; IMPNOT:{{.*}}error:{{.*}} -; FIXME: All occurrences of imp1, imp2, and imp3 are sorting after the first -; directive. They should instead be sorted by when they execute. - ; IMPNOT:<<<<<< ; IMPNOT-NEXT: 1: hello world again! ; IMPNOT-V-NEXT:check:1 ^~~ ; IMPNOT-VV-NEXT:not:imp1 X ; IMPNOT-VV-NEXT:not:imp2 X ; IMPNOT-VV-NEXT:not:imp3 X +; IMPNOT-V-NEXT:check:2 ^~~ ; IMPNOT-VV-NEXT:not:imp1 X~~ ; IMPNOT-VV-NEXT:not:imp2 X~~ ; IMPNOT-VV-NEXT:not:imp3 X~~ +; IMPNOT-V-NEXT:check:3 ^ ; IMPNOT-VV-NEXT:not:imp1 X~~~~~~~ ; IMPNOT-VV-NEXT:not:imp2 X~~~~~~~ ; IMPNOT-NEXT:not:imp3 !~~~~ error: no match expected -; IMPNOT-V-NEXT:check:2 ^~~ -; IMPNOT-V-NEXT:check:3 ^ ; IMPNOT-NEXT:>>>>>> ; IMPNOT-NOT:{{.}} diff --git a/llvm/utils/FileCheck/FileCheck.cpp b/llvm/utils/FileCheck/FileCheck.cpp --- a/llvm/utils/FileCheck/FileCheck.cpp +++ b/llvm/utils/FileCheck/FileCheck.cpp @@ -241,11 +241,8 @@ /// An annotation for a single input line. struct InputAnnotation { - /// The check file line (one-origin indexing) where the directive that - /// produced this annotation is located. - unsigned CheckLine; - /// The index of the match result for this check. - unsigned CheckDiagIndex; + /// The index of the match result across all checks + unsigned DiagIndex; /// The label for this annotation. std::string Label; /// What input line (one-origin indexing) this annotation marks. This might @@ -253,7 +250,7 @@ /// a non-initial fragment of a diagnostic that has been broken across /// multiple lines. unsigned InputLine; - /// The column range (one-origin indexing, open end) in which to to mark the + /// The column range (one-origin indexing, open end) in which to mark the /// input line. If InputEndCol is UINT_MAX, treat it as the last column /// before the newline. unsigned InputStartCol, InputEndCol; @@ -300,6 +297,8 @@ const std::vector &Diags, std::vector &Annotations, unsigned &LabelWidth) { + // How many diagnostics have we seen so far? + unsigned DiagCount = 0; // How many diagnostics has the current check seen so far? unsigned CheckDiagCount = 0; // What's the widest label? @@ -307,12 +306,12 @@ for (auto DiagItr = Diags.begin(), DiagEnd = Diags.end(); DiagItr != DiagEnd; ++DiagItr) { InputAnnotation A; + A.DiagIndex = DiagCount++; // Build label, which uniquely identifies this check result. unsigned CheckBufferID = SM.FindBufferContainingLoc(DiagItr->CheckLoc); auto CheckLineAndCol = SM.getLineAndColumn(DiagItr->CheckLoc, CheckBufferID); - A.CheckLine = CheckLineAndCol.first; llvm::raw_string_ostream Label(A.Label); Label << GetCheckTypeAbbreviation(DiagItr->CheckTy) << ":"; if (CheckBufferID == CheckFileBufferID) @@ -323,19 +322,17 @@ else llvm_unreachable("expected diagnostic's check location to be either in " "the check file or for an implicit pattern"); - A.CheckDiagIndex = UINT_MAX; + unsigned CheckDiagIndex = UINT_MAX; auto DiagNext = std::next(DiagItr); if (DiagNext != DiagEnd && DiagItr->CheckTy == DiagNext->CheckTy && DiagItr->CheckLoc == DiagNext->CheckLoc) - A.CheckDiagIndex = CheckDiagCount++; + CheckDiagIndex = CheckDiagCount++; else if (CheckDiagCount) { - A.CheckDiagIndex = CheckDiagCount; + CheckDiagIndex = CheckDiagCount; CheckDiagCount = 0; } - if (A.CheckDiagIndex != UINT_MAX) - Label << "'" << A.CheckDiagIndex; - else - A.CheckDiagIndex = 0; + if (CheckDiagIndex != UINT_MAX) + Label << "'" << CheckDiagIndex; Label.flush(); LabelWidth = std::max((std::string::size_type)LabelWidth, A.Label.size()); @@ -366,8 +363,7 @@ if (DiagItr->InputEndCol == 1 && L == E) break; InputAnnotation B; - B.CheckLine = A.CheckLine; - B.CheckDiagIndex = A.CheckDiagIndex; + B.DiagIndex = A.DiagIndex; B.Label = A.Label; B.InputLine = L; B.Marker = A.Marker; @@ -392,35 +388,53 @@ OS << "Full input was:\n<<<<<<\n"; // Sort annotations. - // - // First, sort in the order of input lines to make it easier to find relevant - // annotations while iterating input lines in the implementation below. - // FileCheck diagnostics are not always reported and recorded in the order of - // input lines due to, for example, CHECK-DAG and CHECK-NOT. - // - // Second, for annotations for the same input line, sort in the order of the - // FileCheck directive's line in the check file (where there's at most one - // directive per line) and then by the index of the match result for that - // directive. The rationale of this choice is that, for any input line, this - // sort establishes a total order of annotations that, with respect to match - // results, is consistent across multiple lines, thus making match results - // easier to track from one line to the next when they span multiple lines. std::sort(Annotations.begin(), Annotations.end(), [](const InputAnnotation &A, const InputAnnotation &B) { + // 1. Sort annotations in the order of the input lines. + // + // This makes it easier to find relevant annotations while + // iterating input lines in the implementation below. FileCheck + // does not always produce diagnostics in the order of input + // lines due to, for example, CHECK-DAG and CHECK-NOT. if (A.InputLine != B.InputLine) return A.InputLine < B.InputLine; - if (A.CheckLine != B.CheckLine) - return A.CheckLine < B.CheckLine; - // FIXME: Sometimes CHECK-LABEL reports its match twice with - // other diagnostics in between, and then diag index incrementing - // fails to work properly, and then this assert fails. We should - // suppress one of those diagnostics or do a better job of - // computing this index. For now, we just produce a redundant - // CHECK-LABEL annotation. - // assert(A.CheckDiagIndex != B.CheckDiagIndex && - // "expected diagnostic indices to be unique within a " - // " check line"); - return A.CheckDiagIndex < B.CheckDiagIndex; + // 2. Sort annotations in the temporal order FileCheck produced + // their associated diagnostics. + // + // This sort offers several benefits: + // + // A. On a single input line, the order of annotations reflects + // the FileCheck logic for processing directives/patterns. + // This can be helpful in understanding cases in which the + // order of the associated directives/patterns in the check + // file or on the command line either (i) does not match the + // temporal order in which FileCheck looks for matches for the + // directives/patterns (due to, for example, CHECK-LABEL, + // CHECK-NOT, or `--implicit-check-not`) or (ii) does match + // that order but does not match the order of those + // diagnostics along an input line (due to, for example, + // CHECK-DAG). + // + // On the other hand, because our presentation format presents + // input lines in order, there's no clear way to offer the + // same benefit across input lines. For consistency, it might + // then seem worthwhile to have annotations on a single line + // also sorted in input order (that is, by input column). + // However, in practice, this appears to be more confusing + // than helpful. Perhaps it's intuitive to expect annotations + // to be listed in the temporal order in which they were + // produced except in cases the presentation format obviously + // and inherently cannot support it (that is, across input + // lines). + // + // B. When diagnostics' annotations are split among multiple + // input lines, the user must track them from one input line + // to the next. One property of the sort chosen here is that + // it facilitates the user in this regard by ensuring the + // following: when comparing any two input lines, a + // diagnostic's annotations are sorted in the same position + // relative to all other diagnostics' annotations. + return A.DiagIndex < B.DiagIndex; }); // Compute the width of the label column.