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 @@ -105,12 +105,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 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 ````. + ```` that can be used in ``CHECK:`` lines. See section + ``FileCheck Numeric Variables and Expressions`` for details on supported + numeric expressions. .. option:: -version @@ -623,11 +623,19 @@ due to ``7`` being unequal to ``5 + 1``. +A numeric variable can also be defined to the result of a numeric expression, +in which case the numeric expression constraint is checked and if verified the +variable is assigned to the value. The unified syntax for both defining numeric +variables and checking a numeric expression is thus +``[[#%,: ]]`` with each element as +described previously. + The ``--enable-var-scope`` option has the same effect on numeric variables as on string variables. Important note: In its current implementation, an expression cannot use a -numeric variable defined on the same line. +numeric variable with a non-empty expression constraint defined on the same +line. FileCheck Pseudo Numeric Variables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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 @@ -97,6 +97,11 @@ /// Name of the numeric variable. StringRef Name; + /// Pointer to expression defining this numeric variable. Null for pseudo + /// variable whose value is known at parse time (e.g. @LINE pseudo variable) + /// or cleared local variable. + std::shared_ptr ExpressionAST; + /// Value of numeric variable, if defined, or None otherwise. Optional Value; @@ -105,14 +110,18 @@ size_t DefLineNumber; public: - /// Constructor for a variable \p Name defined at line \p DefLineNumber. - FileCheckNumericVariable(size_t DefLineNumber, StringRef Name) - : Name(Name), DefLineNumber(DefLineNumber) {} + /// Constructor for a variable \p Name defined at line \p DefLineNumber to + /// the expression represented by \p ExpressionAST. + FileCheckNumericVariable( + size_t DefLineNumber, StringRef Name, + std::shared_ptr ExpressionAST) + : Name(Name), ExpressionAST(ExpressionAST), DefLineNumber(DefLineNumber) { + } /// Constructor for numeric variable \p Name with a known \p Value at parse /// time (e.g. the @LINE numeric variable). FileCheckNumericVariable(StringRef Name, uint64_t Value) - : Name(Name), Value(Value), DefLineNumber(0) {} + : Name(Name), ExpressionAST(nullptr), Value(Value), DefLineNumber(0) {} /// \returns name of this numeric variable. StringRef getName() const { return Name; } @@ -120,6 +129,10 @@ /// \returns this variable's value. Expected eval() const; + /// \returns whether this variable's value is known at match time, when + /// performing the substitutions. + bool isMatchTimeKnown() const; + /// Sets value of this numeric variable if not defined. \returns whether the /// variable was already defined. bool setValue(uint64_t Value); @@ -217,9 +230,9 @@ public: FileCheckNumericSubstitution( - FileCheckPatternContext *Context, StringRef Expr, + FileCheckPatternContext *Context, StringRef ExpressionStr, std::shared_ptr ExpressionAST, size_t InsertIdx) - : FileCheckSubstitution(Context, Expr, InsertIdx), + : FileCheckSubstitution(Context, ExpressionStr, InsertIdx), ExpressionAST(ExpressionAST) {} /// \returns a string containing the result of evaluating the expression in @@ -467,26 +480,22 @@ /// name. static Expected parseVariable(StringRef &Str, bool &IsPseudo, const SourceMgr &SM); - /// Parses \p Expr for the name of a numeric variable to be defined. \returns - /// an error holding a diagnostic against \p SM should defining such a - /// variable be invalid, or Success otherwise. In the latter case, sets - /// \p Name to the name of the parsed numeric variable name. - static Error parseNumericVariableDefinition(StringRef &Expr, StringRef &Name, - FileCheckPatternContext *Context, - const SourceMgr &SM); - /// Parses \p Expr for a numeric substitution block. Parameter - /// \p LegacyLineExpr 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 substitued, or an error holding a + /// Parses \p Expr for a numeric substitution block at line \p LineNumber, + /// where 0 indicate the command-line. Parameter \p LegacyLineExpr indicates + /// whether \p Expr should be a legacy @LINE expression and \p Context points + /// to the class instance holding the live string and numeric variables. + /// \returns a pointer to the class instance representing the AST of the + /// expression whose value must be substitued, 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 substitution /// block, or None if this block does not define any variable. - Expected> + static Expected> parseNumericSubstitutionBlock( StringRef Expr, Optional &DefinedNumericVariable, - bool LegacyLineExpr, const SourceMgr &SM) const; + bool LegacyLineExpr, size_t LineNumber, FileCheckPatternContext *Context, + const SourceMgr &SM); /// Parses the pattern in \p PatternStr and initializes this FileCheckPattern /// instance accordingly. /// @@ -540,28 +549,43 @@ /// was not found. size_t FindRegexVarEnd(StringRef Str, SourceMgr &SM); - /// 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 Name, bool IsPseudo, - const SourceMgr &SM) const; + /// Parses \p Expr for the use of a numeric variable at line \p LineNumber, + /// where 0 indicates the command-line. Parameter \p Context points to the + /// class instance holding the live string and numeric variables. \returns + /// the pointer to the class instance representing that variable if + /// successful, or an error holding a diagnostic against \p SM otherwise. + static Expected> + parseNumericVariableUse(StringRef Name, bool IsPseudo, size_t LineNumber, + FileCheckPatternContext *Context, + const SourceMgr &SM); + /// Parses \p Expr for the name of a numeric variable to be defined. \returns + /// an error holding a diagnostic against \p SM should defining such a + /// variable be invalid, or Success otherwise. In the latter case, sets + /// \p Name to the name of the parsed numeric variable name. + static Error parseNumericVariableDefinition(StringRef &Expr, StringRef &Name, + FileCheckPatternContext *Context, + const SourceMgr &SM); enum 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 LegacyLineExpr 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> + /// Parses \p Expr for use of a numeric operand at line \p LineNumber, where + /// 0 indicates the command-line. Accepts both literal values and numeric + /// variables, depending on the value of \p AO. Parameter \p Context points + /// to the class instance holding the live string and numeric variables. + /// \returns the class representing that operand in the AST of the expression + /// or an error holding a diagnostic against \p SM otherwise. + static Expected> + parseNumericOperand(StringRef &Expr, AllowedOperand AO, size_t LineNumber, + FileCheckPatternContext *Context, const SourceMgr &SM); + /// Parses \p Expr for a binary operation at line \p LineNumber, where 0 + /// indicates the command-line. The left operand of this binary operation is + /// given in \p LeftOp and \p LegacyLineExpr indicates whether we are parsing + /// a legacy @LINE expression. Parameter \p Context points to the class + /// instance holding the live string and numeric variables. \returns the + /// class representing the binary operation in the AST of the expression, or + /// an error holding a diagnostic against \p SM otherwise. + static Expected> parseBinop(StringRef &Expr, std::shared_ptr LeftOp, - bool LegacyLineExpr, const SourceMgr &SM) const; + bool LegacyLineExpr, size_t LineNumber, + FileCheckPatternContext *Context, const SourceMgr &SM); }; //===----------------------------------------------------------------------===// 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 @@ -27,12 +27,28 @@ Expected FileCheckNumericVariable::eval() const { if (Value) return *Value; - return make_error(Name); + + if (ExpressionAST == nullptr) + return make_error(Name); + + return ExpressionAST->eval(); +} + +bool FileCheckNumericVariable::isMatchTimeKnown() const { + if (Value) + return true; + + return ExpressionAST != nullptr; } bool FileCheckNumericVariable::setValue(uint64_t NewValue) { if (Value) return true; + if (ExpressionAST != nullptr) { + Expected EvaluatedValue = ExpressionAST->eval(); + if (!EvaluatedValue || *EvaluatedValue != NewValue) + return true; + } Value = NewValue; return false; } @@ -41,6 +57,7 @@ if (!Value) return true; Value = None; + ExpressionAST = nullptr; return false; } @@ -149,7 +166,9 @@ Expected> FileCheckPattern::parseNumericVariableUse(StringRef Name, bool IsPseudo, - const SourceMgr &SM) const { + size_t LineNumber, + FileCheckPatternContext *Context, + const SourceMgr &SM) { if (IsPseudo && !Name.equals("@LINE")) return FileCheckErrorDiagnostic::get( SM, Name, "invalid pseudo numeric variable '" + Name + "'"); @@ -168,24 +187,28 @@ std::shared_ptr NumericVariable = VarTableIter->second; - if (!IsPseudo && NumericVariable->getDefLineNumber() == LineNumber) + if (!IsPseudo && NumericVariable->getDefLineNumber() == LineNumber && + !NumericVariable->isMatchTimeKnown()) return FileCheckErrorDiagnostic::get( SM, Name, - "numeric variable '" + Name + "' defined on the same line as used"); + "numeric variable '" + Name + + "' defined from input on the same line as used"); return NumericVariable; } Expected> FileCheckPattern::parseNumericOperand(StringRef &Expr, AllowedOperand AO, - const SourceMgr &SM) const { + size_t LineNumber, + FileCheckPatternContext *Context, + const SourceMgr &SM) { if (AO == LineVar || AO == 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); + return parseNumericVariableUse(Name, IsPseudo, LineNumber, Context, SM); } if (AO == LineVar) return ParseVarResult.takeError(); @@ -210,10 +233,10 @@ return LeftOp - RightOp; } -Expected> -FileCheckPattern::parseBinop(StringRef &Expr, - std::shared_ptr LeftOp, - bool LegacyLineExpr, const SourceMgr &SM) const { +Expected> FileCheckPattern::parseBinop( + StringRef &Expr, std::shared_ptr LeftOp, + bool LegacyLineExpr, size_t LineNumber, FileCheckPatternContext *Context, + const SourceMgr &SM) { Expr = Expr.ltrim(SpaceChars); if (Expr.empty()) return LeftOp; @@ -243,7 +266,7 @@ // Second operand in legacy @LINE expression is a literal. AllowedOperand AO = LegacyLineExpr ? Literal : Any; Expected> RightOpResult = - parseNumericOperand(Expr, AO, SM); + parseNumericOperand(Expr, AO, LineNumber, Context, SM); if (!RightOpResult) return RightOpResult; std::shared_ptr RightOp = *RightOpResult; @@ -256,43 +279,28 @@ FileCheckPattern::parseNumericSubstitutionBlock( StringRef Expr, Optional &DefinedNumericVariable, - bool LegacyLineExpr, const SourceMgr &SM) const { + bool LegacyLineExpr, size_t LineNumber, FileCheckPatternContext *Context, + const SourceMgr &SM) { std::shared_ptr ExpressionAST = nullptr; - // Parse the numeric variable definition. + StringRef DefExpr = StringRef(); DefinedNumericVariable = None; + // Save variable definition expression if any. size_t DefEnd = Expr.find(':'); if (DefEnd != StringRef::npos) { - StringRef DefExpr = Expr.substr(0, DefEnd); - StringRef UseExpr = Expr = Expr.substr(DefEnd + 1); - - DefExpr = DefExpr.ltrim(SpaceChars); - StringRef Name; - Error ErrorDiagnostic = - parseNumericVariableDefinition(DefExpr, Name, Context, SM); - if (ErrorDiagnostic) - return std::move(ErrorDiagnostic); - - DefExpr = DefExpr.ltrim(SpaceChars); - if (!DefExpr.empty()) - return FileCheckErrorDiagnostic::get( - SM, DefExpr, "invalid numeric variable definition"); - UseExpr = UseExpr.ltrim(SpaceChars); - if (!UseExpr.empty()) - return FileCheckErrorDiagnostic::get( - SM, UseExpr, - "unexpected string after variable definition: '" + UseExpr + "'"); + DefExpr = Expr.substr(0, DefEnd); + Expr = Expr.substr(DefEnd + 1); + } - DefinedNumericVariable = - new FileCheckNumericVariable(this->LineNumber, Name); - } else { - // Parse the expression itself. - Expr = Expr.ltrim(SpaceChars); + // Parse the expression itself. + Expr = Expr.ltrim(SpaceChars); + if (!Expr.empty()) { // First operand in legacy @LINE expression is the @LINE pseudo variable. AllowedOperand AO = LegacyLineExpr ? LineVar : Any; Expected> ParseResult = - parseNumericOperand(Expr, AO, SM); + parseNumericOperand(Expr, AO, LineNumber, Context, SM); while (ParseResult && !Expr.empty()) { - ParseResult = parseBinop(Expr, *ParseResult, LegacyLineExpr, SM); + ParseResult = parseBinop(Expr, *ParseResult, LegacyLineExpr, LineNumber, + Context, SM); // Legacy @LINE expressions only allow 2 operands. if (ParseResult && LegacyLineExpr && !Expr.empty()) return FileCheckErrorDiagnostic::get( @@ -304,6 +312,24 @@ ExpressionAST = *ParseResult; } + // Parse the numeric variable definition. + if (DefEnd != StringRef::npos) { + DefExpr = DefExpr.ltrim(SpaceChars); + StringRef Name; + Error ParseError = + parseNumericVariableDefinition(DefExpr, Name, Context, SM); + if (ParseError) + return std::move(ParseError); + + DefExpr = DefExpr.ltrim(SpaceChars); + if (!DefExpr.empty()) + return FileCheckErrorDiagnostic::get( + SM, DefExpr, "invalid numeric variable definition"); + + DefinedNumericVariable = + new FileCheckNumericVariable(LineNumber, Name, ExpressionAST); + } + return ExpressionAST; } @@ -394,14 +420,15 @@ continue; } - // String and numeric substitution blocks. String substitution blocks come + // String and numeric substitution blocks. Pattern substitution blocks come // in two forms: [[foo:.*]] and [[foo]]. The former matches .* (or some // other regex) and assigns it to the string variable 'foo'. The latter - // substitutes foo's value. Numeric substitution blocks work the same way - // as string ones, but start with a '#' sign after the double brackets. - // Both string and numeric variable names must satisfy the regular - // expression "[a-zA-Z_][0-9a-zA-Z_]*" to be valid, as this helps catch - // some common errors. + // substitutes foo's value. Numeric substitution blocks recognize the same + // form as string ones, but start with a '#' sign after the double + // brackets. They also accept a combined form which sets a numeric variable + // to the evaluation of an expression. Both string and numeric variable + // names must satisfy the regular expression "[a-zA-Z_][0-9a-zA-Z_]*" to be + // valid, as this helps catch some common errors. if (PatternStr.startswith("[[")) { StringRef UnparsedPatternStr = PatternStr.substr(2); // Find the closing bracket pair ending the match. End is going to be an @@ -422,10 +449,11 @@ PatternStr = UnparsedPatternStr.substr(End + 2); bool IsDefinition = false; + bool SubstNeeded = false; bool IsLegacyLineExpr = false; StringRef DefName; StringRef SubstStr; - StringRef MatchRegexp; + StringRef MatchRegexp = StringRef(); size_t SubstInsertIdx = RegExStr.size(); // Parse string variable or legacy @LINE expression. @@ -450,6 +478,7 @@ StringRef Name = *ParseVarResult; IsDefinition = (VarEndIdx != StringRef::npos); + SubstNeeded = !IsDefinition; if (IsDefinition) { if ((IsPseudo || !MatchStr.consume_front(":"))) { SM.PrintMessage(SMLoc::getFromPointer(Name.data()), @@ -484,22 +513,61 @@ if (IsNumBlock) { Expected> ParseResult = parseNumericSubstitutionBlock(MatchStr, DefinedNumericVariable, - IsLegacyLineExpr, SM); + IsLegacyLineExpr, LineNumber, Context, + SM); if (!ParseResult) { logAllUnhandledErrors(ParseResult.takeError(), errs()); return true; } ExpressionAST = *ParseResult; - if (DefinedNumericVariable) { - IsDefinition = true; + IsDefinition = static_cast(DefinedNumericVariable); + SubstNeeded = ExpressionAST != nullptr; + if (IsDefinition) DefName = (*DefinedNumericVariable)->getName(); - MatchRegexp = StringRef("[0-9]+"); - } else + if (SubstNeeded) SubstStr = MatchStr; + else + MatchRegexp = StringRef("[0-9]+"); + } + + // Handle variable definition: [[:(...)]] and [[#(...):(...)]]. + if (IsDefinition) { + RegExStr += '('; + ++SubstInsertIdx; + + if (IsNumBlock) { + struct FileCheckNumericVariableMatch NumericVariableDefinition = { + *DefinedNumericVariable, CurParen}; + NumericVariableDefs[DefName] = NumericVariableDefinition; + // This store is done here rather than in match() to allow + // parseNumericVariableUse() to get the pointer to the class instance + // of the right variable definition corresponding to a given numeric + // variable use. + Context->GlobalNumericVariableTable[DefName] = + std::shared_ptr( + *DefinedNumericVariable); + } else { + VariableDefs[DefName] = CurParen; + // Mark string variable as defined to detect collisions between + // string and numeric variables in parseNumericVariableUse() and + // DefineCmdlineVariables() when the latter is created later than the + // former. We cannot reuse GlobalVariableTable for this by populating + // it with an empty string since we would then lose the ability to + // detect the use of an undefined variable in match(). + Context->DefinedVariableTable[DefName] = true; + } + + ++CurParen; } + if (!MatchRegexp.empty() && AddRegExToRegEx(MatchRegexp, CurParen, SM)) + return true; + + if (IsDefinition) + RegExStr += ')'; + // Handle substitutions: [[foo]] and [[#]]. - if (!IsDefinition) { + if (SubstNeeded) { // Handle substitution of string variables that were defined earlier on // the same line by emitting a backreference. Expressions do not // support substituting a numeric variable defined on the same line. @@ -522,38 +590,7 @@ : Context->makeStringSubstitution(SubstStr, SubstInsertIdx); Substitutions.push_back(Substitution); } - continue; } - - // Handle variable definitions: [[:(...)]] and - // [[#(...):(...)]]. - if (IsNumBlock) { - FileCheckNumericVariableMatch NumericVariableDefinition = { - *DefinedNumericVariable, CurParen}; - NumericVariableDefs[DefName] = NumericVariableDefinition; - // This store is done here rather than in match() to allow - // parseNumericVariableUse() to get the pointer to the class instance - // of the right variable definition corresponding to a given numeric - // variable use. - Context->GlobalNumericVariableTable[DefName] = - std::shared_ptr(*DefinedNumericVariable); - } else { - VariableDefs[DefName] = CurParen; - // Mark the string variable as defined to detect collisions between - // string and numeric variables in parseNumericVariableUse() and - // DefineCmdlineVariables() when the latter is created later than the - // former. We cannot reuse GlobalVariableTable for this by populating - // it with an empty string since we would then lose the ability to - // detect the use of an undefined variable in match(). - Context->DefinedVariableTable[DefName] = true; - } - RegExStr += '('; - ++CurParen; - - if (AddRegExToRegEx(MatchRegexp, CurParen, SM)) - return true; - - RegExStr += ')'; } // Handle fixed string matches. @@ -1740,9 +1777,35 @@ std::string CmdlineDefsDiag; StringRef Prefix1 = "Global define #"; StringRef Prefix2 = ": "; - for (StringRef CmdlineDef : CmdlineDefines) - CmdlineDefsDiag += - (Prefix1 + Twine(++I) + Prefix2 + CmdlineDef + "\n").str(); + StringRef Suffix1 = " (parsed as: [["; + StringRef Suffix3 = "]])"; + SmallVector, 4> CmdlineDefsIndices; + for (StringRef CmdlineDef : CmdlineDefines) { + StringRef DefNo = Twine(++I).str(); + size_t EqIdx = CmdlineDef.find('='); + if (EqIdx == StringRef::npos) { + CmdlineDefsIndices.push_back(std::make_pair(CmdlineDefsDiag.size(), 0)); + continue; + } + // Numeric variable definition. + if (CmdlineDef[0] == '#') { + // Append a copy command-line definition adapted to use the same format + // as in the input file to be able to reuse + // ParseNumericSubstitutionBlock. + CmdlineDefsDiag += + (Prefix1 + DefNo + Prefix2 + CmdlineDef + Suffix1).str(); + std::string Suffix2 = CmdlineDef; + Suffix2[EqIdx] = ':'; + CmdlineDefsIndices.push_back( + std::make_pair(CmdlineDefsDiag.size(), Suffix2.size())); + CmdlineDefsDiag += (Suffix2 + Suffix3 + "\n").str(); + } else { + CmdlineDefsDiag += (Prefix1 + DefNo + Prefix2).str(); + CmdlineDefsIndices.push_back( + std::make_pair(CmdlineDefsDiag.size(), CmdlineDef.size())); + CmdlineDefsDiag += (CmdlineDef + "\n").str(); + } + } // Create a buffer with fake command line content in order to display // parsing diagnostic with location information and point to the @@ -1752,14 +1815,11 @@ StringRef CmdlineDefsDiagRef = CmdLineDefsDiagBuffer->getBuffer(); SM.AddNewSourceBuffer(std::move(CmdLineDefsDiagBuffer), SMLoc()); - SmallVector CmdlineDefsDiagVec; - CmdlineDefsDiagRef.split(CmdlineDefsDiagVec, '\n', -1 /*MaxSplit*/, - false /*KeepEmpty*/); - for (StringRef CmdlineDefDiag : CmdlineDefsDiagVec) { - unsigned DefStart = CmdlineDefDiag.find(Prefix2) + Prefix2.size(); - StringRef CmdlineDef = CmdlineDefDiag.substr(DefStart); - size_t EqIdx = CmdlineDef.find('='); - if (EqIdx == StringRef::npos) { + for (std::pair CmdlineDefIndices : CmdlineDefsIndices) { + StringRef CmdlineDef = CmdlineDefsDiagRef.substr(CmdlineDefIndices.first, + CmdlineDefIndices.second); + // Error already handled in previous loop, skip this. + if (CmdlineDef.empty()) { Errs = joinErrors( std::move(Errs), FileCheckErrorDiagnostic::get( @@ -1769,52 +1829,29 @@ // Numeric variable definition. if (CmdlineDef[0] == '#') { - StringRef CmdlineName = CmdlineDef.substr(1, EqIdx - 1); - StringRef VarName; - SMLoc CmdlineNameLoc = SMLoc::getFromPointer(CmdlineName.data()); - Error ErrorDiagnostic = FileCheckPattern::parseNumericVariableDefinition( - CmdlineName, VarName, this, SM); - if (ErrorDiagnostic) { - Errs = joinErrors(std::move(Errs), std::move(ErrorDiagnostic)); - continue; - } - // Check that CmdlineName is only composed of the parsed numeric - // variable. This catches cases like "FOO+2" in a "FOO+2=10" definition. - if (!CmdlineName.empty()) { - Errs = joinErrors(std::move(Errs), - FileCheckErrorDiagnostic::get( - SM, CmdlineNameLoc, "invalid variable name")); - continue; - } - - // Detect collisions between string and numeric variables when the latter - // is created later than the former. - if (DefinedVariableTable.find(VarName) != DefinedVariableTable.end()) { - Errs = joinErrors( - std::move(Errs), - FileCheckErrorDiagnostic::get(SM, VarName, - "string variable with name '" + - VarName + "' already exists")); + // Now parse to both check syntax is correct and create the necessary + // class instance. + StringRef CmdlineDefExpr = CmdlineDef.substr(1); + Optional DefinedNumericVariable; + Expected> ExpressionASTResult = + FileCheckPattern::parseNumericSubstitutionBlock( + CmdlineDefExpr, DefinedNumericVariable, false, 0, this, SM); + if (!ExpressionASTResult) { + Errs = joinErrors(std::move(Errs), ExpressionASTResult.takeError()); continue; } - - StringRef CmdlineVal = CmdlineDef.substr(EqIdx + 1); - uint64_t Val; - if (CmdlineVal.getAsInteger(10, Val)) { - Errs = joinErrors(std::move(Errs), - FileCheckErrorDiagnostic::get( - SM, CmdlineVal, - "invalid value in numeric variable definition '" + - CmdlineVal + "'")); + std::shared_ptr ExpressionAST = + *ExpressionASTResult; + Expected Value = ExpressionAST->eval(); + if (!Value) { + Errs = joinErrors(std::move(Errs), Value.takeError()); continue; } - auto DefinedNumericVariable = - std::make_shared(0, VarName); - DefinedNumericVariable->setValue(Val); + (*DefinedNumericVariable)->setValue(*Value); // Record this variable definition. - GlobalNumericVariableTable[DefinedNumericVariable->getName()] = - DefinedNumericVariable; + GlobalNumericVariableTable[(*DefinedNumericVariable)->getName()] = + std::shared_ptr(*DefinedNumericVariable); } else { // String variable definition. std::pair CmdlineNameVal = CmdlineDef.split('='); diff --git a/llvm/test/FileCheck/numeric-defines-diagnostics.txt b/llvm/test/FileCheck/numeric-defines-diagnostics.txt --- a/llvm/test/FileCheck/numeric-defines-diagnostics.txt +++ b/llvm/test/FileCheck/numeric-defines-diagnostics.txt @@ -4,30 +4,22 @@ RUN: not FileCheck -D#10VALUE=10 --input-file %s %s 2>&1 \ RUN: | FileCheck %s --strict-whitespace --check-prefix NUMERRCLIFMT -NUMERRCLIFMT: Global defines:1:20: error: invalid variable name -NUMERRCLIFMT-NEXT: Global define #1: #10VALUE=10 -NUMERRCLIFMT-NEXT: {{^ \^$}} +NUMERRCLIFMT: Global defines:1:46: error: invalid variable name +NUMERRCLIFMT-NEXT: Global define #1: #10VALUE=10 (parsed as: {{\[\[#10VALUE:10\]\]}}) +NUMERRCLIFMT-NEXT: {{^ \^$}} ; Invalid definition of pseudo variable. RUN: not FileCheck -D#@VALUE=10 --input-file %s %s 2>&1 \ RUN: | FileCheck %s --strict-whitespace --check-prefix NUMERRCLIPSEUDO -NUMERRCLIPSEUDO: Global defines:1:20: error: definition of pseudo numeric variable unsupported -NUMERRCLIPSEUDO-NEXT: Global define #1: #@VALUE=10 -NUMERRCLIPSEUDO-NEXT: {{^ \^$}} +NUMERRCLIPSEUDO: Global defines:1:45: error: definition of pseudo numeric variable unsupported +NUMERRCLIPSEUDO-NEXT: Global define #1: #@VALUE=10 (parsed as: {{\[\[#@VALUE:10\]\]}}) +NUMERRCLIPSEUDO-NEXT: {{^ \^$}} ; Invalid definition of an expression. RUN: not FileCheck -D#VALUE+2=10 --input-file %s %s 2>&1 \ RUN: | FileCheck %s --strict-whitespace --check-prefix NUMERRCLITRAIL -NUMERRCLITRAIL: Global defines:1:20: error: invalid variable name -NUMERRCLITRAIL-NEXT: Global define #1: #VALUE+2=10 -NUMERRCLITRAIL-NEXT: {{^ \^$}} - -; Invalid value: numeric expression instead of literal. -RUN: not FileCheck -D#VALUE1=3 -D#VALUE2='VALUE1 + 2' --input-file %s %s 2>&1 \ -RUN: | FileCheck %s --strict-whitespace --check-prefix NUMERRCLIEXPR - -NUMERRCLIEXPR: Global defines:2:27: error: invalid value in numeric variable definition 'VALUE1 + 2' -NUMERRCLIEXPR-NEXT: Global define #2: #VALUE2=VALUE1 + 2 -NUMERRCLIEXPR-NEXT: {{^ \^$}} +NUMERRCLITRAIL: Global defines:1:51: error: invalid numeric variable definition +NUMERRCLITRAIL-NEXT: Global define #1: #VALUE+2=10 (parsed as: {{\[\[#VALUE\+2:10\]\]}}) +NUMERRCLITRAIL-NEXT: {{^ \^$}} diff --git a/llvm/test/FileCheck/numeric-defines.txt b/llvm/test/FileCheck/numeric-defines.txt --- a/llvm/test/FileCheck/numeric-defines.txt +++ b/llvm/test/FileCheck/numeric-defines.txt @@ -1,22 +1,22 @@ ; Test functionality of -D# option: numeric variables are defined to the right ; value and CHECK directives using them match as expected given the value set. -RUN: FileCheck -D#NUMVAL=12 --check-prefix CHECKNUM --input-file %s %s -RUN: not FileCheck -D#NUMVAL=8 --check-prefix CHECKNUM --input-file %s %s 2>&1 \ -RUN: | FileCheck %s --strict-whitespace --check-prefix NUMERRMSG -RUN: not FileCheck -D#NUMVAL=12 --check-prefix NUMNOT --input-file %s %s 2>&1 \ -RUN: | FileCheck %s --strict-whitespace --check-prefix NOT-NUMERRMSG -RUN: FileCheck -D#NUMVAL=8 --check-prefixes NUMNOT --input-file %s %s +RUN: FileCheck -D#NUMVAL1=8 -D#NUMVAL2='NUMVAL1 + 4' -check-prefix CHECKNUM -input-file %s %s +RUN: not FileCheck -D#NUMVAL2=8 -check-prefix CHECKNUM -input-file %s %s 2>&1 \ +RUN: | FileCheck %s --strict-whitespace -check-prefix NUMERRMSG +RUN: not FileCheck -D#NUMVAL2=12 -check-prefix NUMNOT -input-file %s %s 2>&1 \ +RUN: | FileCheck %s --strict-whitespace -check-prefix NOT-NUMERRMSG +RUN: FileCheck -D#NUMVAL2=8 -check-prefixes NUMNOT -input-file %s %s -Numeric value = 12 -CHECKNUM: Numeric value = [[#NUMVAL]] -NUMNOT-NOT: Numeric value = [[#NUMVAL]] +Numeric value #2 = 12 +CHECKNUM: Numeric value #2 = [[#NUMVAL2]] +NUMNOT-NOT: Numeric value #2 = [[#NUMVAL2]] NUMERRMSG: defines.txt:[[#@LINE-3]]:11: error: CHECKNUM: expected string not found in input NUMERRMSG: defines.txt:1:1: note: scanning from here -NUMERRMSG: defines.txt:1:1: note: with "NUMVAL" equal to "8" +NUMERRMSG: defines.txt:1:1: note: with "NUMVAL2" equal to "8" NUMERRMSG: defines.txt:[[#@LINE-7]]:1: note: possible intended match here NOT-NUMERRMSG: defines.txt:[[#@LINE-7]]:13: error: {{NUMNOT}}-NOT: excluded string found in input NOT-NUMERRMSG: defines.txt:[[#@LINE-10]]:1: note: found here -NOT-NUMERRMSG: defines.txt:[[#@LINE-11]]:1: note: with "NUMVAL" equal to "12" +NOT-NUMERRMSG: defines.txt:[[#@LINE-11]]:1: note: with "NUMVAL2" equal to "12" 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 @@ -68,6 +68,20 @@ CHECK-LABEL: USE UNSIGNED IMM CHECK-NEXT: [[#VAR1+9223372036854775808]] +; Numeric expression using a variable defined from a numeric expression. +DEF EXPR GOOD MATCH +42 +41 43 +; CHECK-LABEL: DEF EXPR GOOD MATCH +; CHECK-NEXT: [[# VAR42:VAR1+31]] +; CHECK-NEXT: [[# VAR41: VAR42-1]] [[# VAR41 + 2]] + +; Empty numeric expression. +EMPTY NUM EXPR +foo 104 bar +; CHECK-LABEL: EMPTY NUM EXPR +; CHECK-NEXT: foo [[#]] bar + ; Numeric expression using undefined variable. RUN: not FileCheck --check-prefix UNDEF-USE --input-file %s %s 2>&1 \ RUN: | FileCheck --strict-whitespace --check-prefix UNDEF-USE-MSG %s @@ -125,9 +139,9 @@ INPUT-NUM-CONFLICT: numeric-expression.txt:[[#@LINE-7]]:22: error: string variable with name 'STRVAR' already exists INPUT-NUM-CONFLICT-NEXT: CONFLICT4: redef2 {{\[\[#STRVAR:\]\]}} INPUT-NUM-CONFLICT-NEXT: {{^ \^$}} -CLI-NUM-CONFLICT: Global defines:2:20: error: string variable with name 'STRVAR' already exists -CLI-NUM-CONFLICT-NEXT: Global define #2: #STRVAR=42 -CLI-NUM-CONFLICT-NEXT: {{^ \^$}} +CLI-NUM-CONFLICT: Global defines:2:45: error: string variable with name 'STRVAR' already exists +CLI-NUM-CONFLICT-NEXT: Global define #2: #STRVAR=42 (parsed as: {{\[\[#STRVAR:42\]\]}}) +CLI-NUM-CONFLICT-NEXT: {{^ \^$}} ; Numeric variable definition with too big value. RUN: not FileCheck --check-prefix BIGVAL --input-file %s %s 2>&1 \ @@ -140,3 +154,14 @@ BIGVAL-MSG: numeric-expression.txt:[[#@LINE-3]]:9: error: Unable to store numeric value BIGVAL-MSG-NEXT: {{N}}UMVAR: 10000000000000000000000 BIGVAL-MSG-NEXT: {{^ \^$}} + +; Verify that when variable is set to an expression the expression is still +; checked. +RUN: not FileCheck -check-prefix DEF-EXPR-FAIL -input-file %s %s + +DEF EXPR WRONG MATCH +20 +43 +DEF-EXPR-FAIL-LABEL: DEF EXPR WRONG MATCH +DEF-EXPR-FAIL-NEXT: [[# VAR20:]] +DEF-EXPR-FAIL-NEXT: [[# VAR42: VAR20+22]] 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 @@ -22,51 +22,81 @@ EXPECT_EQ(10U, *Value); } -static void expectUndefErrors(std::set ExpectedUndefVarNames, +// Return whether Err contains any FileCheckUndefVarError whose associated name +// is not in ExpectedUndefVarNames. +static bool expectUndefErrors(std::set ExpectedUndefVarNames, Error Err) { handleAllErrors(std::move(Err), [&](const FileCheckUndefVarError &E) { ExpectedUndefVarNames.erase(E.getVarName()); }); - EXPECT_TRUE(ExpectedUndefVarNames.empty()); + return !ExpectedUndefVarNames.empty(); } -static void expectUndefError(const Twine &ExpectedUndefVarName, Error Err) { - expectUndefErrors({ExpectedUndefVarName.str()}, std::move(Err)); +// Return whether Err contains any FileCheckUndefVarError whose associated name +// is not ExpectedUndefVarName. +static bool expectUndefError(const Twine &ExpectedUndefVarName, Error Err) { + return expectUndefErrors({ExpectedUndefVarName.str()}, std::move(Err)); } +uint64_t doAdd(uint64_t OpL, uint64_t OpR) { return OpL + OpR; } + TEST_F(FileCheckTest, NumericVariable) { - // Undefined variable: eval and clearValue fails, error returned by eval - // holds the name of the undefined variable and setValue works. - FileCheckNumericVariable FooVar = FileCheckNumericVariable(1, "FOO"); - EXPECT_EQ("FOO", FooVar.getName()); - Expected Value = FooVar.eval(); + // Undefined variable: isMatchTimeKnown returns false, eval and clearValue + // fail, error returned by eval holds the name of the undefined variable and + // setValue works. + auto FooVar = std::make_shared(1, "FOO", nullptr); + EXPECT_EQ("FOO", FooVar->getName()); + EXPECT_FALSE(FooVar->isMatchTimeKnown()); + Expected Value = FooVar->eval(); EXPECT_FALSE(Value); - expectUndefError("FOO", Value.takeError()); - EXPECT_FALSE(FooVar.setValue(42)); - - // Defined variable: eval returns value set and setValue fails. - Value = FooVar.eval(); + EXPECT_FALSE(expectUndefError("FOO", Value.takeError())); + EXPECT_TRUE(FooVar->clearValue()); + EXPECT_FALSE(FooVar->setValue(42)); + + // Defined variable: isMatchTimeKnown returns true, eval returns value set + // and setValue fails. + EXPECT_TRUE(FooVar->isMatchTimeKnown()); + Value = FooVar->eval(); EXPECT_TRUE(static_cast(Value)); EXPECT_EQ(42U, *Value); - EXPECT_TRUE(FooVar.setValue(43)); - Value = FooVar.eval(); + EXPECT_TRUE(FooVar->setValue(43)); + Value = FooVar->eval(); EXPECT_TRUE(static_cast(Value)); EXPECT_EQ(42U, *Value); - // Clearing variable: eval fails and clearValue again fails. Error returned - // by eval holds the name of the cleared variable. - EXPECT_FALSE(FooVar.clearValue()); - Value = FooVar.eval(); + // Undefined variable set from numeric expression: isMatchTimeKnown returns + // true, eval returns value of expression, and setValue succeeds. + auto One = std::make_shared(1); + auto Binop = std::make_shared(doAdd, FooVar, One); + FileCheckNumericVariable FoobarVar = + FileCheckNumericVariable(2, "FOOBAR", Binop); + EXPECT_TRUE(FoobarVar.isMatchTimeKnown()); + Value = FoobarVar.eval(); + EXPECT_TRUE(static_cast(Value)); + EXPECT_EQ(43U, *Value); + EXPECT_FALSE(FoobarVar.setValue(43)); + Value = FoobarVar.eval(); + EXPECT_TRUE(static_cast(Value)); + EXPECT_EQ(43U, *Value); + + // Clearing variable: eval fails and clearValue again fails. + // appendUndefVarNames returns the variable again. + EXPECT_FALSE(FooVar->clearValue()); + EXPECT_FALSE(FoobarVar.clearValue()); + Value = FooVar->eval(); EXPECT_FALSE(Value); - expectUndefError("FOO", Value.takeError()); - EXPECT_TRUE(FooVar.clearValue()); + EXPECT_FALSE(expectUndefError("FOO", Value.takeError())); + Value = FoobarVar.eval(); + EXPECT_FALSE(Value); + EXPECT_FALSE(expectUndefError("FOOBAR", Value.takeError())); + EXPECT_TRUE(FooVar->clearValue()); } -uint64_t doAdd(uint64_t OpL, uint64_t OpR) { return OpL + OpR; } - TEST_F(FileCheckTest, Binop) { - auto FooVar = std::make_shared("FOO", 42); - auto BarVar = std::make_shared("BAR", 18); + auto FooVar = std::make_shared(1, "FOO", nullptr); + FooVar->setValue(42); + auto BarVar = std::make_shared(2, "BAR", nullptr); + BarVar->setValue(18); FileCheckASTBinop Binop = FileCheckASTBinop(doAdd, FooVar, BarVar); // Defined variable: eval returns right value. @@ -79,14 +109,14 @@ FooVar->clearValue(); Value = Binop.eval(); EXPECT_FALSE(Value); - expectUndefError("FOO", Value.takeError()); + EXPECT_FALSE(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()); + EXPECT_FALSE(expectUndefErrors({"FOO", "BAR"}, Value.takeError())); } TEST_F(FileCheckTest, ValidVarNameStart) { @@ -214,19 +244,13 @@ P = FileCheckPattern(Check::CheckPlain, &Context, LineNumber++); } - bool parseNumVarDefExpect(StringRef Expr) { - StringRef ExprBufferRef = bufferize(SM, Expr); - StringRef Name; - return errorToBool(FileCheckPattern::parseNumericVariableDefinition( - ExprBufferRef, Name, &Context, SM)); - } - bool parseSubstExpect(StringRef Expr) { StringRef ExprBufferRef = bufferize(SM, Expr); Optional DefinedNumericVariable; - return errorToBool(P.parseNumericSubstitutionBlock( - ExprBufferRef, DefinedNumericVariable, false, SM) - .takeError()); + return errorToBool( + P.parseNumericSubstitutionBlock(ExprBufferRef, DefinedNumericVariable, + false, LineNumber - 1, &Context, SM) + .takeError()); } bool parsePatternExpect(StringRef Pattern) { @@ -241,19 +265,6 @@ } }; -TEST_F(FileCheckTest, ParseNumericVariableDefinition) { - PatternTester Tester; - - // Invalid definition of pseudo. - EXPECT_TRUE(Tester.parseNumVarDefExpect("@LINE")); - - // Conflict with pattern variable. - EXPECT_TRUE(Tester.parseNumVarDefExpect("BAR")); - - // Defined variable. - EXPECT_FALSE(Tester.parseNumVarDefExpect("FOO")); -} - TEST_F(FileCheckTest, ParseExpr) { PatternTester Tester; @@ -264,17 +275,18 @@ EXPECT_TRUE(Tester.parseSubstExpect("@FOO:")); EXPECT_TRUE(Tester.parseSubstExpect("@LINE:")); + // Conflict with pattern variable. + EXPECT_TRUE(Tester.parseSubstExpect("BAR:")); + // Garbage after name of variable being defined. EXPECT_TRUE(Tester.parseSubstExpect("VAR GARBAGE:")); - // Variable defined to numeric expression. - EXPECT_TRUE(Tester.parseSubstExpect("VAR1: FOO")); - // Acceptable variable definition. EXPECT_FALSE(Tester.parseSubstExpect("VAR1:")); EXPECT_FALSE(Tester.parseSubstExpect(" VAR2:")); EXPECT_FALSE(Tester.parseSubstExpect("VAR3 :")); EXPECT_FALSE(Tester.parseSubstExpect("VAR3: ")); + EXPECT_FALSE(Tester.parsePatternExpect("[[#FOOBAR: FOO+1]]")); // Numeric expression. @@ -306,6 +318,7 @@ // Valid expression. EXPECT_FALSE(Tester.parseSubstExpect("@LINE+5")); EXPECT_FALSE(Tester.parseSubstExpect("FOO+4")); + EXPECT_FALSE(Tester.parseSubstExpect("FOOBAR")); Tester.initNextPattern(); EXPECT_FALSE(Tester.parsePatternExpect("[[#FOO+FOO]]")); EXPECT_FALSE(Tester.parsePatternExpect("[[#FOO+3-FOO]]")); @@ -337,7 +350,6 @@ EXPECT_TRUE(Tester.parsePatternExpect("[[#42INVALID]]")); EXPECT_TRUE(Tester.parsePatternExpect("[[#@FOO]]")); EXPECT_TRUE(Tester.parsePatternExpect("[[#@LINE/2]]")); - EXPECT_TRUE(Tester.parsePatternExpect("[[#YUP:@LINE]]")); // Valid numeric expressions and numeric variable definition. EXPECT_FALSE(Tester.parsePatternExpect("[[#FOO]]")); @@ -374,12 +386,15 @@ FileCheckStringSubstitution(&Context, "VAR404", 42); Expected SubstValue = StringSubstitution.getResult(); EXPECT_FALSE(static_cast(SubstValue)); - expectUndefError("VAR404", SubstValue.takeError()); + EXPECT_FALSE(expectUndefError("VAR404", SubstValue.takeError())); // Substitutions of defined pseudo and non-pseudo numeric variables return // the right value. - auto LineVar = std::make_shared("@LINE", 42); - auto NVar = std::make_shared("N", 10); + auto LineVar = + std::make_shared(1, "@LINE", nullptr); + auto NVar = std::make_shared(1, "N", nullptr); + LineVar->setValue(42); + NVar->setValue(10); FileCheckNumericSubstitution SubstitutionLine = FileCheckNumericSubstitution(&Context, "@LINE", LineVar, 12); FileCheckNumericSubstitution SubstitutionN = @@ -396,11 +411,11 @@ LineVar->clearValue(); SubstValue = SubstitutionLine.getResult(); EXPECT_FALSE(static_cast(SubstValue)); - expectUndefError("@LINE", SubstValue.takeError()); + EXPECT_FALSE(expectUndefError("@LINE", SubstValue.takeError())); NVar->clearValue(); SubstValue = SubstitutionN.getResult(); EXPECT_FALSE(static_cast(SubstValue)); - expectUndefError("N", SubstValue.takeError()); + EXPECT_FALSE(expectUndefError("N", SubstValue.takeError())); // Substitution of a defined string variable returns the right value. FileCheckPattern P = FileCheckPattern(Check::CheckPlain, &Context, 1); @@ -472,7 +487,8 @@ Optional DefinedNumericVariable; Expected> ExpressionAST = P.parseNumericSubstitutionBlock(LocalNumVarRef, DefinedNumericVariable, - false /*Legacy*/, SM); + false /*Legacy*/, 1 /*LineNumber*/, &Cxt, + SM); EXPECT_TRUE(static_cast(LocalVar)); EXPECT_EQ(*LocalVar, "FOO"); Expected EmptyVar = Cxt.getPatternVarValue(EmptyVarStr); @@ -496,7 +512,8 @@ EXPECT_TRUE(errorToBool((*ExpressionAST)->eval().takeError())); P = FileCheckPattern(Check::CheckPlain, &Cxt, 2); ExpressionAST = P.parseNumericSubstitutionBlock( - LocalNumVarRef, DefinedNumericVariable, false /*Legacy*/, SM); + LocalNumVarRef, DefinedNumericVariable, false /*Legacy*/, + 2 /*LineNumber*/, &Cxt, SM); EXPECT_TRUE(errorToBool(ExpressionAST.takeError())); EmptyVar = Cxt.getPatternVarValue(EmptyVarStr); EXPECT_TRUE(errorToBool(EmptyVar.takeError())); @@ -512,7 +529,8 @@ EXPECT_EQ(*GlobalVar, "BAR"); P = FileCheckPattern(Check::CheckPlain, &Cxt, 3); ExpressionAST = P.parseNumericSubstitutionBlock( - GlobalNumVarRef, DefinedNumericVariable, false /*Legacy*/, SM); + GlobalNumVarRef, DefinedNumericVariable, false /*Legacy*/, + 3 /*LineNumber*/, &Cxt, SM); EXPECT_TRUE(static_cast(ExpressionAST)); ExpressionVal = (*ExpressionAST)->eval(); EXPECT_TRUE(static_cast(ExpressionVal)); @@ -523,7 +541,8 @@ EXPECT_FALSE(errorToBool(Cxt.getPatternVarValue(GlobalVarStr).takeError())); P = FileCheckPattern(Check::CheckPlain, &Cxt, 4); ExpressionAST = P.parseNumericSubstitutionBlock( - GlobalNumVarRef, DefinedNumericVariable, false /*Legacy*/, SM); + GlobalNumVarRef, DefinedNumericVariable, false /*Legacy*/, + 4 /*LineNumber*/, &Cxt, SM); EXPECT_TRUE(static_cast(ExpressionAST)); ExpressionVal = (*ExpressionAST)->eval(); EXPECT_TRUE(static_cast(ExpressionVal));