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 @@ -675,7 +675,8 @@ would match ``mov r5, 0xF0F0`` and set ``REG`` to the value ``5`` and ``IMM`` to the value ``0xF0F0``. -The syntax of a numeric substitution is ``[[#%,]]`` where: +The syntax of a numeric substitution is +``[[#%: ]]`` where: * ``%`` is the same matching format specifier as for defining numeric variables but acting as a printf-style format to indicate how a numeric @@ -685,6 +686,12 @@ is used. In case of conflict between matching formats of several numeric variables the format specifier is mandatory. +* ```` is the matching constraint describing how the value to match + must relate to the value of the numeric expression. The only currently + accepted constraint is ``==`` for an exact match and is the default if + ```` is not provided. No matching constraint must be specified + when the ```` is empty. + * ```` is an expression. An expression is in turn recursively defined as: @@ -737,11 +744,12 @@ to check that a value is synthesized rather than moved around. A numeric variable can also be defined to the result of a numeric expression, -in which case the numeric expression 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. One can use this syntax to make a -testcase more self-describing by using variables instead of values: +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. One can use this syntax to make a testcase more +self-describing by using variables instead of values: .. code-block:: gas 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 @@ -423,8 +423,9 @@ } Expected> Pattern::parseNumericOperand( - StringRef &Expr, AllowedOperand AO, Optional LineNumber, - FileCheckPatternContext *Context, const SourceMgr &SM) { + StringRef &Expr, AllowedOperand AO, bool MaybeInvalidConstraint, + Optional LineNumber, FileCheckPatternContext *Context, + const SourceMgr &SM) { if (Expr.startswith("(")) { if (AO != AllowedOperand::Any) return ErrorDiagnostic::get( @@ -460,8 +461,11 @@ return std::make_unique(SaveExpr.drop_back(Expr.size()), SignedLiteralValue); - return ErrorDiagnostic::get(SM, Expr, - "invalid operand format '" + Expr + "'"); + return ErrorDiagnostic::get( + SM, Expr, + Twine("invalid ") + + (MaybeInvalidConstraint ? "matching constraint or " : "") + + "operand format"); } Expected> @@ -477,8 +481,9 @@ return ErrorDiagnostic::get(SM, Expr, "missing operand in expression"); // Note: parseNumericOperand handles nested opening parentheses. - Expected> SubExprResult = - parseNumericOperand(Expr, AllowedOperand::Any, LineNumber, Context, SM); + Expected> SubExprResult = parseNumericOperand( + Expr, AllowedOperand::Any, /*MaybeInvalidConstraint=*/false, LineNumber, + Context, SM); Expr = Expr.ltrim(SpaceChars); while (SubExprResult && !Expr.empty() && !Expr.startswith(")")) { StringRef OrigExpr = Expr; @@ -531,7 +536,8 @@ AllowedOperand AO = IsLegacyLineExpr ? AllowedOperand::LegacyLiteral : AllowedOperand::Any; Expected> RightOpResult = - parseNumericOperand(RemainingExpr, AO, LineNumber, Context, SM); + parseNumericOperand(RemainingExpr, AO, /*MaybeInvalidConstraint=*/false, + LineNumber, Context, SM); if (!RightOpResult) return RightOpResult; @@ -591,17 +597,28 @@ Expr = Expr.substr(DefEnd + 1); } + // Parse matching constraint. + Expr = Expr.ltrim(SpaceChars); + bool MaybeInvalidConstraint = true; + if (Expr.consume_front("==")) + MaybeInvalidConstraint = false; + // Parse the expression itself. Expr = Expr.ltrim(SpaceChars); - if (!Expr.empty()) { + if (Expr.empty()) { + if (!MaybeInvalidConstraint) + return ErrorDiagnostic::get( + SM, Expr, + "Empty numeric expression should not have a matching constraint"); + } else { Expr = Expr.rtrim(SpaceChars); StringRef OuterBinOpExpr = Expr; // 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, LineNumber, Context, SM); + Expected> ParseResult = parseNumericOperand( + Expr, AO, MaybeInvalidConstraint, LineNumber, Context, SM); while (ParseResult && !Expr.empty()) { ParseResult = parseBinop(OuterBinOpExpr, Expr, std::move(*ParseResult), IsLegacyLineExpr, LineNumber, Context, SM); diff --git a/llvm/lib/Support/FileCheckImpl.h b/llvm/lib/Support/FileCheckImpl.h --- a/llvm/lib/Support/FileCheckImpl.h +++ b/llvm/lib/Support/FileCheckImpl.h @@ -713,13 +713,14 @@ /// Parses \p Expr for use of a numeric operand at line \p LineNumber, or /// before input is parsed if \p LineNumber is None. 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. If \p Expr starts with a "(" this function will attempt to - /// parse a parenthesized expression. + //\ \p MaybeInvalidConstraint indicate whether the text being parsed could be + /// an invalid matching constraint. 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. If \p Expr starts with a + /// "(" this function will attempt to parse a parenthesized expression. static Expected> - parseNumericOperand(StringRef &Expr, AllowedOperand AO, + parseNumericOperand(StringRef &Expr, AllowedOperand AO, bool ConstraintParsed, Optional LineNumber, FileCheckPatternContext *Context, const SourceMgr &SM); /// Parses and updates \p RemainingExpr for a binary operation at line 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,31 +50,33 @@ 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 operand format 'x' -54 -55 BAD11: [[@LINE-1x]] -56 ERR11: line-count.txt:[[#@LINE-1]]:20: error: unexpected characters at end of expression 'x' -57 +53 ERR10: line-count.txt:[[#@LINE-1]]:19: error: invalid operand format +54 ERR10-NEXT: 52 {{B}}AD10: {{\[\[@LINE-x\]\]}} +55 ERR10-NEXT: {{^}} ^{{$}} +56 +57 BAD11: [[@LINE-1x]] +58 ERR11: line-count.txt:[[#@LINE-1]]:20: error: unexpected characters at end of expression 'x' +59 ; RUN: %ProtectFileCheckOutput \ ; RUN: not FileCheck -check-prefix BAD12 -input-file %s %s 2>&1 \ ; RUN: | FileCheck -check-prefix ERR12 %s -61 -62 BAD12: [[#@LINE-1]] NOT HERE -63 ERR12: note: with "@LINE-1" equal to "61" -64 +63 +64 BAD12: [[#@LINE-1]] NOT HERE +65 ERR12: note: with "@LINE-1" equal to "63" +66 ; RUN: %ProtectFileCheckOutput \ ; RUN: not FileCheck --check-prefix BAD13 --input-file %s %s 2>&1 \ ; RUN: | FileCheck --check-prefix ERR13 %s -68 -69 BAD13: [[@LINE-0xA]] -70 ERR13: line-count.txt:[[#@LINE-1]]:20: error: unexpected characters at end of expression 'xA' -71 -72 CHECK: [[#@LINE]] CHECK -73 CHECK: [[# @LINE]] CHECK -74 CHECK: [[# @LINE ]] CHECK -75 -76 CHECK: [[#@LINE-1]] -77 CHECK: [[# @LINE-1]] CHECK -78 CHECK: [[# @LINE -1]] CHECK -79 CHECK: [[# @LINE - 1]] CHECK -80 CHECK: [[# @LINE - 1 ]] CHECK +70 +71 BAD13: [[@LINE-0xA]] +72 ERR13: line-count.txt:[[#@LINE-1]]:20: error: unexpected characters at end of expression 'xA' +73 +74 CHECK: [[#@LINE]] CHECK +75 CHECK: [[# @LINE]] CHECK +76 CHECK: [[# @LINE ]] CHECK +77 +78 CHECK: [[#@LINE-1]] +79 CHECK: [[# @LINE-1]] CHECK +80 CHECK: [[# @LINE -1]] CHECK +81 CHECK: [[# @LINE - 1]] CHECK +82 CHECK: [[# @LINE - 1 ]] CHECK 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 @@ -121,6 +121,17 @@ CHECK-LABEL: USE IMPL FMT IMPL MATCH UNSIGNED IMM CHECK-NEXT: [[#UNSI+0x8000000000000000]] +; Numeric expressions in default matching format and explicit matching rule using +; variables defined on other lines +USE DEF FMT EXPL MATCH +11 +11 +12 +; CHECK-LABEL: USE DEF FMT EXPL MATCH +; CHECK-NEXT: [[#==UNSI]] +; CHECK-NEXT: [[# == UNSI]] +; CHECK-NEXT: [[#UNSI2: == UNSI + 1]] + ; Numeric expressions with matching format overriding the implicit format of ; variables defined on other lines. USE CONV FMT IMPL MATCH // CHECK-LABEL: USE CONV FMT IMPL MATCH @@ -367,3 +378,27 @@ UNDERFLOW-MSG: numeric-expression.txt:[[#@LINE-1]]:29: error: unable to substitute variable or numeric expression UNDERFLOW-MSG-NEXT: {{U}}NDERFLOW-NEXT: TINYVAR: {{\[\[#%d,TINYVAR:-0x8000000000000000-0x8000000000000000\]\]}} UNDERFLOW-MSG-NEXT: {{^}} ^{{$}} + +; Empty numeric expression with matching constraint. +RUN: not FileCheck --check-prefix EMPTY-NUMEXPR-CONSTRAINT --input-file %s %s 2>&1 \ +RUN: | FileCheck --check-prefix EMPTY-NUMEXPR-CONSTRAINT-MSG --strict-whitespace %s + +EMPTY NUMEXPR USE WITH CONSTRAINT +18 +EMPTY-NUMEXPR-CONSTRAINT-LABEL: EMPTY NUMEXPR USE WITH CONSTRAINT +EMPTY-NUMEXPR-CONSTRAINT-NEXT: [[# ==]] +EMPTY-NUMEXPR-CONSTRAINT-MSG: numeric-expression.txt:[[#@LINE-1]]:38: error: Empty numeric expression should not have a matching constraint +EMPTY-NUMEXPR-CONSTRAINT-MSG-NEXT: {{E}}MPTY-NUMEXPR-CONSTRAINT-NEXT: {{\[\[# ==\]\]}} +EMPTY-NUMEXPR-CONSTRAINT-MSG-NEXT: {{^}} ^{{$}} + +; Definition from empty numeric expression with matching constraint. +RUN: not FileCheck --check-prefix EMPTY-NUMDEF-CONSTRAINT --input-file %s %s 2>&1 \ +RUN: | FileCheck --check-prefix EMPTY-NUMDEF-CONSTRAINT-MSG %s + +EMPTY NUMEXPR DEF WITH CONSTRAINT +18 +EMPTY-NUMDEF-CONSTRAINT-LABEL: EMPTY NUMEXPR CONSTRAINT +EMPTY-NUMDEF-CONSTRAINT-NEXT: [[#VARDEF: ==]] +EMPTY-NUMDEF-CONSTRAINT-MSG: numeric-expression.txt:[[#@LINE-1]]:44: error: Empty numeric expression should not have a matching constraint +EMPTY-NUMDEF-CONSTRAINT-MSG-NEXT: {{E}}MPTY-NUMDEF-CONSTRAINT-NEXT: {{\[\[#VARDEF: ==\]\]}} +EMPTY-NUMDEF-CONSTRAINT-MSG-NEXT: {{^}} ^{{$}} 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 @@ -971,7 +971,7 @@ Tester.initNextPattern(); // Invalid variable name. - expectDiagnosticError("invalid operand format '%VAR'", + expectDiagnosticError("invalid matching constraint or operand format", Tester.parseSubst("%VAR").takeError()); expectDiagnosticError("invalid pseudo numeric variable '@FOO'", @@ -1006,6 +1006,11 @@ // Valid empty expression. EXPECT_THAT_EXPECTED(Tester.parseSubst(""), Succeeded()); + // Invalid equality matching constraint with empty expression. + expectDiagnosticError( + "Empty numeric expression should not have a matching constraint", + Tester.parseSubst("==").takeError()); + // Valid single operand expression. EXPECT_THAT_EXPECTED(Tester.parseSubst("FOO"), Succeeded()); EXPECT_THAT_EXPECTED(Tester.parseSubst("18"), Succeeded()); @@ -1016,6 +1021,13 @@ EXPECT_THAT_EXPECTED(Tester.parseSubst(std::to_string(MinInt64)), Succeeded()); + // Valid optional matching constraint. + EXPECT_THAT_EXPECTED(Tester.parseSubst("==FOO"), Succeeded()); + + // Invalid matching constraint. + expectDiagnosticError("invalid matching constraint or operand format", + Tester.parseSubst(">=FOO").takeError()); + // Invalid format. expectDiagnosticError("invalid matching format specification in expression", Tester.parseSubst("X,FOO:").takeError()); @@ -1037,12 +1049,12 @@ // Errors in RHS operand are bubbled up by parseBinop() to // parseNumericSubstitutionBlock(). - expectDiagnosticError("invalid operand format '%VAR'", + expectDiagnosticError("invalid operand format", Tester.parseSubst("@LINE+%VAR").takeError()); // Invalid legacy @LINE expression with non literal rhs. expectDiagnosticError( - "invalid operand format '@LINE'", + "invalid operand format", Tester.parseSubst("@LINE+@LINE", /*IsLegacyNumExpr=*/true).takeError()); // Invalid legacy @LINE expression made of a single literal. @@ -1115,7 +1127,7 @@ // Test more closing than opening parentheses. The diagnostic messages are // not ideal, but for now simply check that we reject invalid input. - expectDiagnosticError("invalid operand format ')'", + expectDiagnosticError("invalid matching constraint or operand format", Tester.parseSubst(")").takeError()); expectDiagnosticError("unsupported operation ')'", Tester.parseSubst("1)").takeError());