Index: llvm/include/llvm/FileCheck/FileCheck.h =================================================================== --- llvm/include/llvm/FileCheck/FileCheck.h +++ llvm/include/llvm/FileCheck/FileCheck.h @@ -13,7 +13,9 @@ #ifndef LLVM_FILECHECK_FILECHECK_H #define LLVM_FILECHECK_FILECHECK_H +#include "llvm/ADT/Optional.h" #include "llvm/ADT/StringRef.h" +#include "llvm/Support/Casting.h" #include "llvm/Support/Regex.h" #include "llvm/Support/SMLoc.h" #include @@ -109,67 +111,323 @@ }; } // namespace Check -/// Summary of a FileCheck diagnostic. -struct FileCheckDiag { - /// What is the FileCheck directive for this diagnostic? +class MatchResultDiag; + +/// Abstract base class for recording a FileCheck diagnostic for a pattern +/// (e.g., \c CHECK-NEXT directive or \c --implicit-check-not). +/// +/// \c FileCheckDiag has two direct derived classes: +/// - \c MatchResultDiag records a match result for a pattern. There might be +/// more than one for a single pattern. For example, there might be several +/// discarded matches before either a good match or a failure to match. +/// - \c MatchNoteDiag provides an additional note about the most recent +/// \c MatchResultDiag emitted by a FileCheck invocation. For example, there +/// might be a fuzzy match after a failure to match. +/// +/// Throughout this class hierarchy, a pattern is said to be either expected or +/// excluded depending on whether the pattern must have or must not have a match +/// in order for it to succeed. For example, a \c CHECK directive's pattern is +/// expected, and a \c CHECK-NOT directive's pattern is excluded. +class FileCheckDiag { +public: + enum FileCheckDiagKind { + // MatchResultDiag + FCDK_MatchResultDiag_First, + FCDK_MatchFoundDiag = FCDK_MatchResultDiag_First, + FCDK_MatchNoneDiag, + FCDK_MatchResultDiag_Last = FCDK_MatchNoneDiag, + // MatchNoteDiag + FCDK_MatchNoteDiag_First, + FCDK_MatchFuzzyDiag = FCDK_MatchNoteDiag_First, + FCDK_MatchCustomNoteDiag, + FCDK_MatchNoteDiag_Last = FCDK_MatchCustomNoteDiag + }; + +private: + const FileCheckDiagKind Kind; + +public: + FileCheckDiag(FileCheckDiagKind Kind) : Kind(Kind) {} + /// Destructor is purely virtual to ensure this remains an abstract class. + virtual ~FileCheckDiag() = 0; + /// Of what derived class is this an instance? + FileCheckDiagKind getKind() const { return Kind; } + /// If this is a \c MatchResultDiag, return itself. If this is a + /// \c MatchNoteDiag, return its associated \c MatchResultDiag. + virtual const MatchResultDiag &getMatchResultDiag() const = 0; + /// Does this diagnostic reveal an error? + /// + /// For \c MatchResultDiag, \c !isError() is not always the same as a + /// successful pattern match result. For \c MatchNoteDiag, \c !isError() + /// does not indicate the lack of an error but rather the lack of an + /// additional error beyond its associated \c MatchResultDiag. See + /// documentation on derived types for details. + virtual bool isError() const = 0; + /// Return the input range for which this diagnostic indicates text that was + /// matched in some way (e.g., successful pattern match, discarded pattern + /// match, or variable capture), or return \c llvm::None if the diagnostic has + /// no such input range. + virtual Optional getMatchRange() const = 0; +}; + +/// Abstract base class for recording a FileCheck diagnostic that reports a +/// match result for a pattern. +class MatchResultDiag : public FileCheckDiag { +private: Check::FileCheckType CheckTy; - /// Where is the FileCheck directive for this diagnostic? SMLoc CheckLoc; - /// What type of match result does this diagnostic describe? - /// - /// A directive's supplied pattern is said to be either expected or excluded - /// depending on whether the pattern must have or must not have a match in - /// order for the directive to succeed. For example, a CHECK directive's - /// pattern is expected, and a CHECK-NOT directive's pattern is excluded. - /// - /// There might be more than one match result for a single pattern. For - /// example, there might be several discarded matches - /// (MatchFoundButDiscarded) before either a good match - /// (MatchFoundAndExpected) or a failure to match (MatchNoneButExpected), - /// and there might be a fuzzy match (MatchFuzzy) after the latter. - enum MatchType { + SMRange SearchRange; + +public: + MatchResultDiag(FileCheckDiagKind Kind, const Check::FileCheckType &CheckTy, + SMLoc CheckLoc, SMRange SearchRange) + : FileCheckDiag(Kind), CheckTy(CheckTy), CheckLoc(CheckLoc), + SearchRange(SearchRange) {} + /// Destructor is purely virtual to ensure this remains an abstract class. + virtual ~MatchResultDiag() = 0; + /// Is \p FCD an instance of \c MatchResultDiag? + static bool classof(const FileCheckDiag *FCD) { + FileCheckDiagKind FCDK = FCD->getKind(); + return FCDK_MatchResultDiag_First <= FCDK && + FCDK <= FCDK_MatchResultDiag_Last; + } + /// Get itself. + virtual const MatchResultDiag &getMatchResultDiag() const { return *this; } + /// What is the type of pattern for this match result? + Check::FileCheckType getCheckTy() const { return CheckTy; } + /// Where is the pattern for this match result? + SMLoc getCheckLoc() const { return CheckLoc; } + /// What is the search range for the match result? + SMRange getSearchRange() const { return SearchRange; } +}; + +/// \c MatchResultDiag for a pattern that matched the input. +class MatchFoundDiag : public MatchResultDiag { +public: + enum StatusTy { /// Indicates a good match for an expected pattern. - MatchFoundAndExpected, - /// Indicates a match for an excluded pattern. - MatchFoundButExcluded, + Success, + /// Indicates a match for an excluded pattern (error). + Excluded, /// Indicates a match for an expected pattern, but the match is on the - /// wrong line. - MatchFoundButWrongLine, - /// Indicates a discarded match for an expected pattern. - MatchFoundButDiscarded, - /// Indicates an error while processing a match after the match was found - /// for an expected or excluded pattern. The error is specified by \c Note, - /// to which it should be appropriate to prepend "error: " later. The full - /// match itself should be recorded in a preceding diagnostic of a different - /// \c MatchFound match type. - MatchFoundErrorNote, + /// wrong line (error). + WrongLine, + /// Indicates a discarded match for an expected pattern (not an error). + Discarded + }; + +private: + StatusTy Status; + SMRange MatchRange; + +public: + MatchFoundDiag(const Check::FileCheckType &CheckTy, SMLoc CheckLoc, + StatusTy Status, SMRange MatchRange, SMRange SearchRange) + : MatchResultDiag(FCDK_MatchFoundDiag, CheckTy, CheckLoc, SearchRange), + Status(Status), MatchRange(MatchRange) {} + /// Is \p FCD an instance of \c MatchFoundDiag? + static bool classof(const FileCheckDiag *FCD) { + return FCD->getKind() == FCDK_MatchFoundDiag; + } + /// Does this match produce an error? + /// + /// This is not always the same as \c getStatus()!=Success. For example, + /// \c CHECK-DAG discarded matches are neither successful matches nor errors. + virtual bool isError() const { + return Status != Success && Status != Discarded; + } + /// Was this a successful match? If not, why not? + /// + /// See \c isError comments for the relationship between the two. + StatusTy getStatus() const { return Status; } + /// Adjust a successful status to a non-successful status. + /// + /// This is designed to be called while emitting diagnostics. It is not + /// designed to be called by a diagnostic presentation layer like + /// `-dump-input`. + /// + /// For example, a match that was originally thought to be successful might + /// later be discarded, or it might be determined that it violates a matching + /// constraint (e.g., wrong line). + void markUnsuccessful(StatusTy S) { + assert(Status == Success && S != Success && + "expected to change successful status to unsuccessful"); + Status = S; + } + /// Return the match's input range, never \c llvm::None. + virtual Optional getMatchRange() const { return MatchRange; } +}; + +/// \c MatchResultDiag for a pattern that did not match the input. +class MatchNoneDiag : public MatchResultDiag { +public: + enum StatusTy { /// Indicates no match for an excluded pattern. - MatchNoneAndExcluded, - /// Indicates no match for an expected pattern, but this might follow good - /// matches when multiple matches are expected for the pattern, or it might - /// follow discarded matches for the pattern. - MatchNoneButExpected, + Success, /// Indicates no match due to an expected or excluded pattern that has - /// proven to be invalid at match time. The exact problems are usually - /// reported in subsequent diagnostics of the same match type but with - /// \c Note set. - MatchNoneForInvalidPattern, - /// Indicates a fuzzy match that serves as a suggestion for the next - /// intended match for an expected pattern with too few or no good matches. - MatchFuzzy, - } MatchTy; - /// The search range if MatchTy starts with MatchNone, or the match range - /// otherwise. - unsigned InputStartLine; - unsigned InputStartCol; - unsigned InputEndLine; - unsigned InputEndCol; - /// A note to replace the one normally indicated by MatchTy, or the empty - /// string if none. + /// proven to be invalid at match time (error). The exact problems are + /// usually reported in subsequent \c MatchNoteDiag objects. + InvalidPattern, + /// Indicates no match for an expected pattern (error). This might follow + /// good matches when multiple matches are expected for the pattern, or it + /// might follow discarded matches for the pattern. + Expected + }; + +private: + StatusTy Status; + +public: + MatchNoneDiag(const Check::FileCheckType &CheckTy, SMLoc CheckLoc, + StatusTy Status, SMRange SearchRange) + : MatchResultDiag(FCDK_MatchNoneDiag, CheckTy, CheckLoc, SearchRange), + Status(Status) {} + /// Is \p FCD an instance of \c MatchNoneDiag? + static bool classof(const FileCheckDiag *FCD) { + return FCD->getKind() == FCDK_MatchNoneDiag; + } + /// Does the lack of match represent an error? + virtual bool isError() const { return Status != Success; } + /// Does the lack of a match indicate a success? If not, why not? + StatusTy getStatus() const { return Status; } + /// Return \c llvm::None. + virtual Optional getMatchRange() const { return None; } +}; + +/// Abstract base class for recording a FileCheck diagnostic that provides an +/// additional note (possibly an additional error) about the most recent +/// \c MatchResultDiag. +class MatchNoteDiag : public FileCheckDiag { +private: + MatchResultDiag *MRD; + +public: + MatchNoteDiag(FileCheckDiagKind FCDK) : FileCheckDiag(FCDK), MRD(nullptr) {} + /// Destructor is purely virtual to ensure this remains an abstract class. + virtual ~MatchNoteDiag() = 0; + /// Is \p FCD an instance of \c MatchNoteDiag? + static bool classof(const FileCheckDiag *FCD) { + FileCheckDiagKind FCDK = FCD->getKind(); + return FCDK_MatchNoteDiag_First <= FCDK && FCDK <= FCDK_MatchNoteDiag_Last; + } + /// Get the note's associated \c MatchResultDiag. + virtual const MatchResultDiag &getMatchResultDiag() const { return *MRD; } + /// Set the note's associated \c MatchResultDiag. + void setMatchResultDiag(MatchResultDiag *MRDNew) { + assert(!MRD && "expected setMatchResultDiag to be called only once"); + MRD = MRDNew; + } +}; + +/// \c MatchNoteDiag for a fuzzy match that serves as a suggestion for the next +/// intended match for an expected pattern with too few or no good matches. +class MatchFuzzyDiag : public MatchNoteDiag { +private: + SMLoc MatchStart; + +public: + MatchFuzzyDiag(SMLoc MatchStart) + : MatchNoteDiag(FCDK_MatchFuzzyDiag), MatchStart(MatchStart) {} + /// Is \p FCD an instance of \c MatchFuzzyDiag? + static bool classof(const FileCheckDiag *FCD) { + return FCD->getKind() == FCDK_MatchFuzzyDiag; + } + /// Always false. A fuzzy match is not an error even though it is performed + /// due to an error. + virtual bool isError() const { return false; } + /// Return an input range (never \c llvm::None) starting and ending at the + /// match start. The actual match end is not computed. + virtual Optional getMatchRange() const { + return SMRange(MatchStart, MatchStart); + } +}; + +/// \c MatchNoteDiag with a custom note not described by any other class derived +/// from \c MatchNoteDiag. +class MatchCustomNoteDiag : public MatchNoteDiag { +private: std::string Note; - FileCheckDiag(const SourceMgr &SM, const Check::FileCheckType &CheckTy, - SMLoc CheckLoc, MatchType MatchTy, SMRange InputRange, - StringRef Note = ""); + bool AddsError; + Optional MatchRange; + +public: + /// If \p MatchRange is specified, it is a range for input text that was + /// matched in some way (e.g., variable capture) and that is described by + /// this note. Either way, as usual, the associated \c MatchResultDiag has + /// any full match range for the pattern. + /// + /// If \p AddsError is true, then this note indicates an \a additional error + /// that is distinct from any error indicated by the associated + /// \c MatchResultDiag. The error is described by \c Note, which must be + /// worded appropriately for prepending "error: " when presented later. For + /// example, the associated \c MatchResultDiag indicates a match to either an + /// expected pattern (success) or an excluded pattern (error), and \c Note + /// is "unable to represent numeric value" to indicate the match could not be + /// processed afterward. + /// + /// If \p AddsError is false, then this note merely provides additional + /// information about the associated \c MatchResultDiag. That information + /// might be something harmless (e.g., variable substitution), or it might be + /// one of potentially many problems summarized as an error by the + /// \c MatchResultDiag (e.g., one way in which the pattern was invalid). + ///@{ + MatchCustomNoteDiag(SMRange MatchRange, StringRef Note, + bool AddsError = false) + : MatchNoteDiag(FCDK_MatchCustomNoteDiag), Note(Note), + AddsError(AddsError), MatchRange(MatchRange) {} + MatchCustomNoteDiag(StringRef Note) + : MatchNoteDiag(FCDK_MatchCustomNoteDiag), Note(Note), AddsError(false) {} + ///@} + /// Is \p FCD an instance of \c MatchCustomNoteDiag? + static bool classof(const FileCheckDiag *FCD) { + return FCD->getKind() == FCDK_MatchCustomNoteDiag; + } + const std::string &getNote() const { return Note; } + /// Does this note indicate an \a additional error not indicated by the + /// associated \c MatchResultDiag? + /// + /// For details, see the \c MatchCustomNoteDiag::MatchCustomNoteDiag comments + /// for its \c AddsError parameter. + virtual bool isError() const { return AddsError; } + /// Return the match range described by the note, or \c llvm::None if none. + virtual Optional getMatchRange() const { return MatchRange; } +}; + +/// A \c FileCheckDiag series emitted by the FileCheck library. +class FileCheckDiagList { +private: + MatchResultDiag *CurMatchResultDiag = nullptr; + std::vector> DiagList; + +public: + /// Emplace a new \c FileCheckDiag of type \c DiagTy. If it's a + /// \c MatchNoteDiag, associate it with its \c MatchResultDiag. + /// + /// \c FileCheckTest.cpp calls \c Pattern::printVariableDefs directly, so it + /// can add a \c MatchNoteDiag without a previous \c MatchResultDiag. + /// Otherwise, there should always be a previous \c MatchResultDiag. + template + void emplace(const ArgTys &...Args) { + DiagList.emplace_back(std::make_unique(Args...)); + FileCheckDiag *Diag = DiagList.back().get(); + if (MatchResultDiag *MRD = dyn_cast(Diag)) { + CurMatchResultDiag = MRD; + return; + } + MatchNoteDiag *Note = cast(Diag); + if (!CurMatchResultDiag) + return; + Note->setMatchResultDiag(CurMatchResultDiag); + } + /// Adjust the previous \c MatchResultDiag, which must be a \c MatchFoundDiag, + /// from successful status to unsuccessful status. + void adjustPrevMatchFoundDiag(MatchFoundDiag::StatusTy Status) { + cast(CurMatchResultDiag)->markUnsuccessful(Status); + } + /// The \c FileCheckDiag list. + const std::vector> &getList() const { + return DiagList; + } }; class FileCheckPatternContext; @@ -220,7 +478,7 @@ /// /// \returns false if the input fails to satisfy the checks. bool checkInput(SourceMgr &SM, StringRef Buffer, - std::vector *Diags = nullptr); + FileCheckDiagList *Diags = nullptr); }; } // namespace llvm Index: llvm/lib/FileCheck/FileCheck.cpp =================================================================== --- llvm/lib/FileCheck/FileCheck.cpp +++ llvm/lib/FileCheck/FileCheck.cpp @@ -1357,8 +1357,7 @@ void Pattern::printSubstitutions(const SourceMgr &SM, StringRef Buffer, SMRange Range, - FileCheckDiag::MatchType MatchTy, - std::vector *Diags) const { + FileCheckDiagList *Diags) const { // Print what we know about substitutions. if (!Substitutions.empty()) { for (const auto &Substitution : Substitutions) { @@ -1376,13 +1375,13 @@ OS.write_escaped(Substitution->getFromString()) << "\" equal to \""; OS.write_escaped(*MatchedValue) << "\""; - // We report only the start of the match/search range to suggest we are - // reporting the substitutions as set at the start of the match/search. - // Indicating a non-zero-length range might instead seem to imply that the + // Unlike MatchCustomNoteDiag, PrintMessage needs a location. We report + // only the start of the match/search range to suggest we are reporting + // the substitutions as set at the start of the match/search. Indicating + // a non-zero-length range might instead seem to imply that the // substitution matches or was captured from exactly that range. if (Diags) - Diags->emplace_back(SM, CheckTy, getLoc(), MatchTy, - SMRange(Range.Start, Range.Start), OS.str()); + Diags->emplace(OS.str()); else SM.PrintMessage(Range.Start, SourceMgr::DK_Note, OS.str()); } @@ -1390,8 +1389,7 @@ } void Pattern::printVariableDefs(const SourceMgr &SM, - FileCheckDiag::MatchType MatchTy, - std::vector *Diags) const { + FileCheckDiagList *Diags) const { if (VariableDefs.empty() && NumericVariableDefs.empty()) return; // Build list of variable captures. @@ -1434,35 +1432,26 @@ raw_svector_ostream OS(Msg); OS << "captured var \"" << VC.Name << "\""; if (Diags) - Diags->emplace_back(SM, CheckTy, getLoc(), MatchTy, VC.Range, OS.str()); + Diags->emplace(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, - StringRef Buffer, size_t Pos, size_t Len, - std::vector *Diags, - bool AdjustPrevDiags = false) { +static SMRange buildMatchRange(StringRef Buffer, size_t Pos, size_t Len) { SMLoc Start = SMLoc::getFromPointer(Buffer.data() + Pos); SMLoc End = SMLoc::getFromPointer(Buffer.data() + Pos + Len); - SMRange Range(Start, End); - if (Diags) { - if (AdjustPrevDiags) { - SMLoc CheckLoc = Diags->rbegin()->CheckLoc; - for (auto I = Diags->rbegin(), E = Diags->rend(); - I != E && I->CheckLoc == CheckLoc; ++I) - I->MatchTy = MatchTy; - } else - Diags->emplace_back(SM, CheckTy, Loc, MatchTy, Range); - } - return Range; + return SMRange(Start, End); +} + +static SMRange buildSearchRange(StringRef Buffer) { + SMLoc Start = SMLoc::getFromPointer(Buffer.data()); + SMLoc End = SMLoc::getFromPointer(Buffer.data() + Buffer.size()); + return SMRange(Start, End); } void Pattern::printFuzzyMatch(const SourceMgr &SM, StringRef Buffer, - std::vector *Diags) const { + FileCheckDiagList *Diags) const { // Attempt to find the closest/best fuzzy match. Usually an error happens // because some string in the output didn't exactly match. In these cases, we // would like to show the user a best guess at what "should have" matched, to @@ -1496,10 +1485,10 @@ // reasonable and not equal to what we showed in the "scanning from here" // line. if (Best && Best != StringRef::npos && BestQuality < 50) { - SMRange MatchRange = - ProcessMatchResult(FileCheckDiag::MatchFuzzy, SM, getLoc(), - getCheckTy(), Buffer, Best, 0, Diags); - SM.PrintMessage(MatchRange.Start, SourceMgr::DK_Note, + SMLoc MatchStart = SMLoc::getFromPointer(Buffer.data() + Best); + if (Diags) + Diags->emplace(MatchStart); + SM.PrintMessage(MatchStart, SourceMgr::DK_Note, "possible intended match here"); // FIXME: If we wanted to be really friendly we would show why the match @@ -1605,18 +1594,9 @@ return StringRef(OutputBuffer.data(), OutputBuffer.size() - 1); } -FileCheckDiag::FileCheckDiag(const SourceMgr &SM, - const Check::FileCheckType &CheckTy, - SMLoc CheckLoc, MatchType MatchTy, - SMRange InputRange, StringRef Note) - : CheckTy(CheckTy), CheckLoc(CheckLoc), MatchTy(MatchTy), Note(Note) { - auto Start = SM.getLineAndColumn(InputRange.Start); - auto End = SM.getLineAndColumn(InputRange.End); - InputStartLine = Start.first; - InputStartCol = Start.second; - InputEndLine = End.first; - InputEndCol = End.second; -} +FileCheckDiag::~FileCheckDiag() {} +MatchResultDiag::~MatchResultDiag() {} +MatchNoteDiag::~MatchNoteDiag() {} static bool IsPartOfWord(char c) { return (isAlnum(c) || c == '-' || c == '_'); @@ -2075,8 +2055,7 @@ StringRef Prefix, SMLoc Loc, const Pattern &Pat, int MatchedCount, StringRef Buffer, Pattern::MatchResult MatchResult, - const FileCheckRequest &Req, - std::vector *Diags) { + const FileCheckRequest &Req, FileCheckDiagList *Diags) { // Suppress some verbosity if there's no error. bool HasError = !ExpectedMatch || MatchResult.TheError; bool PrintDiag = true; @@ -2092,15 +2071,16 @@ } // Add "found" diagnostic, substitutions, and variable definitions to Diags. - FileCheckDiag::MatchType MatchTy = ExpectedMatch - ? FileCheckDiag::MatchFoundAndExpected - : FileCheckDiag::MatchFoundButExcluded; - SMRange MatchRange = ProcessMatchResult(MatchTy, SM, Loc, Pat.getCheckTy(), - Buffer, MatchResult.TheMatch->Pos, - MatchResult.TheMatch->Len, Diags); + MatchFoundDiag::StatusTy Status = + ExpectedMatch ? MatchFoundDiag::Success : MatchFoundDiag::Excluded; + SMRange MatchRange = buildMatchRange(Buffer, MatchResult.TheMatch->Pos, + MatchResult.TheMatch->Len); + SMRange SearchRange = buildSearchRange(Buffer); if (Diags) { - Pat.printSubstitutions(SM, Buffer, MatchRange, MatchTy, Diags); - Pat.printVariableDefs(SM, MatchTy, Diags); + Diags->emplace(Pat.getCheckTy(), Loc, Status, MatchRange, + SearchRange); + Pat.printSubstitutions(SM, Buffer, MatchRange, Diags); + Pat.printVariableDefs(SM, Diags); } if (!PrintDiag) { assert(!HasError && "expected to report more diagnostics for error"); @@ -2120,8 +2100,8 @@ {MatchRange}); // Print additional information, which can be useful even if there are errors. - Pat.printSubstitutions(SM, Buffer, MatchRange, MatchTy, nullptr); - Pat.printVariableDefs(SM, MatchTy, nullptr); + Pat.printSubstitutions(SM, Buffer, MatchRange, nullptr); + Pat.printVariableDefs(SM, nullptr); // Print errors and add them to Diags. We report these errors after the match // itself because we found them after the match. If we had found them before @@ -2130,9 +2110,9 @@ [&](const ErrorDiagnostic &E) { E.log(errs()); if (Diags) { - Diags->emplace_back(SM, Pat.getCheckTy(), Loc, - FileCheckDiag::MatchFoundErrorNote, - E.getRange(), E.getMessage().str()); + Diags->emplace(E.getRange(), + E.getMessage().str(), + /*AddsError=*/true); } }); return ErrorReported::reportedOrSuccess(HasError); @@ -2144,20 +2124,18 @@ static Error printNoMatch(bool ExpectedMatch, const SourceMgr &SM, StringRef Prefix, SMLoc Loc, const Pattern &Pat, int MatchedCount, StringRef Buffer, Error MatchError, - bool VerboseVerbose, - std::vector *Diags) { + bool VerboseVerbose, FileCheckDiagList *Diags) { // Print any pattern errors, and record them to be added to Diags later. bool HasError = ExpectedMatch; bool HasPatternError = false; - FileCheckDiag::MatchType MatchTy = ExpectedMatch - ? FileCheckDiag::MatchNoneButExpected - : FileCheckDiag::MatchNoneAndExcluded; + MatchNoneDiag::StatusTy Status = + ExpectedMatch ? MatchNoneDiag::Expected : MatchNoneDiag::Success; SmallVector ErrorMsgs; handleAllErrors( std::move(MatchError), [&](const ErrorDiagnostic &E) { HasError = HasPatternError = true; - MatchTy = FileCheckDiag::MatchNoneForInvalidPattern; + Status = MatchNoneDiag::InvalidPattern; E.log(errs()); if (Diags) ErrorMsgs.push_back(E.getMessage().str()); @@ -2183,14 +2161,12 @@ // errors. The reason is that we need to attach pattern errors as notes // somewhere in the input, and the input search range from the "not found" // diagnostic is all we have to anchor them. - SMRange SearchRange = ProcessMatchResult(MatchTy, SM, Loc, Pat.getCheckTy(), - Buffer, 0, Buffer.size(), Diags); + SMRange SearchRange = buildSearchRange(Buffer); if (Diags) { - SMRange NoteRange = SMRange(SearchRange.Start, SearchRange.Start); + Diags->emplace(Pat.getCheckTy(), Loc, Status, SearchRange); for (StringRef ErrorMsg : ErrorMsgs) - Diags->emplace_back(SM, Pat.getCheckTy(), Loc, MatchTy, NoteRange, - ErrorMsg); - Pat.printSubstitutions(SM, Buffer, SearchRange, MatchTy, Diags); + Diags->emplace(ErrorMsg); + Pat.printSubstitutions(SM, Buffer, SearchRange, Diags); } if (!PrintDiag) { assert(!HasError && "expected to report more diagnostics for error"); @@ -2216,7 +2192,7 @@ // Print additional information, which can be useful even after a pattern // error. - Pat.printSubstitutions(SM, Buffer, SearchRange, MatchTy, nullptr); + Pat.printSubstitutions(SM, Buffer, SearchRange, nullptr); if (ExpectedMatch) Pat.printFuzzyMatch(SM, Buffer, Diags); return ErrorReported::reportedOrSuccess(HasError); @@ -2229,7 +2205,7 @@ int MatchedCount, StringRef Buffer, Pattern::MatchResult MatchResult, const FileCheckRequest &Req, - std::vector *Diags) { + FileCheckDiagList *Diags) { if (MatchResult.TheMatch) return printMatch(ExpectedMatch, SM, Prefix, Loc, Pat, MatchedCount, Buffer, std::move(MatchResult), Req, Diags); @@ -2264,7 +2240,7 @@ size_t FileCheckString::Check(const SourceMgr &SM, StringRef Buffer, bool IsLabelScanMode, size_t &MatchLen, FileCheckRequest &Req, - std::vector *Diags) const { + FileCheckDiagList *Diags) const { size_t LastPos = 0; std::vector NotStrings; @@ -2318,18 +2294,34 @@ // If this check is a "CHECK-NEXT", verify that the previous match was on // the previous line (i.e. that there is one newline between them). if (CheckNext(SM, SkippedRegion)) { - ProcessMatchResult(FileCheckDiag::MatchFoundButWrongLine, SM, Loc, - Pat.getCheckTy(), MatchBuffer, MatchPos, MatchLen, - Diags, Req.Verbose); + if (Diags) { + if (Req.Verbose) + Diags->adjustPrevMatchFoundDiag(MatchFoundDiag::WrongLine); + else { + SMRange MatchRange = buildMatchRange(MatchBuffer, MatchPos, MatchLen); + SMRange SearchRange = buildSearchRange(MatchBuffer); + Diags->emplace(Pat.getCheckTy(), Loc, + MatchFoundDiag::WrongLine, MatchRange, + SearchRange); + } + } return StringRef::npos; } // If this check is a "CHECK-SAME", verify that the previous match was on // the same line (i.e. that there is no newline between them). if (CheckSame(SM, SkippedRegion)) { - ProcessMatchResult(FileCheckDiag::MatchFoundButWrongLine, SM, Loc, - Pat.getCheckTy(), MatchBuffer, MatchPos, MatchLen, - Diags, Req.Verbose); + if (Diags) { + if (Req.Verbose) + Diags->adjustPrevMatchFoundDiag(MatchFoundDiag::WrongLine); + else { + SMRange MatchRange = buildMatchRange(MatchBuffer, MatchPos, MatchLen); + SMRange SearchRange = buildSearchRange(MatchBuffer); + Diags->emplace(Pat.getCheckTy(), Loc, + MatchFoundDiag::WrongLine, MatchRange, + SearchRange); + } + } return StringRef::npos; } @@ -2406,7 +2398,7 @@ bool FileCheckString::CheckNot(const SourceMgr &SM, StringRef Buffer, const std::vector &NotStrings, const FileCheckRequest &Req, - std::vector *Diags) const { + FileCheckDiagList *Diags) const { bool DirectiveFail = false; for (const Pattern *Pat : NotStrings) { assert((Pat->getCheckTy() == Check::CheckNot) && "Expect CHECK-NOT!"); @@ -2425,7 +2417,7 @@ size_t FileCheckString::CheckDag(const SourceMgr &SM, StringRef Buffer, std::vector &NotStrings, const FileCheckRequest &Req, - std::vector *Diags) const { + FileCheckDiagList *Diags) const { if (DagNotStrings.empty()) return 0; @@ -2513,18 +2505,15 @@ // Due to their verbosity, we don't print verbose diagnostics here if // we're gathering them for a different rendering, but we always print // other diagnostics. - if (!Diags) { + if (Diags) + Diags->adjustPrevMatchFoundDiag(MatchFoundDiag::Discarded); + else { SMLoc OldStart = SMLoc::getFromPointer(Buffer.data() + MI->Pos); SMLoc OldEnd = SMLoc::getFromPointer(Buffer.data() + MI->End); SMRange OldRange(OldStart, OldEnd); SM.PrintMessage(OldStart, SourceMgr::DK_Note, "match discarded, overlaps earlier DAG match here", {OldRange}); - } else { - SMLoc CheckLoc = Diags->rbegin()->CheckLoc; - for (auto I = Diags->rbegin(), E = Diags->rend(); - I != E && I->CheckLoc == CheckLoc; ++I) - I->MatchTy = FileCheckDiag::MatchFoundButDiscarded; } } MatchPos = MI->End; @@ -2797,7 +2786,7 @@ } bool FileCheck::checkInput(SourceMgr &SM, StringRef Buffer, - std::vector *Diags) { + FileCheckDiagList *Diags) { bool ChecksFailed = false; unsigned i = 0, j = 0, e = CheckStrings->size(); Index: llvm/lib/FileCheck/FileCheckImpl.h =================================================================== --- llvm/lib/FileCheck/FileCheckImpl.h +++ llvm/lib/FileCheck/FileCheckImpl.h @@ -757,16 +757,14 @@ MatchResult match(StringRef Buffer, const SourceMgr &SM) const; /// Prints the value of successful substitutions. void printSubstitutions(const SourceMgr &SM, StringRef Buffer, - SMRange MatchRange, FileCheckDiag::MatchType MatchTy, - std::vector *Diags) const; + SMRange MatchRange, FileCheckDiagList *Diags) const; void printFuzzyMatch(const SourceMgr &SM, StringRef Buffer, - std::vector *Diags) const; + FileCheckDiagList *Diags) const; bool hasVariable() const { return !(Substitutions.empty() && VariableDefs.empty()); } - void printVariableDefs(const SourceMgr &SM, FileCheckDiag::MatchType MatchTy, - std::vector *Diags) const; + void printVariableDefs(const SourceMgr &SM, FileCheckDiagList *Diags) const; Check::FileCheckType getCheckTy() const { return CheckTy; } @@ -882,7 +880,7 @@ /// Matches check string and its "not strings" and/or "dag strings". size_t Check(const SourceMgr &SM, StringRef Buffer, bool IsLabelScanMode, size_t &MatchLen, FileCheckRequest &Req, - std::vector *Diags) const; + FileCheckDiagList *Diags) const; /// Verifies that there is a single line in the given \p Buffer. Errors are /// reported against \p SM. @@ -895,13 +893,11 @@ /// \p Diags according to the verbosity level set in \p Req. bool CheckNot(const SourceMgr &SM, StringRef Buffer, const std::vector &NotStrings, - const FileCheckRequest &Req, - std::vector *Diags) const; + const FileCheckRequest &Req, FileCheckDiagList *Diags) const; /// Matches "dag strings" and their mixed "not strings". size_t CheckDag(const SourceMgr &SM, StringRef Buffer, std::vector &NotStrings, - const FileCheckRequest &Req, - std::vector *Diags) const; + const FileCheckRequest &Req, FileCheckDiagList *Diags) const; }; } // namespace llvm Index: llvm/unittests/FileCheck/FileCheckTest.cpp =================================================================== --- llvm/unittests/FileCheck/FileCheckTest.cpp +++ llvm/unittests/FileCheck/FileCheckTest.cpp @@ -1033,12 +1033,30 @@ return Res.TheMatch->Pos; } - void printVariableDefs(FileCheckDiag::MatchType MatchTy, - std::vector &Diags) { - P.printVariableDefs(SM, MatchTy, &Diags); + void printVariableDefs(FileCheckDiagList &Diags) { + P.printVariableDefs(SM, &Diags); } + + SourceMgr &getSourceMgr() { return SM; } }; +#define EXPECT_SM_RANGE(SM, RangeOpt, StartLineExpected, StartColExpected, \ + EndLineExpected, EndColExpected) \ + do { \ + EXPECT_TRUE(RangeOpt->isValid()); \ + SMRange Range = RangeOpt.getValue(); \ + auto StartActual = SM.getLineAndColumn(Range.Start); \ + auto EndActual = SM.getLineAndColumn(Range.End); \ + unsigned StartLineActual = StartActual.first; \ + unsigned StartColActual = StartActual.second; \ + unsigned EndLineActual = EndActual.first; \ + unsigned EndColActual = EndActual.second; \ + EXPECT_EQ(StartLineActual, StartLineExpected); \ + EXPECT_EQ(StartColActual, StartColExpected); \ + EXPECT_EQ(EndLineActual, EndLineExpected); \ + EXPECT_EQ(EndColActual, EndColExpected); \ + } while (0) + TEST_F(FileCheckTest, ParseNumericSubstitutionBlock) { PatternTester Tester; @@ -1759,22 +1777,21 @@ TEST_F(FileCheckTest, CapturedVarDiags) { PatternTester Tester; + SourceMgr &SM = Tester.getSourceMgr(); ASSERT_FALSE(Tester.parsePattern("[[STRVAR:[a-z]+]] [[#NUMVAR:@LINE]]")); EXPECT_THAT_EXPECTED(Tester.match("foobar 2"), Succeeded()); - std::vector Diags; - Tester.printVariableDefs(FileCheckDiag::MatchFoundAndExpected, Diags); - EXPECT_EQ(Diags.size(), 2ul); - for (FileCheckDiag Diag : Diags) { - EXPECT_EQ(Diag.CheckTy, Check::CheckPlain); - EXPECT_EQ(Diag.MatchTy, FileCheckDiag::MatchFoundAndExpected); - EXPECT_EQ(Diag.InputStartLine, 1u); - EXPECT_EQ(Diag.InputEndLine, 1u); + FileCheckDiagList Diags; + Tester.printVariableDefs(Diags); + EXPECT_EQ(Diags.getList().size(), 2ul); + for (const std::unique_ptr &Diag : Diags.getList()) { + EXPECT_EQ(Diag->getKind(), FileCheckDiag::FCDK_MatchCustomNoteDiag); + EXPECT_FALSE(Diag->isError()); } - EXPECT_EQ(Diags[0].InputStartCol, 1u); - EXPECT_EQ(Diags[0].InputEndCol, 7u); - EXPECT_EQ(Diags[1].InputStartCol, 8u); - EXPECT_EQ(Diags[1].InputEndCol, 9u); - EXPECT_EQ(Diags[0].Note, "captured var \"STRVAR\""); - EXPECT_EQ(Diags[1].Note, "captured var \"NUMVAR\""); + EXPECT_SM_RANGE(SM, Diags.getList()[0]->getMatchRange(), 1u, 1u, 1u, 7u); + EXPECT_SM_RANGE(SM, Diags.getList()[1]->getMatchRange(), 1u, 8u, 1u, 9u); + EXPECT_EQ(cast(Diags.getList()[0].get())->getNote(), + "captured var \"STRVAR\""); + EXPECT_EQ(cast(Diags.getList()[1].get())->getNote(), + "captured var \"NUMVAR\""); } } // namespace Index: llvm/utils/FileCheck/FileCheck.cpp =================================================================== --- llvm/utils/FileCheck/FileCheck.cpp +++ llvm/utils/FileCheck/FileCheck.cpp @@ -193,47 +193,71 @@ std::string Note; /// Does this marker indicate inclusion by -dump-input-filter=error? bool FiltersAsError; - MarkerStyle() {} - MarkerStyle(char Lead, raw_ostream::Colors Color, - const std::string &Note = "", bool FiltersAsError = false) - : Lead(Lead), Color(Color), Note(Note), FiltersAsError(FiltersAsError) { - assert((!FiltersAsError || !Note.empty()) && - "expected error diagnostic to have note"); - } }; -static MarkerStyle GetMarker(FileCheckDiag::MatchType MatchTy) { - switch (MatchTy) { - case FileCheckDiag::MatchFoundAndExpected: - return MarkerStyle('^', raw_ostream::GREEN); - case FileCheckDiag::MatchFoundButExcluded: - return MarkerStyle('!', raw_ostream::RED, "error: no match expected", - /*FiltersAsError=*/true); - case FileCheckDiag::MatchFoundButWrongLine: - return MarkerStyle('!', raw_ostream::RED, "error: match on wrong line", - /*FiltersAsError=*/true); - case FileCheckDiag::MatchFoundButDiscarded: - return MarkerStyle('!', raw_ostream::CYAN, - "discard: overlaps earlier match"); - case FileCheckDiag::MatchFoundErrorNote: - // Note should always be overridden within the FileCheckDiag. - return MarkerStyle('!', raw_ostream::RED, - "error: unknown error after match", - /*FiltersAsError=*/true); - case FileCheckDiag::MatchNoneAndExcluded: - return MarkerStyle('X', raw_ostream::GREEN); - case FileCheckDiag::MatchNoneButExpected: - return MarkerStyle('X', raw_ostream::RED, "error: no match found", - /*FiltersAsError=*/true); - case FileCheckDiag::MatchNoneForInvalidPattern: - return MarkerStyle('X', raw_ostream::RED, - "error: match failed for invalid pattern", - /*FiltersAsError=*/true); - case FileCheckDiag::MatchFuzzy: - return MarkerStyle('?', raw_ostream::MAGENTA, "possible intended match", - /*FiltersAsError=*/true); +static MarkerStyle getMarker(const FileCheckDiag *Diag) { + // By default, the marker is based on whether the diagnostic is an error or is + // a MatchNoteDiag on a MatchResultDiag that is an error. + // + // It's less confusing if diagnostics that don't actually have match ranges + // 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. + MarkerStyle Res; + bool IsError = Diag->isError() || Diag->getMatchResultDiag().isError(); + Res.Lead = !Diag->getMatchRange().hasValue() ? ' ' : IsError ? '!' : '^'; + Res.Color = IsError ? raw_ostream::RED : raw_ostream::GREEN; + Res.FiltersAsError = IsError; + + // Override the default for some kinds of diagnostics. + switch (Diag->getKind()) { + case FileCheckDiag::FCDK_MatchFoundDiag: + switch (cast(Diag)->getStatus()) { + case MatchFoundDiag::Success: + break; + case MatchFoundDiag::Excluded: + Res.Note = "error: no match expected"; + break; + case MatchFoundDiag::WrongLine: + Res.Note = "error: match on wrong line"; + break; + case MatchFoundDiag::Discarded: + Res.Lead = '!'; // Not an error, but not a successful match either. + Res.Color = raw_ostream::CYAN; + Res.Note = "discard: overlaps earlier match"; + break; + } + break; + case FileCheckDiag::FCDK_MatchNoneDiag: + Res.Lead = 'X'; + switch (cast(Diag)->getStatus()) { + case MatchNoneDiag::Success: + break; + case MatchNoneDiag::InvalidPattern: + Res.Note = "error: match failed for invalid pattern"; + break; + case MatchNoneDiag::Expected: + Res.Note = "error: no match found"; + break; + } + break; + case FileCheckDiag::FCDK_MatchFuzzyDiag: + Res.Lead = '?'; + Res.Color = raw_ostream::MAGENTA; + Res.Note = "possible intended match"; + break; + case FileCheckDiag::FCDK_MatchCustomNoteDiag: + Res.Note = cast(Diag)->getNote(); + if (Diag->isError()) + Res.Note = "error: " + Res.Note; + break; } - llvm_unreachable_internal("unexpected match type"); + assert((!Res.FiltersAsError || !Res.Note.empty()) && + "expected error diagnostic to have note"); + return Res; } static void DumpInputAnnotationHelp(raw_ostream &OS) { @@ -378,9 +402,9 @@ } static void -BuildInputAnnotations(const SourceMgr &SM, unsigned CheckFileBufferID, +buildInputAnnotations(const SourceMgr &SM, unsigned CheckFileBufferID, const std::pair &ImpPatBufferIDRange, - const std::vector &Diags, + const std::vector> &Diags, std::vector &Annotations, unsigned &LabelWidth) { struct CompareSMLoc { @@ -390,80 +414,112 @@ }; // How many diagnostics does each pattern have? std::map DiagCountPerPattern; - for (auto Diag : Diags) - ++DiagCountPerPattern[Diag.CheckLoc]; + for (const std::unique_ptr &Diag : Diags) + ++DiagCountPerPattern[Diag->getMatchResultDiag().getCheckLoc()]; // How many diagnostics have we seen so far per pattern? std::map DiagIndexPerPattern; // How many total diagnostics have we seen so far? unsigned DiagIndex = 0; // What's the widest label? LabelWidth = 0; - for (auto DiagItr = Diags.begin(), DiagEnd = Diags.end(); DiagItr != DiagEnd; - ++DiagItr) { + // The label prefix that uniquely identifies the current MatchResultDiag and + // its MatchNoteDiag series. + std::string CurLabelPrefix; + for (const std::unique_ptr &Diag : Diags) { InputAnnotation A; A.DiagIndex = DiagIndex++; + if (MatchResultDiag *MRD = dyn_cast(Diag.get())) { + CurLabelPrefix.clear(); + llvm::raw_string_ostream LabelStrm(CurLabelPrefix); + LabelStrm << GetCheckTypeAbbreviation(MRD->getCheckTy()) << ":"; + unsigned CheckBufferID = SM.FindBufferContainingLoc(MRD->getCheckLoc()); + if (CheckBufferID == CheckFileBufferID) + LabelStrm + << SM.getLineAndColumn(MRD->getCheckLoc(), CheckBufferID).first; + else if (ImpPatBufferIDRange.first <= CheckBufferID && + CheckBufferID < ImpPatBufferIDRange.second) + LabelStrm << "imp" << (CheckBufferID - ImpPatBufferIDRange.first + 1); + else + llvm_unreachable("expected diagnostic's check location to be either in " + "the check file or for an implicit pattern"); + } - // Build label, which uniquely identifies this check result. - unsigned CheckBufferID = SM.FindBufferContainingLoc(DiagItr->CheckLoc); - auto CheckLineAndCol = - SM.getLineAndColumn(DiagItr->CheckLoc, CheckBufferID); - llvm::raw_string_ostream Label(A.Label); - Label << GetCheckTypeAbbreviation(DiagItr->CheckTy) << ":"; - if (CheckBufferID == CheckFileBufferID) - Label << CheckLineAndCol.first; - else if (ImpPatBufferIDRange.first <= CheckBufferID && - CheckBufferID < ImpPatBufferIDRange.second) - Label << "imp" << (CheckBufferID - ImpPatBufferIDRange.first + 1); - else - llvm_unreachable("expected diagnostic's check location to be either in " - "the check file or for an implicit pattern"); - if (DiagCountPerPattern[DiagItr->CheckLoc] > 1) - Label << "'" << DiagIndexPerPattern[DiagItr->CheckLoc]++; + // Build label that uniquely identifies this FileCheckDiag. + llvm::raw_string_ostream LabelStrm(A.Label); + LabelStrm << CurLabelPrefix; + if (DiagCountPerPattern[Diag->getMatchResultDiag().getCheckLoc()] > 1) + LabelStrm + << "'" + << DiagIndexPerPattern[Diag->getMatchResultDiag().getCheckLoc()]++; LabelWidth = std::max((std::string::size_type)LabelWidth, A.Label.size()); - A.Marker = GetMarker(DiagItr->MatchTy); - if (!DiagItr->Note.empty()) { - A.Marker.Note = DiagItr->Note; - // It's less confusing if notes that don't actually have ranges don't have - // markers. For example, a marker for 'with "VAR" equal to "5"' would - // seem to indicate where "VAR" matches, but the location we actually have - // for the marker simply points to the start of the match/search range for - // the full pattern of which the substitution is potentially just one - // component. - if (DiagItr->InputStartLine == DiagItr->InputEndLine && - DiagItr->InputStartCol == DiagItr->InputEndCol) - A.Marker.Lead = ' '; + // Build the input marker. + A.Marker = getMarker(Diag.get()); + + // Does this diagnostic mark text that has been successfully matched? + A.FoundAndExpectedMatch = false; + if (const MatchFoundDiag *Found = dyn_cast(Diag.get())) { + if (Found->getStatus() == MatchFoundDiag::Success) + A.FoundAndExpectedMatch = true; } - if (DiagItr->MatchTy == FileCheckDiag::MatchFoundErrorNote) { - assert(!DiagItr->Note.empty() && - "expected custom note for MatchFoundErrorNote"); - A.Marker.Note = "error: " + A.Marker.Note; + + // If Diag has a match range, use it. If it's a MatchNoneDiag, use its + // search range. Otherwise, position it at the start of the most recent + // MatchResultDiag, with which it's associated. + unsigned InputStartLine; + unsigned InputStartCol; + unsigned InputEndLine; + unsigned InputEndCol; + if (Diag->getMatchRange().hasValue()) { + SMRange InputRange = Diag->getMatchRange().getValue(); + auto InputStartLineAndCol = SM.getLineAndColumn(InputRange.Start); + auto InputEndLineAndCol = SM.getLineAndColumn(InputRange.End); + InputStartLine = InputStartLineAndCol.first; + InputStartCol = InputStartLineAndCol.second; + InputEndLine = InputEndLineAndCol.first; + InputEndCol = InputEndLineAndCol.second; + } else if (MatchNoneDiag *MND = dyn_cast(Diag.get())) { + SMRange InputRange = MND->getSearchRange(); + auto InputStartLineAndCol = SM.getLineAndColumn(InputRange.Start); + auto InputEndLineAndCol = SM.getLineAndColumn(InputRange.End); + InputStartLine = InputStartLineAndCol.first; + InputStartCol = InputStartLineAndCol.second; + InputEndLine = InputEndLineAndCol.first; + InputEndCol = InputEndLineAndCol.second; + } else { + assert(isa(Diag.get()) && + "expected only MatchNoteDiag to have no input range"); + const MatchResultDiag &MRD = Diag->getMatchResultDiag(); + SMRange InputRange; + if (MRD.getMatchRange().hasValue()) + InputRange = MRD.getMatchRange().getValue(); + else + InputRange = MRD.getSearchRange(); + auto InputStartLineAndCol = SM.getLineAndColumn(InputRange.Start); + InputStartLine = InputEndLine = InputStartLineAndCol.first; + InputStartCol = InputEndCol = InputStartLineAndCol.second; } - A.FoundAndExpectedMatch = - DiagItr->MatchTy == FileCheckDiag::MatchFoundAndExpected; // Compute the mark location, and break annotation into multiple // annotations if it spans multiple lines. A.IsFirstLine = true; - A.InputLine = DiagItr->InputStartLine; - A.InputStartCol = DiagItr->InputStartCol; - if (DiagItr->InputStartLine == DiagItr->InputEndLine) { + A.InputLine = InputStartLine; + A.InputStartCol = InputStartCol; + if (InputStartLine == InputEndLine) { // Sometimes ranges are empty in order to indicate a specific point, but // that would mean nothing would be marked, so adjust the range to // include the following character. - A.InputEndCol = - std::max(DiagItr->InputStartCol + 1, DiagItr->InputEndCol); + A.InputEndCol = std::max(InputStartCol + 1, InputEndCol); Annotations.push_back(A); } else { - assert(DiagItr->InputStartLine < DiagItr->InputEndLine && + assert(InputStartLine < InputEndLine && "expected input range not to be inverted"); A.InputEndCol = UINT_MAX; Annotations.push_back(A); - for (unsigned L = DiagItr->InputStartLine + 1, E = DiagItr->InputEndLine; - L <= E; ++L) { + for (unsigned L = InputStartLine + 1, E = InputEndLine; L <= E; ++L) { // If a range ends before the first column on a line, then it has no // characters on that line, so there's nothing to render. - if (DiagItr->InputEndCol == 1 && L == E) + if (InputEndCol == 1 && L == E) break; InputAnnotation B; B.DiagIndex = A.DiagIndex; @@ -477,7 +533,7 @@ if (L != E) B.InputEndCol = UINT_MAX; else - B.InputEndCol = DiagItr->InputEndCol; + B.InputEndCol = InputEndCol; B.FoundAndExpectedMatch = A.FoundAndExpectedMatch; Annotations.push_back(B); } @@ -870,7 +926,7 @@ InputFileText, InputFile.getBufferIdentifier()), SMLoc()); - std::vector Diags; + FileCheckDiagList Diags; int ExitCode = FC.checkInput(SM, InputFileText, DumpInput == DumpInputNever ? nullptr : &Diags) ? EXIT_SUCCESS @@ -885,8 +941,8 @@ << "\n"; std::vector Annotations; unsigned LabelWidth; - BuildInputAnnotations(SM, CheckFileBufferID, ImpPatBufferIDRange, Diags, - Annotations, LabelWidth); + buildInputAnnotations(SM, CheckFileBufferID, ImpPatBufferIDRange, + Diags.getList(), Annotations, LabelWidth); DumpAnnotatedInput(errs(), Req, DumpInputFilter, DumpInputContext, InputFileText, Annotations, LabelWidth); }