Index: llvm/lib/Support/FileCheck.cpp =================================================================== --- llvm/lib/Support/FileCheck.cpp +++ llvm/lib/Support/FileCheck.cpp @@ -1218,7 +1218,7 @@ Format.valueFromStringRepr(MatchedValue, SM); if (!Value) return Value.takeError(); - DefinedNumericVariable->setValue(*Value); + DefinedNumericVariable->setValue(*Value, MatchedValue); } // Like CHECK-NEXT, CHECK-EMPTY's match range is considered to start after @@ -1295,6 +1295,57 @@ } } +void Pattern::printVariableDefs(const SourceMgr &SM, StringRef Buffer, + FileCheckDiag::MatchType MatchTy, + std::vector *Diags) const { + if (VariableDefs.empty() && NumericVariableDefs.empty()) + return; + // Build list of variable captures. + struct VarCapture { + StringRef Name; + SMRange Range; + }; + SmallVector VarCaptures; + for (const auto &VariableDef : VariableDefs) { + VarCapture VC; + VC.Name = VariableDef.first; + StringRef Value = Context->GlobalVariableTable[VC.Name]; + SMLoc Start = SMLoc::getFromPointer(Value.data()); + SMLoc End = SMLoc::getFromPointer(Value.data() + Value.size()); + VC.Range = SMRange(Start, End); + VarCaptures.push_back(VC); + } + for (const auto &VariableDef : NumericVariableDefs) { + VarCapture VC; + VC.Name = VariableDef.getKey(); + StringRef StrValue = VariableDef.getValue() + .DefinedNumericVariable->getStringValue() + .getValue(); + SMLoc Start = SMLoc::getFromPointer(StrValue.data()); + SMLoc End = SMLoc::getFromPointer(StrValue.data() + StrValue.size()); + VC.Range = SMRange(Start, End); + VarCaptures.push_back(VC); + } + // Sort variable captures by the order in which they matched the input. + // Ranges shouldn't be overlapping, so we can just compare the start. + std::sort(VarCaptures.begin(), VarCaptures.end(), + [](const VarCapture &A, const VarCapture &B) { + assert(A.Range.Start != B.Range.Start && + "unexpected overlapping variable captures"); + return A.Range.Start.getPointer() < B.Range.Start.getPointer(); + }); + // Create notes for the sorted captures. + for (const VarCapture &VC : VarCaptures) { + SmallString<256> Msg; + raw_svector_ostream OS(Msg); + OS << "captured var \"" << VC.Name << "\""; + if (Diags) + Diags->emplace_back(SM, CheckTy, getLoc(), MatchTy, VC.Range, OS.str()); + else + SM.PrintMessage(VC.Range.Start, SourceMgr::DK_Note, OS.str(), VC.Range); + } +} + static SMRange ProcessMatchResult(FileCheckDiag::MatchType MatchTy, const SourceMgr &SM, SMLoc Loc, Check::FileCheckType CheckTy, @@ -1876,8 +1927,10 @@ : FileCheckDiag::MatchFoundButExcluded; SMRange MatchRange = ProcessMatchResult(MatchTy, SM, Loc, Pat.getCheckTy(), Buffer, MatchPos, MatchLen, Diags); - if (Diags) + if (Diags) { Pat.printSubstitutions(SM, Buffer, MatchRange, MatchTy, Diags); + Pat.printVariableDefs(SM, Buffer, MatchTy, Diags); + } if (!PrintDiag) return; @@ -1893,6 +1946,7 @@ SM.PrintMessage(MatchRange.Start, SourceMgr::DK_Note, "found here", {MatchRange}); Pat.printSubstitutions(SM, Buffer, MatchRange, MatchTy, nullptr); + Pat.printVariableDefs(SM, Buffer, MatchTy, nullptr); } static void PrintMatch(bool ExpectedMatch, const SourceMgr &SM, Index: llvm/lib/Support/FileCheckImpl.h =================================================================== --- llvm/lib/Support/FileCheckImpl.h +++ llvm/lib/Support/FileCheckImpl.h @@ -261,6 +261,10 @@ /// Value of numeric variable, if defined, or None otherwise. Optional Value; + /// The input buffer's string from which Value was parsed, or None if the + /// value is not yet defined or was not parsed from the input buffer. + Optional StrValue; + /// Line number where this variable is defined, or None if defined before /// input is parsed. Used to determine whether a variable is defined on the /// same line as a given use. @@ -284,12 +288,26 @@ /// \returns this variable's value. Optional getValue() const { return Value; } - /// Sets value of this numeric variable to \p NewValue. - void setValue(ExpressionValue NewValue) { Value = NewValue; } + /// \returns the input buffer's string from which this variable's value was + /// parsed, or None if the value is not yet defined or was not parsed from the + /// input buffer. + Optional getStringValue() const { return StrValue; } + + /// Sets value of this numeric variable to \p NewValue, and sets the input + /// buffer string from it was parsed to \p NewStrValue, which can be None if + /// it wasn't parsed from the input buffer. + void setValue(ExpressionValue NewValue, + Optional NewStrValue = None) { + Value = NewValue; + StrValue = NewStrValue; + } /// Clears value of this numeric variable, regardless of whether it is /// currently defined or not. - void clearValue() { Value = None; } + void clearValue() { + Value = None; + StrValue = None; + } /// \returns the line number where this variable is defined, if any, or None /// if defined before input is parsed. @@ -691,6 +709,9 @@ bool hasVariable() const { return !(Substitutions.empty() && VariableDefs.empty()); } + void printVariableDefs(const SourceMgr &SM, StringRef Buffer, + FileCheckDiag::MatchType MatchTy, + std::vector *Diags) const; Check::FileCheckType getCheckTy() const { return CheckTy; } Index: llvm/test/FileCheck/dump-input-annotations.txt =================================================================== --- llvm/test/FileCheck/dump-input-annotations.txt +++ llvm/test/FileCheck/dump-input-annotations.txt @@ -665,12 +665,64 @@ ; SUBST-NEG-NEXT:>>>>>> ;-------------------------------------------------- -; Substitutions: CHECK-NEXT, CHECK-SAME, CHECK-DAG fixups. +; Captured variables +;-------------------------------------------------- + +; RUN: echo 'strvar: foo' > %t.in +; RUN: echo 'numvar no expr: 51' >> %t.in +; RUN: echo 'numvar expr: -49' >> %t.in +; RUN: echo 'many: foo 100 8 bar' >> %t.in +; RUN: echo 'var in neg match: foo' >> %t.in +; RUN: echo 'END' >> %t.in + +; RUN: echo 'CHECK: strvar: [[STRVAR:[a-z]+]]' > %t.chk +; RUN: echo 'CHECK: numvar no expr: [[#NUMVAR_NO_EXPR:]]' >> %t.chk +; RUN: echo 'CHECK: numvar expr: [[#%d,NUMVAR_EXPR:2-NUMVAR_NO_EXPR]]' >> %t.chk + +; Capture many variables of different kinds in a different order than their +; names sort alphabetically to ensure they're sorted in capture order. +; RUN: echo 'CHECK: many: [[VAR1:foo]] [[#%d,VAR3:]] [[#VAR2:]] [[VAR4:bar]]' \ +; RUN: >> %t.chk + +; RUN: echo 'CHECK-NOT: var in neg match: [[VAR:foo]]' >> %t.chk +; RUN: echo 'CHECK: END' >> %t.chk + +; RUN: %ProtectFileCheckOutput \ +; RUN: not FileCheck -dump-input=always -vv -input-file=%t.in %t.chk 2>&1 \ +; RUN: | FileCheck -strict-whitespace -match-full-lines %s -check-prefix=CAPTURE-NEG + +; CAPTURE-NEG:<<<<<< +; CAPTURE-NEG-NEXT: 1: strvar: foo +; CAPTURE-NEG-NEXT:check:1'0 ^~~~~~~~~~~ +; CAPTURE-NEG-NEXT:check:1'1 ^~~ captured var "STRVAR" +; CAPTURE-NEG-NEXT: 2: numvar no expr: 51 +; CAPTURE-NEG-NEXT:check:2'0 ^~~~~~~~~~~~~~~~~~ +; CAPTURE-NEG-NEXT:check:2'1 ^~ captured var "NUMVAR_NO_EXPR" +; CAPTURE-NEG-NEXT: 3: numvar expr: -49 +; CAPTURE-NEG-NEXT:check:3'0 ^~~~~~~~~~~~~~~~ +; CAPTURE-NEG-NEXT:check:3'1 with "%d,NUMVAR_EXPR:2-NUMVAR_NO_EXPR" equal to "-49" +; CAPTURE-NEG-NEXT:check:3'2 ^~~ captured var "NUMVAR_EXPR" +; CAPTURE-NEG-NEXT: 4: many: foo 100 8 bar +; CAPTURE-NEG-NEXT:check:4'0 ^~~~~~~~~~~~~~~~~~~ +; CAPTURE-NEG-NEXT:check:4'1 ^~~ captured var "VAR1" +; CAPTURE-NEG-NEXT:check:4'2 ^~~ captured var "VAR3" +; CAPTURE-NEG-NEXT:check:4'3 ^ captured var "VAR2" +; CAPTURE-NEG-NEXT:check:4'4 ^~~ captured var "VAR4" +; 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: 6: END +; CAPTURE-NEG-NEXT:check:6 ^~~ +; CAPTURE-NEG-NEXT:>>>>>> + +;-------------------------------------------------- +; CHECK-NEXT, CHECK-SAME, CHECK-DAG note fixups. ; ; When CHECK-NEXT or CHECK-SAME fails for the wrong line, or when a CHECK-DAG ; match is discarded, the associated diagnostic type must be converted from -; successful to failed or discarded. However, any substitution diagnostics must -; be traversed to find that diagnostic. +; successful to failed or discarded. However, any note annotation must be +; traversed to find that diagnostic. We check this behavior here only for +; substitutions, but it's the same mechanism for all note annotations. ;-------------------------------------------------- ;- - - - - - - - - - - - - - - - - - - - - - - - - Index: llvm/test/FileCheck/verbose.txt =================================================================== --- llvm/test/FileCheck/verbose.txt +++ llvm/test/FileCheck/verbose.txt @@ -55,6 +55,31 @@ VV-NEXT: {{^}}bar{{$}} VV-NEXT: {{^}}^{{$}} +STRVAR=foobar +STRVAR:foobar +CHECK: STRVAR=[[STRVAR:[a-z]+]] +CHECK-NEXT: STRVAR:[[STRVAR]] + + V: verbose.txt:[[#@LINE-3]]:8: remark: {{C}}HECK: expected string found in input +V-NEXT: {{C}}HECK: {{STRVAR=\[\[STRVAR:\[a-z\]\+\]\]}} +V-NEXT: {{^}} ^{{$}} +V-NEXT: verbose.txt:[[#@LINE-8]]:1: note: found here +V-NEXT: {{^}}STRVAR=foobar{{$}} +V-NEXT: {{^}}^~~~~~~~~~~~~{{$}} +V-NEXT: verbose.txt:[[#@LINE-11]]:8: note: captured var "STRVAR" +V-NEXT: {{^}}STRVAR=foobar{{$}} +V-NEXT: {{^}} ^~~~~~{{$}} + +V-NEXT: verbose.txt:[[#@LINE-12]]:13: remark: {{C}}HECK-NEXT: expected string found in input +V-NEXT: {{C}}HECK-NEXT: {{STRVAR:\[\[STRVAR\]\]}} +V-NEXT: {{^}} ^{{$}} +V-NEXT: verbose.txt:[[#@LINE-17]]:1: note: found here +V-NEXT: {{^}}STRVAR:foobar{{$}} +V-NEXT: {{^}}^~~~~~~~~~~~~{{$}} +V-NEXT: verbose.txt:[[#@LINE-20]]:1: note: with "STRVAR" equal to "foobar" +V-NEXT: {{^}}STRVAR:foobar{{$}} +V-NEXT: {{^}}^{{$}} + NUMVAR=42 NUMVAR - 1:41 CHECK: NUMVAR=[[#NUMVAR:]] @@ -67,21 +92,24 @@ V-NEXT: verbose.txt:[[#@LINE-9]]:1: note: found here V-NEXT: {{^}}NUMVAR=42{{$}} V-NEXT: {{^}}^~~~~~~~~{{$}} +V-NEXT: verbose.txt:[[#@LINE-12]]:8: note: captured var "NUMVAR" +V-NEXT: NUMVAR=42 +V-NEXT: ^~ -V-NEXT: verbose.txt:[[#@LINE-9]]:13: remark: {{C}}HECK-NEXT: expected string found in input +V-NEXT: verbose.txt:[[#@LINE-12]]:13: remark: {{C}}HECK-NEXT: expected string found in input V-NEXT: {{C}}HECK-NEXT: {{NUMVAR - 1:[[][[]#NUMVAR - 1[]][]]$}} V-NEXT: {{^}} ^{{$}} -V-NEXT: verbose.txt:[[#@LINE-15]]:1: note: found here +V-NEXT: verbose.txt:[[#@LINE-18]]:1: note: found here V-NEXT: {{^}}NUMVAR - 1:41{{$}} V-NEXT: {{^}}^~~~~~~~~~~~~{{$}} -V-NEXT: verbose.txt:[[#@LINE-18]]:1: note: with "NUMVAR - 1" equal to "41" +V-NEXT: verbose.txt:[[#@LINE-21]]:1: note: with "NUMVAR - 1" equal to "41" V-NEXT: {{^}}NUMVAR - 1:41{{$}} V-NEXT: {{^}}^{{$}} -VV-NEXT: verbose.txt:[[#@LINE-20]]:12: remark: {{C}}HECK-NOT: excluded string not found in input +VV-NEXT: verbose.txt:[[#@LINE-23]]:12: remark: {{C}}HECK-NOT: excluded string not found in input VV-NEXT: {{C}}HECK-NOT: {{[[][[]#NUMVAR [+] 1[]][]]$}} VV-NEXT: {{^}} ^{{$}} -VV-NEXT: verbose.txt:[[#@LINE-25]]:1: note: scanning from here +VV-NEXT: verbose.txt:[[#@LINE-28]]:1: note: scanning from here VV-NEXT: {{^}}NUMVAR - 1:41{{$}} VV-NEXT: {{^}}^{{$}}