diff --git a/llvm/docs/CommandGuide/FileCheck.rst b/llvm/docs/CommandGuide/FileCheck.rst --- a/llvm/docs/CommandGuide/FileCheck.rst +++ b/llvm/docs/CommandGuide/FileCheck.rst @@ -107,10 +107,12 @@ Sets a filecheck pattern variable ``VAR`` with value ``VALUE`` that can be used in ``CHECK:`` lines. -.. option:: -D#= +.. option:: -D#= - Sets a filecheck numeric variable ``NUMVAR`` to ```` that can be used - in ``CHECK:`` lines. + Sets a filecheck numeric variable ``NUMVAR`` to the result of evaluating + ```` that can be used in ``CHECK:`` lines. See section + ``FileCheck Numeric Variables and Expressions`` for details on the format + and meaning of ````. .. option:: -version @@ -590,18 +592,15 @@ would match ``mov r5, 42`` and set ``REG`` to the value ``5``. -The syntax of a numeric substitution is ``[[#]]`` where: +The syntax of a numeric substitution is ``[[#]]`` where ```` is an +expression. An expression is recursively defined as: -* ```` is the name of a defined numeric variable. +* a numeric operand, or +* an expression followed by an operator and a numeric operand. -* ```` is an optional operation to perform on the value of ````. - Currently supported operations are ``+`` and ``-``. - -* ```` is the immediate value that constitutes the second operand of - the operation ````. It must be present if ```` is present, absent - otherwise. - -Spaces are accepted before, after and between any of these elements. +A numeric operand is a previously defined numeric variable, or an integer +literal. The supported operators are ``+`` and ``-``. Spaces are accepted +before, after and between any of these elements. For example: diff --git a/llvm/include/llvm/Support/FileCheck.h b/llvm/include/llvm/Support/FileCheck.h --- a/llvm/include/llvm/Support/FileCheck.h +++ b/llvm/include/llvm/Support/FileCheck.h @@ -40,6 +40,54 @@ // Numeric substitution handling code. //===----------------------------------------------------------------------===// +/// Base class representing the AST of a given expression. +class FileCheckExpressionAST { +public: + virtual ~FileCheckExpressionAST() = default; + + /// Evaluates and \returns the value of the expression represented by this + /// AST or an error if evaluation fails. + virtual Expected eval() const = 0; +}; + +/// Class representing an unsigned literal in the AST of an expression. +class FileCheckExpressionLiteral : public FileCheckExpressionAST { +private: + /// Actual value of the literal. + uint64_t Value; + +public: + /// Constructs a litteral with the specified value. + FileCheckExpressionLiteral(uint64_t Val) : Value(Val) {} + + /// \returns the literal's value. + Expected eval() const { return Value; } +}; + +/// Class to represent an undefined variable error, which quotes that +/// variable's name when printed. +class FileCheckUndefVarError : public ErrorInfo { +private: + StringRef VarName; + +public: + static char ID; + + FileCheckUndefVarError(StringRef VarName) : VarName(VarName) {} + + StringRef getVarName() const { return VarName; } + + std::error_code convertToErrorCode() const override { + return inconvertibleErrorCode(); + } + + /// Print name of variable associated with this error. + void log(raw_ostream &OS) const override { + OS << "\""; + OS.write_escaped(VarName) << "\""; + } +}; + /// Class representing a numeric variable and its associated current value. class FileCheckNumericVariable { private: @@ -81,56 +129,53 @@ size_t getDefLineNumber() { return DefLineNumber; } }; -/// Type of functions evaluating a given binary operation. -using binop_eval_t = uint64_t (*)(uint64_t, uint64_t); - -/// Class to represent an undefined variable error which prints that variable's -/// name between quotes when printed. -class FileCheckUndefVarError : public ErrorInfo { +/// Class representing the use of a numeric variable in the AST of an +/// expression. +class FileCheckNumericVariableUse : public FileCheckExpressionAST { private: - StringRef VarName; - -public: - static char ID; - - FileCheckUndefVarError(StringRef VarName) : VarName(VarName) {} + /// Name of the numeric variable. + StringRef Name; - StringRef getVarName() const { return VarName; } + /// Pointer to the class instance for the variable this use is about. + FileCheckNumericVariable *NumericVariable; - std::error_code convertToErrorCode() const override { - return inconvertibleErrorCode(); - } +public: + FileCheckNumericVariableUse(StringRef Name, + FileCheckNumericVariable *NumericVariable) + : Name(Name), NumericVariable(NumericVariable) {} - /// Print name of variable associated with this error. - void log(raw_ostream &OS) const override { - OS << "\""; - OS.write_escaped(VarName) << "\""; - } + /// \returns the value of the variable referenced by this instance. + Expected eval() const; }; -/// Class representing an expression consisting of either a single numeric -/// variable or a binary operation between a numeric variable and an -/// immediate. -class FileCheckExpression { +/// Type of functions evaluating a given binary operation. +using binop_eval_t = uint64_t (*)(uint64_t, uint64_t); + +/// Class representing a single binary operation in the AST of an expression. +class FileCheckASTBinop : public FileCheckExpressionAST { private: /// Left operand. - FileCheckNumericVariable *LeftOp; + std::unique_ptr LeftOp; /// Right operand. - uint64_t RightOp; + std::unique_ptr RightOp; /// Pointer to function that can evaluate this binary operation. binop_eval_t EvalBinop; public: - FileCheckExpression(binop_eval_t EvalBinop, - FileCheckNumericVariable *OperandLeft, - uint64_t OperandRight) - : LeftOp(OperandLeft), RightOp(OperandRight), EvalBinop(EvalBinop) {} - - /// Evaluates the value of this expression, using EvalBinop to perform the - /// binary operation it consists of. \returns an error if the numeric - /// variable used is undefined, or the expression value otherwise. + FileCheckASTBinop(binop_eval_t EvalBinop, + std::unique_ptr OperandLeft, + std::unique_ptr OperandRight) + : EvalBinop(EvalBinop) { + LeftOp = std::move(OperandLeft); + RightOp = std::move(OperandRight); + } + + /// Evaluates the value of the binary operation represented by this AST, + /// using EvalBinop on the result of recursively evaluating the operands. + /// \returns the expression value or an error if an undefined numeric + /// variable is used in one of the operands. Expected eval() const; }; @@ -187,15 +232,15 @@ private: /// Pointer to the class representing the expression whose value is to be /// substituted. - FileCheckExpression *Expression; + std::unique_ptr ExpressionAST; public: - FileCheckNumericSubstitution(FileCheckPatternContext *Context, - StringRef ExpressionStr, - FileCheckExpression *Expression, + FileCheckNumericSubstitution(FileCheckPatternContext *Context, StringRef Expr, + std::unique_ptr ExprAST, size_t InsertIdx) - : FileCheckSubstitution(Context, ExpressionStr, InsertIdx), - Expression(Expression) {} + : FileCheckSubstitution(Context, Expr, InsertIdx) { + ExpressionAST = std::move(ExprAST); + } /// \returns a string containing the result of evaluating the expression in /// this substitution, or an error if evaluation failed. @@ -278,10 +323,6 @@ /// easily updating its value. FileCheckNumericVariable *LineVariable = nullptr; - /// Vector holding pointers to all parsed expressions. Used to automatically - /// free the expressions once they are guaranteed to no longer be used. - std::vector> Expressions; - /// Vector holding pointers to all parsed numeric variables. Used to /// automatically free them once they are guaranteed to no longer be used. std::vector> NumericVariables; @@ -313,12 +354,6 @@ void clearLocalVars(); private: - /// Makes a new expression instance and registers it for destruction when - /// the context is destroyed. - FileCheckExpression *makeExpression(binop_eval_t EvalBinop, - FileCheckNumericVariable *OperandLeft, - uint64_t OperandRight); - /// Makes a new numeric variable and registers it for destruction when the /// context is destroyed. template @@ -333,7 +368,8 @@ /// the context is destroyed. FileCheckSubstitution * makeNumericSubstitution(StringRef ExpressionStr, - FileCheckExpression *Expression, size_t InsertIdx); + std::unique_ptr ExpressionAST, + size_t InsertIdx); }; /// Class to represent an error holding a diagnostic with location information @@ -473,16 +509,19 @@ parseNumericVariableDefinition(StringRef &Expr, FileCheckPatternContext *Context, size_t LineNumber, const SourceMgr &SM); - /// Parses \p Expr for a numeric substitution block. \returns the class - /// representing the AST of the expression whose value must be substituted, - /// or an error holding a diagnostic against \p SM if parsing fails. If - /// substitution was successful, sets \p DefinedNumericVariable to point to - /// the class representing the numeric variable defined in this numeric + /// Parses \p Expr for a numeric substitution block. Parameter + /// \p IsLegacyLineExpr indicates whether \p Expr should be a legacy @LINE + /// expression. \returns a pointer to the class instance representing the AST + /// of the expression whose value must be substituted, or an error holding a + /// diagnostic against \p SM if parsing fails. If substitution was + /// successful, sets \p DefinedNumericVariable to point to the class + /// representing the numeric variable being defined in this numeric /// substitution block, or None if this block does not define any variable. - Expected parseNumericSubstitutionBlock( + Expected> + parseNumericSubstitutionBlock( StringRef Expr, Optional &DefinedNumericVariable, - const SourceMgr &SM) const; + bool IsLegacyLineExpr, const SourceMgr &SM) const; /// Parses the pattern in \p PatternStr and initializes this FileCheckPattern /// instance accordingly. /// @@ -507,7 +546,7 @@ Expected match(StringRef Buffer, size_t &MatchLen, const SourceMgr &SM) const; /// Prints the value of successful substitutions or the name of the undefined - /// string or numeric variable preventing a successful substitution. + /// string or numeric variables preventing a successful substitution. void printSubstitutions(const SourceMgr &SM, StringRef Buffer, SMRange MatchRange = None) const; void printFuzzyMatch(const SourceMgr &SM, StringRef Buffer, @@ -539,13 +578,25 @@ /// Parses \p Expr for the use of a numeric variable. \returns the pointer to /// the class instance representing that variable if successful, or an error /// holding a diagnostic against \p SM otherwise. - Expected - parseNumericVariableUse(StringRef &Expr, const SourceMgr &SM) const; - /// Parses \p Expr for a binary operation. - /// \returns the class representing the binary operation of the expression, - /// or an error holding a diagnostic against \p SM otherwise. - Expected parseBinop(StringRef &Expr, - const SourceMgr &SM) const; + Expected> + parseNumericVariableUse(StringRef Name, bool IsPseudo, + const SourceMgr &SM) const; + enum class AllowedOperand { LineVar, Literal, Any }; + /// Parses \p Expr for use of a numeric operand. Accepts both literal values + /// and numeric variables, depending on the value of \p AO. \returns the + /// class representing that operand in the AST of the expression or an error + /// holding a diagnostic against \p SM otherwise. + Expected> + parseNumericOperand(StringRef &Expr, AllowedOperand AO, + const SourceMgr &SM) const; + /// Parses \p Expr for a binary operation. The left operand of this binary + /// operation is given in \p LeftOp and \p IsLegacyLineExpr indicates whether + /// we are parsing a legacy @LINE expression. \returns the class representing + /// the binary operation in the AST of the expression, or an error holding a + /// diagnostic against \p SM otherwise. + Expected> + parseBinop(StringRef &Expr, std::unique_ptr LeftOp, + bool IsLegacyLineExpr, const SourceMgr &SM) const; }; //===----------------------------------------------------------------------===// diff --git a/llvm/lib/Support/FileCheck.cpp b/llvm/lib/Support/FileCheck.cpp --- a/llvm/lib/Support/FileCheck.cpp +++ b/llvm/lib/Support/FileCheck.cpp @@ -35,17 +35,32 @@ Value = None; } -Expected FileCheckExpression::eval() const { - assert(LeftOp && "Evaluating an empty expression"); - Optional LeftOpValue = LeftOp->getValue(); - // Variable is undefined. - if (!LeftOpValue) - return make_error(LeftOp->getName()); - return EvalBinop(*LeftOpValue, RightOp); +Expected FileCheckNumericVariableUse::eval() const { + Optional Value = NumericVariable->getValue(); + if (Value) + return *Value; + return make_error(Name); +} + +Expected FileCheckASTBinop::eval() const { + Expected LeftOp = this->LeftOp->eval(); + Expected RightOp = this->RightOp->eval(); + + // Bubble up any error (eg. undefined variable) in the recursive evaluation. + if (!LeftOp || !RightOp) { + Error Err = Error::success(); + if (!LeftOp) + Err = joinErrors(std::move(Err), LeftOp.takeError()); + if (!RightOp) + Err = joinErrors(std::move(Err), RightOp.takeError()); + return std::move(Err); + } + + return EvalBinop(*LeftOp, *RightOp); } Expected FileCheckNumericSubstitution::getResult() const { - Expected EvaluatedValue = Expression->eval(); + Expected EvaluatedValue = ExpressionAST->eval(); if (!EvaluatedValue) return EvaluatedValue.takeError(); return utostr(*EvaluatedValue); @@ -143,15 +158,9 @@ return DefinedNumericVariable; } -Expected -FileCheckPattern::parseNumericVariableUse(StringRef &Expr, +Expected> +FileCheckPattern::parseNumericVariableUse(StringRef Name, bool IsPseudo, const SourceMgr &SM) const { - bool IsPseudo; - Expected ParseVarResult = parseVariable(Expr, IsPseudo, SM); - if (!ParseVarResult) - return ParseVarResult.takeError(); - StringRef Name = *ParseVarResult; - if (IsPseudo && !Name.equals("@LINE")) return FileCheckErrorDiagnostic::get( SM, Name, "invalid pseudo numeric variable '" + Name + "'"); @@ -178,7 +187,33 @@ SM, Name, "numeric variable '" + Name + "' defined on the same line as used"); - return NumericVariable; + return llvm::make_unique(Name, NumericVariable); +} + +Expected> +FileCheckPattern::parseNumericOperand(StringRef &Expr, AllowedOperand AO, + const SourceMgr &SM) const { + if (AO == AllowedOperand::LineVar || AO == AllowedOperand::Any) { + // Try to parse as a numeric variable use. + bool IsPseudo; + Expected ParseVarResult = parseVariable(Expr, IsPseudo, SM); + if (ParseVarResult) { + StringRef Name = *ParseVarResult; + return parseNumericVariableUse(Name, IsPseudo, SM); + } + if (AO == AllowedOperand::LineVar) + return ParseVarResult.takeError(); + // Ignore the error and retry parsing as a literal. + consumeError(ParseVarResult.takeError()); + } + + // Otherwise, parse it as a literal. + uint64_t LiteralValue; + if (!Expr.consumeInteger(/*Radix=*/10, LiteralValue)) + return llvm::make_unique(LiteralValue); + + return FileCheckErrorDiagnostic::get(SM, Expr, + "invalid operand format '" + Expr + "'"); } static uint64_t add(uint64_t LeftOp, uint64_t RightOp) { @@ -189,20 +224,16 @@ return LeftOp - RightOp; } -Expected -FileCheckPattern::parseBinop(StringRef &Expr, const SourceMgr &SM) const { - Expected LeftParseResult = - parseNumericVariableUse(Expr, SM); - if (!LeftParseResult) { - return LeftParseResult.takeError(); - } - FileCheckNumericVariable *LeftOp = *LeftParseResult; +Expected> +FileCheckPattern::parseBinop(StringRef &Expr, + std::unique_ptr LeftOp, + bool IsLegacyLineExpr, const SourceMgr &SM) const { + Expr = Expr.ltrim(SpaceChars); + if (Expr.empty()) + return LeftOp; // Check if this is a supported operation and select a function to perform // it. - Expr = Expr.ltrim(SpaceChars); - if (Expr.empty()) - return Context->makeExpression(add, LeftOp, 0); SMLoc OpLoc = SMLoc::getFromPointer(Expr.data()); char Operator = popFront(Expr); binop_eval_t EvalBinop; @@ -223,22 +254,24 @@ if (Expr.empty()) return FileCheckErrorDiagnostic::get(SM, Expr, "missing operand in expression"); - uint64_t RightOp; - if (Expr.consumeInteger(10, RightOp)) - return FileCheckErrorDiagnostic::get( - SM, Expr, "invalid offset in expression '" + Expr + "'"); - Expr = Expr.ltrim(SpaceChars); - if (!Expr.empty()) - return FileCheckErrorDiagnostic::get( - SM, Expr, "unexpected characters at end of expression '" + Expr + "'"); + // The second operand in a legacy @LINE expression is always a literal. + AllowedOperand AO = + IsLegacyLineExpr ? AllowedOperand::Literal : AllowedOperand::Any; + Expected> RightOpResult = + parseNumericOperand(Expr, AO, SM); + if (!RightOpResult) + return RightOpResult; - return Context->makeExpression(EvalBinop, LeftOp, RightOp); + Expr = Expr.ltrim(SpaceChars); + return llvm::make_unique(EvalBinop, std::move(LeftOp), + std::move(*RightOpResult)); } -Expected FileCheckPattern::parseNumericSubstitutionBlock( +Expected> +FileCheckPattern::parseNumericSubstitutionBlock( StringRef Expr, Optional &DefinedNumericVariable, - const SourceMgr &SM) const { + bool IsLegacyLineExpr, const SourceMgr &SM) const { // Parse the numeric variable definition. DefinedNumericVariable = None; size_t DefEnd = Expr.find(':'); @@ -259,12 +292,29 @@ return ParseResult.takeError(); DefinedNumericVariable = *ParseResult; - return Context->makeExpression(add, nullptr, 0); + return nullptr; } // Parse the expression itself. Expr = Expr.ltrim(SpaceChars); - return parseBinop(Expr, SM); + // The first operand in a legacy @LINE expression is always the @LINE pseudo + // variable. + AllowedOperand AO = + IsLegacyLineExpr ? AllowedOperand::LineVar : AllowedOperand::Any; + Expected> ParseResult = + parseNumericOperand(Expr, AO, SM); + while (ParseResult && !Expr.empty()) { + ParseResult = + parseBinop(Expr, std::move(*ParseResult), IsLegacyLineExpr, SM); + // Legacy @LINE expressions only allow 2 operands. + if (ParseResult && IsLegacyLineExpr && !Expr.empty()) + return FileCheckErrorDiagnostic::get( + SM, Expr, + "unexpected characters at end of expression '" + Expr + "'"); + } + if (!ParseResult) + return ParseResult; + return std::move(*ParseResult); } bool FileCheckPattern::parsePattern(StringRef PatternStr, StringRef Prefix, @@ -375,12 +425,13 @@ PatternStr = UnparsedPatternStr.substr(End + 2); bool IsDefinition = false; + bool IsLegacyLineExpr = false; StringRef DefName; StringRef SubstStr; StringRef MatchRegexp; size_t SubstInsertIdx = RegExStr.size(); - // Parse string variable or legacy expression. + // Parse string variable or legacy @LINE expression. if (!IsNumBlock) { size_t VarEndIdx = MatchStr.find(":"); size_t SpacePos = MatchStr.substr(0, VarEndIdx).find_first_of(" \t"); @@ -424,23 +475,24 @@ } else { if (IsPseudo) { MatchStr = OrigMatchStr; - IsNumBlock = true; + IsLegacyLineExpr = IsNumBlock = true; } else SubstStr = Name; } } // Parse numeric substitution block. - FileCheckExpression *Expression; + std::unique_ptr ExpressionAST; Optional DefinedNumericVariable; if (IsNumBlock) { - Expected ParseResult = - parseNumericSubstitutionBlock(MatchStr, DefinedNumericVariable, SM); + Expected> ParseResult = + parseNumericSubstitutionBlock(MatchStr, DefinedNumericVariable, + IsLegacyLineExpr, SM); if (!ParseResult) { logAllUnhandledErrors(ParseResult.takeError(), errs()); return true; } - Expression = *ParseResult; + ExpressionAST = std::move(*ParseResult); if (DefinedNumericVariable) { IsDefinition = true; DefName = (*DefinedNumericVariable)->getName(); @@ -468,8 +520,8 @@ // previous CHECK patterns, and substitution of expressions. FileCheckSubstitution *Substitution = IsNumBlock - ? Context->makeNumericSubstitution(SubstStr, Expression, - SubstInsertIdx) + ? Context->makeNumericSubstitution( + SubstStr, std::move(ExpressionAST), SubstInsertIdx) : Context->makeStringSubstitution(SubstStr, SubstInsertIdx); Substitutions.push_back(Substitution); } @@ -660,7 +712,7 @@ Expected MatchedValue = Substitution->getResult(); // Substitution failed or is not known at match time, print the undefined - // variable it uses. + // variables it uses. if (!MatchedValue) { bool UndefSeen = false; handleAllErrors(MatchedValue.takeError(), @@ -669,13 +721,11 @@ [](const FileCheckErrorDiagnostic &E) {}, [&](const FileCheckUndefVarError &E) { if (!UndefSeen) { - OS << "uses undefined variable "; + OS << "uses undefined variable(s):"; UndefSeen = true; } + OS << " "; E.log(OS); - }, - [](const ErrorInfoBase &E) { - llvm_unreachable("Unexpected error"); }); } else { // Substitution succeeded. Print substituted value. @@ -768,15 +818,6 @@ return VarIter->second; } -FileCheckExpression * -FileCheckPatternContext::makeExpression(binop_eval_t EvalBinop, - FileCheckNumericVariable *OperandLeft, - uint64_t OperandRight) { - Expressions.push_back(llvm::make_unique( - EvalBinop, OperandLeft, OperandRight)); - return Expressions.back().get(); -} - template FileCheckNumericVariable * FileCheckPatternContext::makeNumericVariable(Types... args) { @@ -794,10 +835,10 @@ } FileCheckSubstitution *FileCheckPatternContext::makeNumericSubstitution( - StringRef ExpressionStr, FileCheckExpression *Expression, - size_t InsertIdx) { + StringRef ExpressionStr, + std::unique_ptr ExpressionAST, size_t InsertIdx) { Substitutions.push_back(llvm::make_unique( - this, ExpressionStr, Expression, InsertIdx)); + this, ExpressionStr, std::move(ExpressionAST), InsertIdx)); return Substitutions.back().get(); } diff --git a/llvm/test/FileCheck/line-count.txt b/llvm/test/FileCheck/line-count.txt --- a/llvm/test/FileCheck/line-count.txt +++ b/llvm/test/FileCheck/line-count.txt @@ -50,7 +50,7 @@ 50 ERR9: line-count.txt:[[#@LINE-1]]:17: error: unsupported operation '*' 51 52 BAD10: [[@LINE-x]] -53 ERR10: line-count.txt:[[#@LINE-1]]:19: error: invalid offset in expression 'x' +53 ERR10: line-count.txt:[[#@LINE-1]]:19: error: invalid operand format 'x' 54 55 BAD11: [[@LINE-1x]] 56 ERR11: line-count.txt:[[#@LINE-1]]:20: error: unexpected characters at end of expression 'x' diff --git a/llvm/test/FileCheck/numeric-expression.txt b/llvm/test/FileCheck/numeric-expression.txt --- a/llvm/test/FileCheck/numeric-expression.txt +++ b/llvm/test/FileCheck/numeric-expression.txt @@ -59,8 +59,8 @@ CHECK-NEXT: [[# VAR1 - 1]] CHECK-NEXT: [[# VAR1 - 1 ]] -; Numeric expressions using variables defined on the command-line and an -; immediate interpreted as an unsigned value. +; Numeric expressions using variables defined on other lines and an immediate +; interpreted as an unsigned value. ; Note: 9223372036854775819 = 0x8000000000000000 + 11 ; 9223372036854775808 = 0x8000000000000000 USE UNSIGNED IMM @@ -68,21 +68,29 @@ CHECK-LABEL: USE UNSIGNED IMM CHECK-NEXT: [[#VAR1+9223372036854775808]] -; Numeric expression using undefined variable. +; Numeric expressions using more than one variable defined on other lines. +USE MULTI VAR +31 +42 +CHECK-LABEL: USE MULTI VAR +CHECK-NEXT: [[#VAR2:]] +CHECK-NEXT: [[#VAR1+VAR2]] + +; Numeric expression using undefined variables. RUN: not FileCheck --check-prefix UNDEF-USE --input-file %s %s 2>&1 \ RUN: | FileCheck --strict-whitespace --check-prefix UNDEF-USE-MSG %s UNDEF VAR USE UNDEFVAR: 11 UNDEF-USE-LABEL: UNDEF VAR USE -UNDEF-USE-NEXT: UNDEFVAR: [[#UNDEFVAR]] +UNDEF-USE-NEXT: UNDEFVAR: [[#UNDEFVAR1+UNDEFVAR2]] UNDEF-USE-MSG: numeric-expression.txt:[[#@LINE-1]]:17: error: {{U}}NDEF-USE-NEXT: expected string not found in input -UNDEF-USE-MSG-NEXT: {{U}}NDEF-USE-NEXT: UNDEFVAR: {{\[\[#UNDEFVAR\]\]}} +UNDEF-USE-MSG-NEXT: {{U}}NDEF-USE-NEXT: UNDEFVAR: {{\[\[#UNDEFVAR1\+UNDEFVAR2\]\]}} UNDEF-USE-MSG-NEXT: {{^ \^$}} UNDEF-USE-MSG-NEXT: numeric-expression.txt:[[#@LINE-6]]:1: note: scanning from here UNDEF-USE-MSG-NEXT: UNDEFVAR: 11 UNDEF-USE-MSG-NEXT: {{^\^$}} -UNDEF-USE-MSG-NEXT: numeric-expression.txt:[[#@LINE-9]]:1: note: uses undefined variable "UNDEFVAR" +UNDEF-USE-MSG-NEXT: numeric-expression.txt:[[#@LINE-9]]:1: note: uses undefined variable(s): "UNDEFVAR1" "UNDEFVAR2" UNDEF-USE-MSG-NEXT: UNDEFVAR: 11 UNDEF-USE-MSG-NEXT: {{^\^$}} diff --git a/llvm/test/FileCheck/var-scope.txt b/llvm/test/FileCheck/var-scope.txt --- a/llvm/test/FileCheck/var-scope.txt +++ b/llvm/test/FileCheck/var-scope.txt @@ -34,5 +34,5 @@ GLOBAL: [[$GLOBAL]][[#$GLOBNUM+2]] ERRUNDEF: expected string not found in input -ERRUNDEFLOCAL: uses undefined variable "LOCAL" -ERRUNDEFLOCNUM: uses undefined variable "LOCNUM" +ERRUNDEFLOCAL: uses undefined variable(s): "LOCAL" +ERRUNDEFLOCNUM: uses undefined variable(s): "LOCNUM" diff --git a/llvm/unittests/Support/FileCheckTest.cpp b/llvm/unittests/Support/FileCheckTest.cpp --- a/llvm/unittests/Support/FileCheckTest.cpp +++ b/llvm/unittests/Support/FileCheckTest.cpp @@ -14,50 +14,109 @@ class FileCheckTest : public ::testing::Test {}; +TEST_F(FileCheckTest, Literal) { + // Eval returns the literal's value. + auto Ten = FileCheckExpressionLiteral(10); + Expected Value = Ten.eval(); + EXPECT_TRUE(bool(Value)); + EXPECT_EQ(10U, *Value); + + // Max value can be correctly represented. + auto Max = FileCheckExpressionLiteral(std::numeric_limits::max()); + Value = Max.eval(); + EXPECT_TRUE(bool(Value)); + EXPECT_EQ(std::numeric_limits::max(), *Value); +} +}; + +namespace std { +std::ostream& operator<<(std::ostream& os, const std::set &set) { + bool first = true; + for (const std::string &s : set) { + os << s; + if (!first) + os << " "; + first = false; + } + return os; +} +}; + +namespace { + +static void expectUndefErrors(std::set ExpectedUndefVarNames, + Error Err) { + handleAllErrors(std::move(Err), [&](const FileCheckUndefVarError &E) { + ExpectedUndefVarNames.erase(E.getVarName()); + }); + EXPECT_TRUE(ExpectedUndefVarNames.empty()) << ExpectedUndefVarNames; +} + +static void expectUndefError(const Twine &ExpectedUndefVarName, Error Err) { + expectUndefErrors({ExpectedUndefVarName.str()}, std::move(Err)); +} + TEST_F(FileCheckTest, NumericVariable) { - // Undefined variable: getValue fails, setValue does not trigger assert. + // Undefined variable: getValue and eval fail, error returned by eval holds + // the name of the undefined variable and setValue does not trigger assert. FileCheckNumericVariable FooVar = FileCheckNumericVariable(1, "FOO"); EXPECT_EQ("FOO", FooVar.getName()); - llvm::Optional Value = FooVar.getValue(); - EXPECT_FALSE(Value); - FooVar.clearValue(); + FileCheckNumericVariableUse FooVarUse = + FileCheckNumericVariableUse("FOO", &FooVar); + EXPECT_FALSE(FooVar.getValue()); + Expected EvalResult = FooVarUse.eval(); + EXPECT_FALSE(EvalResult); + expectUndefError("FOO", EvalResult.takeError()); FooVar.setValue(42); - // Defined variable: getValue returns value set. - Value = FooVar.getValue(); - EXPECT_TRUE(Value); + // Defined variable: getValue and eval return value set. + Optional Value = FooVar.getValue(); + EXPECT_TRUE(bool(Value)); EXPECT_EQ(42U, *Value); + EvalResult = FooVarUse.eval(); + EXPECT_TRUE(bool(EvalResult)); + EXPECT_EQ(42U, *EvalResult); - // Clearing variable: getValue fails. + // Clearing variable: getValue and eval fail. Error returned by eval holds + // the name of the cleared variable. FooVar.clearValue(); Value = FooVar.getValue(); EXPECT_FALSE(Value); + EvalResult = FooVarUse.eval(); + EXPECT_FALSE(EvalResult); + expectUndefError("FOO", EvalResult.takeError()); } uint64_t doAdd(uint64_t OpL, uint64_t OpR) { return OpL + OpR; } -static void expectUndefError(const Twine &ExpectedStr, Error Err) { - handleAllErrors(std::move(Err), [&](const FileCheckUndefVarError &E) { - EXPECT_EQ(ExpectedStr.str(), E.getVarName()); - }); -} - -TEST_F(FileCheckTest, Expression) { +TEST_F(FileCheckTest, Binop) { FileCheckNumericVariable FooVar = FileCheckNumericVariable("FOO", 42); - FileCheckExpression Expression = FileCheckExpression(doAdd, &FooVar, 18); + std::unique_ptr FooVarUse = + llvm::make_unique("FOO", &FooVar); + FileCheckNumericVariable BarVar = FileCheckNumericVariable("BAR", 18); + std::unique_ptr BarVarUse = + llvm::make_unique("BAR", &BarVar); + FileCheckASTBinop Binop = + FileCheckASTBinop(doAdd, std::move(FooVarUse), std::move(BarVarUse)); // Defined variable: eval returns right value. - Expected Value = Expression.eval(); + Expected Value = Binop.eval(); EXPECT_TRUE(bool(Value)); EXPECT_EQ(60U, *Value); - // Undefined variable: eval fails, undefined variable returned. We call - // getUndefVarName first to check that it can be called without calling - // eval() first. + // 1 undefined variable: eval fails, error contains name of undefined + // variable. FooVar.clearValue(); - Error EvalError = Expression.eval().takeError(); - EXPECT_TRUE(errorToBool(std::move(EvalError))); - expectUndefError("FOO", std::move(EvalError)); + Value = Binop.eval(); + EXPECT_FALSE(Value); + expectUndefError("FOO", Value.takeError()); + + // 2 undefined variables: eval fails, error contains names of all undefined + // variables. + BarVar.clearValue(); + Value = Binop.eval(); + EXPECT_FALSE(Value); + expectUndefErrors({"FOO", "BAR"}, Value.takeError()); } TEST_F(FileCheckTest, ValidVarNameStart) { @@ -197,7 +256,7 @@ StringRef ExprBufferRef = bufferize(SM, Expr); Optional DefinedNumericVariable; return errorToBool(P.parseNumericSubstitutionBlock( - ExprBufferRef, DefinedNumericVariable, SM) + ExprBufferRef, DefinedNumericVariable, false, SM) .takeError()); } @@ -269,15 +328,12 @@ // Missing offset operand. EXPECT_TRUE(Tester.parseSubstExpect("@LINE+")); - // Cannot parse offset operand. - EXPECT_TRUE(Tester.parseSubstExpect("@LINE+x")); - - // Unexpected string at end of numeric expression. - EXPECT_TRUE(Tester.parseSubstExpect("@LINE+5x")); - // Valid expression. EXPECT_FALSE(Tester.parseSubstExpect("@LINE+5")); EXPECT_FALSE(Tester.parseSubstExpect("FOO+4")); + Tester.initNextPattern(); + EXPECT_FALSE(Tester.parsePatternExpect("[[#FOO+FOO]]")); + EXPECT_FALSE(Tester.parsePatternExpect("[[#FOO+3-FOO]]")); } TEST_F(FileCheckTest, ParsePattern) { @@ -306,7 +362,6 @@ EXPECT_TRUE(Tester.parsePatternExpect("[[#42INVALID]]")); EXPECT_TRUE(Tester.parsePatternExpect("[[#@FOO]]")); EXPECT_TRUE(Tester.parsePatternExpect("[[#@LINE/2]]")); - EXPECT_TRUE(Tester.parsePatternExpect("[[#2+@LINE]]")); EXPECT_TRUE(Tester.parsePatternExpect("[[#YUP:@LINE]]")); // Valid numeric expressions and numeric variable definition. @@ -365,35 +420,37 @@ // the right value. FileCheckNumericVariable LineVar = FileCheckNumericVariable("@LINE", 42); FileCheckNumericVariable NVar = FileCheckNumericVariable("N", 10); - FileCheckExpression LineExpression = FileCheckExpression(doAdd, &LineVar, 0); - FileCheckExpression NExpression = FileCheckExpression(doAdd, &NVar, 3); - FileCheckNumericSubstitution SubstitutionLine = - FileCheckNumericSubstitution(&Context, "@LINE", &LineExpression, 12); + auto LineVarUse = + llvm::make_unique("@LINE", &LineVar); + auto NVarUse = llvm::make_unique("N", &NVar); + FileCheckNumericSubstitution SubstitutionLine = FileCheckNumericSubstitution( + &Context, "@LINE", std::move(LineVarUse), 12); FileCheckNumericSubstitution SubstitutionN = - FileCheckNumericSubstitution(&Context, "N", &NExpression, 30); - Expected Value = SubstitutionLine.getResult(); - EXPECT_TRUE(bool(Value)); - EXPECT_EQ("42", *Value); - Value = SubstitutionN.getResult(); - EXPECT_TRUE(bool(Value)); - EXPECT_EQ("13", *Value); - - // Substitution of an undefined numeric variable fails. + FileCheckNumericSubstitution(&Context, "N", std::move(NVarUse), 30); + SubstValue = SubstitutionLine.getResult(); + EXPECT_TRUE(bool(SubstValue)); + EXPECT_EQ("42", *SubstValue); + SubstValue = SubstitutionN.getResult(); + EXPECT_TRUE(bool(SubstValue)); + EXPECT_EQ("10", *SubstValue); + + // Substitution of an undefined numeric variable fails, error holds name of + // undefined variable. LineVar.clearValue(); - SubstValue = SubstitutionLine.getResult().takeError(); + SubstValue = SubstitutionLine.getResult(); EXPECT_FALSE(bool(SubstValue)); expectUndefError("@LINE", SubstValue.takeError()); NVar.clearValue(); - SubstValue = SubstitutionN.getResult().takeError(); + SubstValue = SubstitutionN.getResult(); EXPECT_FALSE(bool(SubstValue)); expectUndefError("N", SubstValue.takeError()); // Substitution of a defined string variable returns the right value. FileCheckPattern P = FileCheckPattern(Check::CheckPlain, &Context, 1); StringSubstitution = FileCheckStringSubstitution(&Context, "FOO", 42); - Value = StringSubstitution.getResult(); - EXPECT_TRUE(bool(Value)); - EXPECT_EQ("BAR", *Value); + SubstValue = StringSubstitution.getResult(); + EXPECT_TRUE(bool(SubstValue)); + EXPECT_EQ("BAR", *SubstValue); } TEST_F(FileCheckTest, FileCheckContext) { @@ -456,14 +513,15 @@ Expected LocalVar = Cxt.getPatternVarValue(LocalVarStr); FileCheckPattern P = FileCheckPattern(Check::CheckPlain, &Cxt, 1); Optional DefinedNumericVariable; - Expected Expression = P.parseNumericSubstitutionBlock( - LocalNumVarRef, DefinedNumericVariable, SM); - Expected EmptyVar = Cxt.getPatternVarValue(EmptyVarStr); - Expected UnknownVar = Cxt.getPatternVarValue(UnknownVarStr); + Expected> ExpressionAST = + P.parseNumericSubstitutionBlock(LocalNumVarRef, DefinedNumericVariable, + /*IsLegacyLineExpr=*/false, SM); EXPECT_TRUE(bool(LocalVar)); EXPECT_EQ(*LocalVar, "FOO"); - EXPECT_TRUE(bool(Expression)); - Expected ExpressionVal = (*Expression)->eval(); + Expected EmptyVar = Cxt.getPatternVarValue(EmptyVarStr); + Expected UnknownVar = Cxt.getPatternVarValue(UnknownVarStr); + EXPECT_TRUE(bool(ExpressionAST)); + Expected ExpressionVal = (*ExpressionAST)->eval(); EXPECT_TRUE(bool(ExpressionVal)); EXPECT_EQ(*ExpressionVal, 18U); EXPECT_TRUE(bool(EmptyVar)); @@ -478,12 +536,12 @@ // local variables, if it was created before. This is important because local // variable clearing due to --enable-var-scope happens after numeric // expressions are linked to the numeric variables they use. - EXPECT_TRUE(errorToBool((*Expression)->eval().takeError())); + EXPECT_TRUE(errorToBool((*ExpressionAST)->eval().takeError())); P = FileCheckPattern(Check::CheckPlain, &Cxt, 2); - Expression = P.parseNumericSubstitutionBlock(LocalNumVarRef, - DefinedNumericVariable, SM); - EXPECT_TRUE(bool(Expression)); - ExpressionVal = (*Expression)->eval(); + ExpressionAST = P.parseNumericSubstitutionBlock( + LocalNumVarRef, DefinedNumericVariable, /*IsLegacyLineExpr=*/false, SM); + EXPECT_TRUE(bool(ExpressionAST)); + ExpressionVal = (*ExpressionAST)->eval(); EXPECT_TRUE(errorToBool(ExpressionVal.takeError())); EmptyVar = Cxt.getPatternVarValue(EmptyVarStr); EXPECT_TRUE(errorToBool(EmptyVar.takeError())); @@ -501,10 +559,10 @@ EXPECT_TRUE(bool(GlobalVar)); EXPECT_EQ(*GlobalVar, "BAR"); P = FileCheckPattern(Check::CheckPlain, &Cxt, 3); - Expression = P.parseNumericSubstitutionBlock(GlobalNumVarRef, - DefinedNumericVariable, SM); - EXPECT_TRUE(bool(Expression)); - ExpressionVal = (*Expression)->eval(); + ExpressionAST = P.parseNumericSubstitutionBlock( + GlobalNumVarRef, DefinedNumericVariable, /*IsLegacyLineExpr=*/false, SM); + EXPECT_TRUE(bool(ExpressionAST)); + ExpressionVal = (*ExpressionAST)->eval(); EXPECT_TRUE(bool(ExpressionVal)); EXPECT_EQ(*ExpressionVal, 36U); @@ -512,10 +570,10 @@ Cxt.clearLocalVars(); EXPECT_FALSE(errorToBool(Cxt.getPatternVarValue(GlobalVarStr).takeError())); P = FileCheckPattern(Check::CheckPlain, &Cxt, 4); - Expression = P.parseNumericSubstitutionBlock(GlobalNumVarRef, - DefinedNumericVariable, SM); - EXPECT_TRUE(bool(Expression)); - ExpressionVal = (*Expression)->eval(); + ExpressionAST = P.parseNumericSubstitutionBlock( + GlobalNumVarRef, DefinedNumericVariable, /*IsLegacyLineExpr=*/false, SM); + EXPECT_TRUE(bool(ExpressionAST)); + ExpressionVal = (*ExpressionAST)->eval(); EXPECT_TRUE(bool(ExpressionVal)); EXPECT_EQ(*ExpressionVal, 36U); }