[FileCheck] Better diagnostic for format conflict

Summary:
Improve error message in case of conflict between several implicit
format to mention the operand that conflict.

Reviewers: jhenderson, jdenny, probinson, grimar, arichardson, rnk

Reviewed By: jdenny

Subscribers: hiraditya, llvm-commits

Tags: #llvm

Differential Revision: https://reviews.llvm.org/D77741
This commit is contained in:
Thomas Preud'homme 2020-04-04 01:02:45 +01:00
parent 2a0a26bd98
commit 9743123af8
4 changed files with 275 additions and 145 deletions

View File

@ -25,6 +25,20 @@
using namespace llvm; using namespace llvm;
StringRef ExpressionFormat::toString() const {
switch (Value) {
case Kind::NoFormat:
return StringRef("<none>");
case Kind::Unsigned:
return StringRef("%u");
case Kind::HexUpper:
return StringRef("%X");
case Kind::HexLower:
return StringRef("%x");
}
llvm_unreachable("unknown expression format");
}
Expected<StringRef> ExpressionFormat::getWildcardRegex() const { Expected<StringRef> ExpressionFormat::getWildcardRegex() const {
switch (Value) { switch (Value) {
case Kind::Unsigned: case Kind::Unsigned:
@ -71,7 +85,7 @@ Expected<uint64_t> NumericVariableUse::eval() const {
if (Value) if (Value)
return *Value; return *Value;
return make_error<UndefVarError>(Name); return make_error<UndefVarError>(getExpressionStr());
} }
Expected<uint64_t> BinaryOperation::eval() const { Expected<uint64_t> BinaryOperation::eval() const {
@ -92,18 +106,31 @@ Expected<uint64_t> BinaryOperation::eval() const {
return EvalBinop(*LeftOp, *RightOp); return EvalBinop(*LeftOp, *RightOp);
} }
ExpressionFormat BinaryOperation::getImplicitFormat() const { Expected<ExpressionFormat>
ExpressionFormat LeftFormat = LeftOperand->getImplicitFormat(); BinaryOperation::getImplicitFormat(const SourceMgr &SM) const {
ExpressionFormat RightFormat = RightOperand->getImplicitFormat(); Expected<ExpressionFormat> LeftFormat = LeftOperand->getImplicitFormat(SM);
Expected<ExpressionFormat> RightFormat = RightOperand->getImplicitFormat(SM);
if (!LeftFormat || !RightFormat) {
Error Err = Error::success();
if (!LeftFormat)
Err = joinErrors(std::move(Err), LeftFormat.takeError());
if (!RightFormat)
Err = joinErrors(std::move(Err), RightFormat.takeError());
return std::move(Err);
}
ExpressionFormat Format = if (*LeftFormat != ExpressionFormat::Kind::NoFormat &&
LeftFormat != ExpressionFormat::Kind::NoFormat ? LeftFormat : RightFormat; *RightFormat != ExpressionFormat::Kind::NoFormat &&
if (LeftFormat != ExpressionFormat::Kind::NoFormat && *LeftFormat != *RightFormat)
RightFormat != ExpressionFormat::Kind::NoFormat && return ErrorDiagnostic::get(
LeftFormat != RightFormat) SM, getExpressionStr(),
Format = ExpressionFormat(ExpressionFormat::Kind::Conflict); "implicit format conflict between '" + LeftOperand->getExpressionStr() +
"' (" + LeftFormat->toString() + ") and '" +
RightOperand->getExpressionStr() + "' (" + RightFormat->toString() +
"), need an explicit format specifier");
return Format; return *LeftFormat != ExpressionFormat::Kind::NoFormat ? *LeftFormat
: *RightFormat;
} }
Expected<std::string> NumericSubstitution::getResult() const { Expected<std::string> NumericSubstitution::getResult() const {
@ -262,9 +289,12 @@ Expected<std::unique_ptr<ExpressionAST>> Pattern::parseNumericOperand(
// Otherwise, parse it as a literal. // Otherwise, parse it as a literal.
uint64_t LiteralValue; uint64_t LiteralValue;
StringRef OperandExpr = Expr;
if (!Expr.consumeInteger((AO == AllowedOperand::LegacyLiteral) ? 10 : 0, if (!Expr.consumeInteger((AO == AllowedOperand::LegacyLiteral) ? 10 : 0,
LiteralValue)) LiteralValue)) {
return std::make_unique<ExpressionLiteral>(LiteralValue); return std::make_unique<ExpressionLiteral>(
OperandExpr.drop_back(Expr.size()), LiteralValue);
}
return ErrorDiagnostic::get(SM, Expr, return ErrorDiagnostic::get(SM, Expr,
"invalid operand format '" + Expr + "'"); "invalid operand format '" + Expr + "'");
@ -279,17 +309,18 @@ static uint64_t sub(uint64_t LeftOp, uint64_t RightOp) {
} }
Expected<std::unique_ptr<ExpressionAST>> Expected<std::unique_ptr<ExpressionAST>>
Pattern::parseBinop(StringRef &Expr, std::unique_ptr<ExpressionAST> LeftOp, Pattern::parseBinop(StringRef Expr, StringRef &RemainingExpr,
std::unique_ptr<ExpressionAST> LeftOp,
bool IsLegacyLineExpr, Optional<size_t> LineNumber, bool IsLegacyLineExpr, Optional<size_t> LineNumber,
FileCheckPatternContext *Context, const SourceMgr &SM) { FileCheckPatternContext *Context, const SourceMgr &SM) {
Expr = Expr.ltrim(SpaceChars); RemainingExpr = RemainingExpr.ltrim(SpaceChars);
if (Expr.empty()) if (RemainingExpr.empty())
return std::move(LeftOp); return std::move(LeftOp);
// Check if this is a supported operation and select a function to perform // Check if this is a supported operation and select a function to perform
// it. // it.
SMLoc OpLoc = SMLoc::getFromPointer(Expr.data()); SMLoc OpLoc = SMLoc::getFromPointer(RemainingExpr.data());
char Operator = popFront(Expr); char Operator = popFront(RemainingExpr);
binop_eval_t EvalBinop; binop_eval_t EvalBinop;
switch (Operator) { switch (Operator) {
case '+': case '+':
@ -304,19 +335,20 @@ Pattern::parseBinop(StringRef &Expr, std::unique_ptr<ExpressionAST> LeftOp,
} }
// Parse right operand. // Parse right operand.
Expr = Expr.ltrim(SpaceChars); RemainingExpr = RemainingExpr.ltrim(SpaceChars);
if (Expr.empty()) if (RemainingExpr.empty())
return ErrorDiagnostic::get(SM, Expr, "missing operand in expression"); return ErrorDiagnostic::get(SM, RemainingExpr,
"missing operand in expression");
// The second operand in a legacy @LINE expression is always a literal. // The second operand in a legacy @LINE expression is always a literal.
AllowedOperand AO = AllowedOperand AO =
IsLegacyLineExpr ? AllowedOperand::LegacyLiteral : AllowedOperand::Any; IsLegacyLineExpr ? AllowedOperand::LegacyLiteral : AllowedOperand::Any;
Expected<std::unique_ptr<ExpressionAST>> RightOpResult = Expected<std::unique_ptr<ExpressionAST>> RightOpResult =
parseNumericOperand(Expr, AO, LineNumber, Context, SM); parseNumericOperand(RemainingExpr, AO, LineNumber, Context, SM);
if (!RightOpResult) if (!RightOpResult)
return RightOpResult; return RightOpResult;
Expr = Expr.ltrim(SpaceChars); Expr = Expr.drop_back(RemainingExpr.size());
return std::make_unique<BinaryOperation>(EvalBinop, std::move(LeftOp), return std::make_unique<BinaryOperation>(Expr, EvalBinop, std::move(LeftOp),
std::move(*RightOpResult)); std::move(*RightOpResult));
} }
@ -370,8 +402,9 @@ Expected<std::unique_ptr<Expression>> Pattern::parseNumericSubstitutionBlock(
// Parse the expression itself. // Parse the expression itself.
Expr = Expr.ltrim(SpaceChars); Expr = Expr.ltrim(SpaceChars);
StringRef UseExpr = Expr;
if (!Expr.empty()) { if (!Expr.empty()) {
Expr = Expr.rtrim(SpaceChars);
StringRef OuterBinOpExpr = Expr;
// The first operand in a legacy @LINE expression is always the @LINE // The first operand in a legacy @LINE expression is always the @LINE
// pseudo variable. // pseudo variable.
AllowedOperand AO = AllowedOperand AO =
@ -379,8 +412,8 @@ Expected<std::unique_ptr<Expression>> Pattern::parseNumericSubstitutionBlock(
Expected<std::unique_ptr<ExpressionAST>> ParseResult = Expected<std::unique_ptr<ExpressionAST>> ParseResult =
parseNumericOperand(Expr, AO, LineNumber, Context, SM); parseNumericOperand(Expr, AO, LineNumber, Context, SM);
while (ParseResult && !Expr.empty()) { while (ParseResult && !Expr.empty()) {
ParseResult = parseBinop(Expr, std::move(*ParseResult), IsLegacyLineExpr, ParseResult = parseBinop(OuterBinOpExpr, Expr, std::move(*ParseResult),
LineNumber, Context, SM); IsLegacyLineExpr, LineNumber, Context, SM);
// Legacy @LINE expressions only allow 2 operands. // Legacy @LINE expressions only allow 2 operands.
if (ParseResult && IsLegacyLineExpr && !Expr.empty()) if (ParseResult && IsLegacyLineExpr && !Expr.empty())
return ErrorDiagnostic::get( return ErrorDiagnostic::get(
@ -396,19 +429,17 @@ Expected<std::unique_ptr<Expression>> Pattern::parseNumericSubstitutionBlock(
// otherwise (ii) its implicit format, if any, otherwise (iii) the default // otherwise (ii) its implicit format, if any, otherwise (iii) the default
// format (unsigned). Error out in case of conflicting implicit format // format (unsigned). Error out in case of conflicting implicit format
// without explicit format. // without explicit format.
ExpressionFormat Format, ExpressionFormat Format;
ImplicitFormat = ExpressionASTPointer if (ExplicitFormat)
? ExpressionASTPointer->getImplicitFormat()
: ExpressionFormat(ExpressionFormat::Kind::NoFormat);
if (bool(ExplicitFormat))
Format = ExplicitFormat; Format = ExplicitFormat;
else if (ImplicitFormat == ExpressionFormat::Kind::Conflict) else if (ExpressionASTPointer) {
return ErrorDiagnostic::get( Expected<ExpressionFormat> ImplicitFormat =
SM, UseExpr, ExpressionASTPointer->getImplicitFormat(SM);
"variables with conflicting format specifier: need an explicit one"); if (!ImplicitFormat)
else if (bool(ImplicitFormat)) return ImplicitFormat.takeError();
Format = ImplicitFormat; Format = *ImplicitFormat;
else }
if (!Format)
Format = ExpressionFormat(ExpressionFormat::Kind::Unsigned); Format = ExpressionFormat(ExpressionFormat::Kind::Unsigned);
std::unique_ptr<Expression> ExpressionPointer = std::unique_ptr<Expression> ExpressionPointer =

View File

@ -39,9 +39,6 @@ struct ExpressionFormat {
/// Denote absence of format. Used for implicit format of literals and /// Denote absence of format. Used for implicit format of literals and
/// empty expressions. /// empty expressions.
NoFormat, NoFormat,
/// Used when there are several conflicting implicit formats in an
/// expression.
Conflict,
/// Value is an unsigned integer and should be printed as a decimal number. /// Value is an unsigned integer and should be printed as a decimal number.
Unsigned, Unsigned,
/// Value should be printed as an uppercase hex number. /// Value should be printed as an uppercase hex number.
@ -55,9 +52,7 @@ private:
public: public:
/// Evaluates a format to true if it can be used in a match. /// Evaluates a format to true if it can be used in a match.
explicit operator bool() const { explicit operator bool() const { return Value != Kind::NoFormat; }
return Value != Kind::NoFormat && Value != Kind::Conflict;
}
/// Define format equality: formats are equal if neither is NoFormat and /// Define format equality: formats are equal if neither is NoFormat and
/// their kinds are the same. /// their kinds are the same.
@ -73,16 +68,19 @@ public:
bool operator!=(Kind OtherValue) const { return !(*this == OtherValue); } bool operator!=(Kind OtherValue) const { return !(*this == OtherValue); }
/// \returns the format specifier corresponding to this format as a string.
StringRef toString() const;
ExpressionFormat() : Value(Kind::NoFormat){}; ExpressionFormat() : Value(Kind::NoFormat){};
explicit ExpressionFormat(Kind Value) : Value(Value){}; explicit ExpressionFormat(Kind Value) : Value(Value){};
/// \returns a wildcard regular expression StringRef that matches any value /// \returns a wildcard regular expression StringRef that matches any value
/// in the format represented by this instance, or an error if the format is /// in the format represented by this instance, or an error if the format is
/// NoFormat or Conflict. /// NoFormat.
Expected<StringRef> getWildcardRegex() const; Expected<StringRef> getWildcardRegex() const;
/// \returns the string representation of \p Value in the format represented /// \returns the string representation of \p Value in the format represented
/// by this instance, or an error if the format is NoFormat or Conflict. /// by this instance, or an error if the format is NoFormat.
Expected<std::string> getMatchingString(uint64_t Value) const; Expected<std::string> getMatchingString(uint64_t Value) const;
/// \returns the value corresponding to string representation \p StrVal /// \returns the value corresponding to string representation \p StrVal
@ -95,17 +93,26 @@ public:
/// Base class representing the AST of a given expression. /// Base class representing the AST of a given expression.
class ExpressionAST { class ExpressionAST {
private:
StringRef ExpressionStr;
public: public:
ExpressionAST(StringRef ExpressionStr) : ExpressionStr(ExpressionStr) {}
virtual ~ExpressionAST() = default; virtual ~ExpressionAST() = default;
StringRef getExpressionStr() const { return ExpressionStr; }
/// Evaluates and \returns the value of the expression represented by this /// Evaluates and \returns the value of the expression represented by this
/// AST or an error if evaluation fails. /// AST or an error if evaluation fails.
virtual Expected<uint64_t> eval() const = 0; virtual Expected<uint64_t> eval() const = 0;
/// \returns either the implicit format of this AST, FormatConflict if /// \returns either the implicit format of this AST, a diagnostic against
/// implicit formats of the AST's components conflict, or NoFormat if the AST /// \p SM if implicit formats of the AST's components conflict, or NoFormat
/// has no implicit format (e.g. AST is made up of a single literal). /// if the AST has no implicit format (e.g. AST is made up of a single
virtual ExpressionFormat getImplicitFormat() const { /// literal).
virtual Expected<ExpressionFormat>
getImplicitFormat(const SourceMgr &SM) const {
return ExpressionFormat(); return ExpressionFormat();
} }
}; };
@ -117,8 +124,10 @@ private:
uint64_t Value; uint64_t Value;
public: public:
/// Constructs a literal with the specified value. /// Constructs a literal with the specified value parsed from
ExpressionLiteral(uint64_t Val) : Value(Val) {} /// \p ExpressionStr.
ExpressionLiteral(StringRef ExpressionStr, uint64_t Val)
: ExpressionAST(ExpressionStr), Value(Val) {}
/// \returns the literal's value. /// \returns the literal's value.
Expected<uint64_t> eval() const override { return Value; } Expected<uint64_t> eval() const override { return Value; }
@ -222,21 +231,18 @@ public:
/// expression. /// expression.
class NumericVariableUse : public ExpressionAST { class NumericVariableUse : public ExpressionAST {
private: private:
/// Name of the numeric variable.
StringRef Name;
/// Pointer to the class instance for the variable this use is about. /// Pointer to the class instance for the variable this use is about.
NumericVariable *Variable; NumericVariable *Variable;
public: public:
NumericVariableUse(StringRef Name, NumericVariable *Variable) NumericVariableUse(StringRef Name, NumericVariable *Variable)
: Name(Name), Variable(Variable) {} : ExpressionAST(Name), Variable(Variable) {}
/// \returns the value of the variable referenced by this instance. /// \returns the value of the variable referenced by this instance.
Expected<uint64_t> eval() const override; Expected<uint64_t> eval() const override;
/// \returns implicit format of this numeric variable. /// \returns implicit format of this numeric variable.
ExpressionFormat getImplicitFormat() const override { Expected<ExpressionFormat>
getImplicitFormat(const SourceMgr &SM) const override {
return Variable->getImplicitFormat(); return Variable->getImplicitFormat();
} }
}; };
@ -257,9 +263,10 @@ private:
binop_eval_t EvalBinop; binop_eval_t EvalBinop;
public: public:
BinaryOperation(binop_eval_t EvalBinop, std::unique_ptr<ExpressionAST> LeftOp, BinaryOperation(StringRef ExpressionStr, binop_eval_t EvalBinop,
std::unique_ptr<ExpressionAST> LeftOp,
std::unique_ptr<ExpressionAST> RightOp) std::unique_ptr<ExpressionAST> RightOp)
: EvalBinop(EvalBinop) { : ExpressionAST(ExpressionStr), EvalBinop(EvalBinop) {
LeftOperand = std::move(LeftOp); LeftOperand = std::move(LeftOp);
RightOperand = std::move(RightOp); RightOperand = std::move(RightOp);
} }
@ -270,10 +277,12 @@ public:
/// variable is used in one of the operands. /// variable is used in one of the operands.
Expected<uint64_t> eval() const override; Expected<uint64_t> eval() const override;
/// \returns the implicit format of this AST, if any, a format conflict if /// \returns the implicit format of this AST, if any, a diagnostic against
/// the implicit formats of the AST's components conflict, or no format if /// \p SM if the implicit formats of the AST's components conflict, or no
/// the AST has no implicit format (e.g. AST is made of a single literal). /// format if the AST has no implicit format (e.g. AST is made of a single
ExpressionFormat getImplicitFormat() const override; /// literal).
Expected<ExpressionFormat>
getImplicitFormat(const SourceMgr &SM) const override;
}; };
class FileCheckPatternContext; class FileCheckPatternContext;
@ -663,17 +672,20 @@ private:
parseNumericOperand(StringRef &Expr, AllowedOperand AO, parseNumericOperand(StringRef &Expr, AllowedOperand AO,
Optional<size_t> LineNumber, Optional<size_t> LineNumber,
FileCheckPatternContext *Context, const SourceMgr &SM); FileCheckPatternContext *Context, const SourceMgr &SM);
/// Parses \p Expr for a binary operation at line \p LineNumber, or before /// Parses and updates \p RemainingExpr for a binary operation at line
/// input is parsed if \p LineNumber is None. The left operand of this binary /// \p LineNumber, or before input is parsed if \p LineNumber is None. The
/// operation is given in \p LeftOp and \p IsLegacyLineExpr indicates whether /// left operand of this binary operation is given in \p LeftOp and \p Expr
/// we are parsing a legacy @LINE expression. Parameter \p Context points to /// holds the string for the full expression, including the left operand.
/// the class instance holding the live string and numeric variables. /// Parameter \p IsLegacyLineExpr indicates whether we are parsing a legacy
/// \returns the class representing the binary operation in the AST of the /// @LINE expression. Parameter \p Context points to the class instance
/// expression, or an error holding a diagnostic against \p SM otherwise. /// 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<std::unique_ptr<ExpressionAST>> static Expected<std::unique_ptr<ExpressionAST>>
parseBinop(StringRef &Expr, std::unique_ptr<ExpressionAST> LeftOp, parseBinop(StringRef Expr, StringRef &RemainingExpr,
bool IsLegacyLineExpr, Optional<size_t> LineNumber, std::unique_ptr<ExpressionAST> LeftOp, bool IsLegacyLineExpr,
FileCheckPatternContext *Context, const SourceMgr &SM); Optional<size_t> LineNumber, FileCheckPatternContext *Context,
const SourceMgr &SM);
}; };
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//

View File

@ -185,16 +185,27 @@ CHECK-NEXT: [[# %u, VAR3]]
; Conflicting implicit format. ; Conflicting implicit format.
RUN: %ProtectFileCheckOutput \ RUN: %ProtectFileCheckOutput \
RUN: not FileCheck --check-prefixes CHECK,FMT-CONFLICT --input-file %s %s 2>&1 \ RUN: not FileCheck --check-prefixes CHECK,FMT-CONFLICT1 --input-file %s %s 2>&1 \
RUN: | FileCheck --strict-whitespace --check-prefix FMT-CONFLICT-MSG %s RUN: | FileCheck --strict-whitespace --check-prefix FMT-CONFLICT1-MSG %s
RUN: %ProtectFileCheckOutput \
RUN: not FileCheck --check-prefixes CHECK,FMT-CONFLICT2 --input-file %s %s 2>&1 \
RUN: | FileCheck --strict-whitespace --check-prefix FMT-CONFLICT2-MSG %s
VAR USE IMPL FMT CONFLICT VAR USE IMPL FMT CONFLICT
23 23
FMT-CONFLICT-LABEL: VAR USE IMPL FMT CONFLICT FMT-CONFLICT1-LABEL: VAR USE IMPL FMT CONFLICT
FMT-CONFLICT-NEXT: [[#VAR1 + VAR2]] FMT-CONFLICT1-NEXT: [[#VAR1 + VAR2]]
FMT-CONFLICT-MSG: numeric-expression.txt:[[#@LINE-1]]:23: error: variables with conflicting format specifier: need an explicit one FMT-CONFLICT1-MSG: numeric-expression.txt:[[#@LINE-1]]:24: error: implicit format conflict between 'VAR1' (%u) and 'VAR2' (%x), need an explicit format specifier
FMT-CONFLICT-MSG-NEXT: {{F}}MT-CONFLICT-NEXT: {{\[\[#VAR1 \+ VAR2\]\]}} FMT-CONFLICT1-MSG-NEXT: {{F}}MT-CONFLICT1-NEXT: {{\[\[#VAR1 \+ VAR2\]\]}}
FMT-CONFLICT-MSG-NEXT: {{^ \^$}} FMT-CONFLICT1-MSG-NEXT: {{^ \^$}}
VAR USE IMPL FMT CONFLICT COMPLEX
34
FMT-CONFLICT2-LABEL: VAR USE IMPL FMT CONFLICT
FMT-CONFLICT2-NEXT: [[#VAR1 + VAR1a + VAR2]]
FMT-CONFLICT2-MSG: numeric-expression.txt:[[#@LINE-1]]:24: error: implicit format conflict between 'VAR1 + VAR1a' (%u) and 'VAR2' (%x), need an explicit format specifier
FMT-CONFLICT2-MSG-NEXT: {{F}}MT-CONFLICT2-NEXT: {{\[\[#VAR1 \+ VAR1a \+ VAR2\]\]}}
FMT-CONFLICT2-MSG-NEXT: {{^ \^$}}
; Explicitly specified format can override conflicting implicit formats. ; Explicitly specified format can override conflicting implicit formats.
VAR USE IMPL OVERRIDE FMT CONFLICT VAR USE IMPL OVERRIDE FMT CONFLICT

View File

@ -28,18 +28,50 @@ static StringRef bufferize(SourceMgr &SM, StringRef Str) {
return StrBufferRef; return StrBufferRef;
} }
static std::string toString(const std::unordered_set<std::string> &Set) {
bool First = true;
std::string Str;
for (StringRef S : Set) {
Str += Twine(First ? "{" + S : ", " + S).str();
First = false;
}
Str += '}';
return Str;
}
template <typename ErrorT>
static void expectSameErrors(std::unordered_set<std::string> ExpectedMsgs,
Error Err) {
auto AnyErrorMsgMatch = [&ExpectedMsgs](std::string &&ErrorMsg) -> bool {
for (auto ExpectedMsgItr = ExpectedMsgs.begin(),
ExpectedMsgEnd = ExpectedMsgs.end();
ExpectedMsgItr != ExpectedMsgEnd; ++ExpectedMsgItr) {
if (ErrorMsg.find(*ExpectedMsgItr) != std::string::npos) {
ExpectedMsgs.erase(ExpectedMsgItr);
return true;
}
}
return false;
};
Error RemainingErrors = std::move(Err);
do {
RemainingErrors =
handleErrors(std::move(RemainingErrors), [&](const ErrorT &E) {
EXPECT_TRUE(AnyErrorMsgMatch(E.message()))
<< "Unexpected error message:" << std::endl
<< E.message();
});
} while (RemainingErrors && !ExpectedMsgs.empty());
EXPECT_THAT_ERROR(std::move(RemainingErrors), Succeeded());
EXPECT_TRUE(ExpectedMsgs.empty())
<< "Error message(s) not found:" << std::endl
<< toString(ExpectedMsgs);
}
template <typename ErrorT> template <typename ErrorT>
static void expectError(StringRef ExpectedMsg, Error Err) { static void expectError(StringRef ExpectedMsg, Error Err) {
bool ErrorHandled = false; expectSameErrors<ErrorT>({ExpectedMsg.str()}, std::move(Err));
EXPECT_THAT_ERROR(handleErrors(std::move(Err),
[&](const ErrorT &E) {
EXPECT_NE(
E.message().find(ExpectedMsg.str()),
std::string::npos);
ErrorHandled = true;
}),
Succeeded());
EXPECT_TRUE(ErrorHandled);
} }
static void expectDiagnosticError(StringRef ExpectedMsg, Error Err) { static void expectDiagnosticError(StringRef ExpectedMsg, Error Err) {
@ -179,15 +211,6 @@ TEST_F(FileCheckTest, NoFormatProperties) {
EXPECT_FALSE(bool(NoFormat)); EXPECT_FALSE(bool(NoFormat));
} }
TEST_F(FileCheckTest, ConflictFormatProperties) {
ExpressionFormat ConflictFormat(ExpressionFormat::Kind::Conflict);
expectError<StringError>("trying to match value with invalid format",
ConflictFormat.getWildcardRegex().takeError());
expectError<StringError>("trying to match value with invalid format",
ConflictFormat.getMatchingString(18).takeError());
EXPECT_FALSE(bool(ConflictFormat));
}
TEST_F(FileCheckTest, FormatEqualityOperators) { TEST_F(FileCheckTest, FormatEqualityOperators) {
ExpressionFormat UnsignedFormat(ExpressionFormat::Kind::Unsigned); ExpressionFormat UnsignedFormat(ExpressionFormat::Kind::Unsigned);
ExpressionFormat UnsignedFormat2(ExpressionFormat::Kind::Unsigned); ExpressionFormat UnsignedFormat2(ExpressionFormat::Kind::Unsigned);
@ -202,11 +225,6 @@ TEST_F(FileCheckTest, FormatEqualityOperators) {
ExpressionFormat NoFormat2(ExpressionFormat::Kind::NoFormat); ExpressionFormat NoFormat2(ExpressionFormat::Kind::NoFormat);
EXPECT_FALSE(NoFormat == NoFormat2); EXPECT_FALSE(NoFormat == NoFormat2);
EXPECT_TRUE(NoFormat != NoFormat2); EXPECT_TRUE(NoFormat != NoFormat2);
ExpressionFormat ConflictFormat(ExpressionFormat::Kind::Conflict);
ExpressionFormat ConflictFormat2(ExpressionFormat::Kind::Conflict);
EXPECT_TRUE(ConflictFormat == ConflictFormat2);
EXPECT_FALSE(ConflictFormat != ConflictFormat2);
} }
TEST_F(FileCheckTest, FormatKindEqualityOperators) { TEST_F(FileCheckTest, FormatKindEqualityOperators) {
@ -215,43 +233,36 @@ TEST_F(FileCheckTest, FormatKindEqualityOperators) {
EXPECT_FALSE(UnsignedFormat != ExpressionFormat::Kind::Unsigned); EXPECT_FALSE(UnsignedFormat != ExpressionFormat::Kind::Unsigned);
EXPECT_FALSE(UnsignedFormat == ExpressionFormat::Kind::HexLower); EXPECT_FALSE(UnsignedFormat == ExpressionFormat::Kind::HexLower);
EXPECT_TRUE(UnsignedFormat != ExpressionFormat::Kind::HexLower); EXPECT_TRUE(UnsignedFormat != ExpressionFormat::Kind::HexLower);
ExpressionFormat ConflictFormat(ExpressionFormat::Kind::Conflict);
EXPECT_TRUE(ConflictFormat == ExpressionFormat::Kind::Conflict);
EXPECT_FALSE(ConflictFormat != ExpressionFormat::Kind::Conflict);
ExpressionFormat NoFormat(ExpressionFormat::Kind::NoFormat); ExpressionFormat NoFormat(ExpressionFormat::Kind::NoFormat);
EXPECT_TRUE(NoFormat == ExpressionFormat::Kind::NoFormat); EXPECT_TRUE(NoFormat == ExpressionFormat::Kind::NoFormat);
EXPECT_FALSE(NoFormat != ExpressionFormat::Kind::NoFormat); EXPECT_FALSE(NoFormat != ExpressionFormat::Kind::NoFormat);
} }
TEST_F(FileCheckTest, Literal) { TEST_F(FileCheckTest, Literal) {
SourceMgr SM;
// Eval returns the literal's value. // Eval returns the literal's value.
ExpressionLiteral Ten(10); ExpressionLiteral Ten(bufferize(SM, "10"), 10);
Expected<uint64_t> Value = Ten.eval(); Expected<uint64_t> Value = Ten.eval();
ASSERT_THAT_EXPECTED(Value, Succeeded()); ASSERT_THAT_EXPECTED(Value, Succeeded());
EXPECT_EQ(10U, *Value); EXPECT_EQ(10U, *Value);
EXPECT_EQ(Ten.getImplicitFormat(), ExpressionFormat::Kind::NoFormat); Expected<ExpressionFormat> ImplicitFormat = Ten.getImplicitFormat(SM);
ASSERT_THAT_EXPECTED(ImplicitFormat, Succeeded());
EXPECT_EQ(*ImplicitFormat, ExpressionFormat::Kind::NoFormat);
// Max value can be correctly represented. // Max value can be correctly represented.
ExpressionLiteral Max(std::numeric_limits<uint64_t>::max()); uint64_t MaxUint64 = std::numeric_limits<uint64_t>::max();
ExpressionLiteral Max(bufferize(SM, std::to_string(MaxUint64)), MaxUint64);
Value = Max.eval(); Value = Max.eval();
ASSERT_THAT_EXPECTED(Value, Succeeded()); ASSERT_THAT_EXPECTED(Value, Succeeded());
EXPECT_EQ(std::numeric_limits<uint64_t>::max(), *Value); EXPECT_EQ(std::numeric_limits<uint64_t>::max(), *Value);
} }
static std::string toString(const std::unordered_set<std::string> &Set) {
bool First = true;
std::string Str;
for (StringRef S : Set) {
Str += Twine(First ? "{" + S : ", " + S).str();
First = false;
}
Str += '}';
return Str;
}
TEST_F(FileCheckTest, Expression) { TEST_F(FileCheckTest, Expression) {
SourceMgr SM;
std::unique_ptr<ExpressionLiteral> Ten = std::unique_ptr<ExpressionLiteral> Ten =
std::make_unique<ExpressionLiteral>(10); std::make_unique<ExpressionLiteral>(bufferize(SM, "10"), 10);
ExpressionLiteral *TenPtr = Ten.get(); ExpressionLiteral *TenPtr = Ten.get();
Expression Expr(std::move(Ten), Expression Expr(std::move(Ten),
ExpressionFormat(ExpressionFormat::Kind::HexLower)); ExpressionFormat(ExpressionFormat::Kind::HexLower));
@ -275,6 +286,8 @@ expectUndefErrors(std::unordered_set<std::string> ExpectedUndefVarNames,
uint64_t doAdd(uint64_t OpL, uint64_t OpR) { return OpL + OpR; } uint64_t doAdd(uint64_t OpL, uint64_t OpR) { return OpL + OpR; }
TEST_F(FileCheckTest, NumericVariable) { TEST_F(FileCheckTest, NumericVariable) {
SourceMgr SM;
// Undefined variable: getValue and eval fail, error returned by eval holds // Undefined variable: getValue and eval fail, error returned by eval holds
// the name of the undefined variable. // the name of the undefined variable.
NumericVariable FooVar("FOO", NumericVariable FooVar("FOO",
@ -282,7 +295,9 @@ TEST_F(FileCheckTest, NumericVariable) {
EXPECT_EQ("FOO", FooVar.getName()); EXPECT_EQ("FOO", FooVar.getName());
EXPECT_EQ(FooVar.getImplicitFormat(), ExpressionFormat::Kind::Unsigned); EXPECT_EQ(FooVar.getImplicitFormat(), ExpressionFormat::Kind::Unsigned);
NumericVariableUse FooVarUse("FOO", &FooVar); NumericVariableUse FooVarUse("FOO", &FooVar);
EXPECT_EQ(FooVarUse.getImplicitFormat(), ExpressionFormat::Kind::Unsigned); Expected<ExpressionFormat> ImplicitFormat = FooVarUse.getImplicitFormat(SM);
ASSERT_THAT_EXPECTED(ImplicitFormat, Succeeded());
EXPECT_EQ(*ImplicitFormat, ExpressionFormat::Kind::Unsigned);
EXPECT_FALSE(FooVar.getValue()); EXPECT_FALSE(FooVar.getValue());
Expected<uint64_t> EvalResult = FooVarUse.eval(); Expected<uint64_t> EvalResult = FooVarUse.eval();
expectUndefErrors({"FOO"}, EvalResult.takeError()); expectUndefErrors({"FOO"}, EvalResult.takeError());
@ -306,24 +321,32 @@ TEST_F(FileCheckTest, NumericVariable) {
} }
TEST_F(FileCheckTest, Binop) { TEST_F(FileCheckTest, Binop) {
NumericVariable FooVar("FOO", SourceMgr SM;
StringRef ExprStr = bufferize(SM, "FOO+BAR");
StringRef FooStr = ExprStr.take_front(3);
NumericVariable FooVar(FooStr,
ExpressionFormat(ExpressionFormat::Kind::Unsigned), 1); ExpressionFormat(ExpressionFormat::Kind::Unsigned), 1);
FooVar.setValue(42); FooVar.setValue(42);
std::unique_ptr<NumericVariableUse> FooVarUse = std::unique_ptr<NumericVariableUse> FooVarUse =
std::make_unique<NumericVariableUse>("FOO", &FooVar); std::make_unique<NumericVariableUse>(FooStr, &FooVar);
NumericVariable BarVar("BAR", StringRef BarStr = ExprStr.take_back(3);
NumericVariable BarVar(BarStr,
ExpressionFormat(ExpressionFormat::Kind::Unsigned), 2); ExpressionFormat(ExpressionFormat::Kind::Unsigned), 2);
BarVar.setValue(18); BarVar.setValue(18);
std::unique_ptr<NumericVariableUse> BarVarUse = std::unique_ptr<NumericVariableUse> BarVarUse =
std::make_unique<NumericVariableUse>("BAR", &BarVar); std::make_unique<NumericVariableUse>(BarStr, &BarVar);
BinaryOperation Binop(doAdd, std::move(FooVarUse), std::move(BarVarUse)); BinaryOperation Binop(ExprStr, doAdd, std::move(FooVarUse),
std::move(BarVarUse));
// Defined variables: eval returns right value; implicit format is as // Defined variables: eval returns right value; implicit format is as
// expected. // expected.
Expected<uint64_t> Value = Binop.eval(); Expected<uint64_t> Value = Binop.eval();
ASSERT_THAT_EXPECTED(Value, Succeeded()); ASSERT_THAT_EXPECTED(Value, Succeeded());
EXPECT_EQ(60U, *Value); EXPECT_EQ(60U, *Value);
EXPECT_EQ(Binop.getImplicitFormat(), ExpressionFormat::Kind::Unsigned); Expected<ExpressionFormat> ImplicitFormat = Binop.getImplicitFormat(SM);
ASSERT_THAT_EXPECTED(ImplicitFormat, Succeeded());
EXPECT_EQ(*ImplicitFormat, ExpressionFormat::Kind::Unsigned);
// 1 undefined variable: eval fails, error contains name of undefined // 1 undefined variable: eval fails, error contains name of undefined
// variable. // variable.
@ -338,24 +361,76 @@ TEST_F(FileCheckTest, Binop) {
expectUndefErrors({"FOO", "BAR"}, Value.takeError()); expectUndefErrors({"FOO", "BAR"}, Value.takeError());
// Literal + Variable has format of variable. // Literal + Variable has format of variable.
FooVarUse = std::make_unique<NumericVariableUse>("FOO", &FooVar); ExprStr = bufferize(SM, "FOO+18");
FooStr = ExprStr.take_front(3);
StringRef EighteenStr = ExprStr.take_back(2);
FooVarUse = std::make_unique<NumericVariableUse>(FooStr, &FooVar);
std::unique_ptr<ExpressionLiteral> Eighteen = std::unique_ptr<ExpressionLiteral> Eighteen =
std::make_unique<ExpressionLiteral>(18); std::make_unique<ExpressionLiteral>(EighteenStr, 18);
Binop = BinaryOperation(doAdd, std::move(FooVarUse), std::move(Eighteen)); Binop = BinaryOperation(ExprStr, doAdd, std::move(FooVarUse),
EXPECT_EQ(Binop.getImplicitFormat(), ExpressionFormat::Kind::Unsigned); std::move(Eighteen));
FooVarUse = std::make_unique<NumericVariableUse>("FOO", &FooVar); ImplicitFormat = Binop.getImplicitFormat(SM);
Eighteen = std::make_unique<ExpressionLiteral>(18); ASSERT_THAT_EXPECTED(ImplicitFormat, Succeeded());
Binop = BinaryOperation(doAdd, std::move(Eighteen), std::move(FooVarUse)); EXPECT_EQ(*ImplicitFormat, ExpressionFormat::Kind::Unsigned);
EXPECT_EQ(Binop.getImplicitFormat(), ExpressionFormat::Kind::Unsigned); ExprStr = bufferize(SM, "18+FOO");
FooStr = ExprStr.take_back(3);
EighteenStr = ExprStr.take_front(2);
FooVarUse = std::make_unique<NumericVariableUse>(FooStr, &FooVar);
Eighteen = std::make_unique<ExpressionLiteral>(EighteenStr, 18);
Binop = BinaryOperation(ExprStr, doAdd, std::move(Eighteen),
std::move(FooVarUse));
ImplicitFormat = Binop.getImplicitFormat(SM);
ASSERT_THAT_EXPECTED(ImplicitFormat, Succeeded());
EXPECT_EQ(*ImplicitFormat, ExpressionFormat::Kind::Unsigned);
// Variables with different implicit format conflict. // Variables with different implicit format conflict.
NumericVariable BazVar("BAZ", ExprStr = bufferize(SM, "FOO+BAZ");
FooStr = ExprStr.take_front(3);
StringRef BazStr = ExprStr.take_back(3);
NumericVariable BazVar(BazStr,
ExpressionFormat(ExpressionFormat::Kind::HexLower), 3); ExpressionFormat(ExpressionFormat::Kind::HexLower), 3);
FooVarUse = std::make_unique<NumericVariableUse>("BAZ", &FooVar); FooVarUse = std::make_unique<NumericVariableUse>(FooStr, &FooVar);
std::unique_ptr<NumericVariableUse> BazVarUse = std::unique_ptr<NumericVariableUse> BazVarUse =
std::make_unique<NumericVariableUse>("BAZ", &BazVar); std::make_unique<NumericVariableUse>(BazStr, &BazVar);
Binop = BinaryOperation(doAdd, std::move(FooVarUse), std::move(BazVarUse)); Binop = BinaryOperation(ExprStr, doAdd, std::move(FooVarUse),
EXPECT_EQ(Binop.getImplicitFormat(), ExpressionFormat::Kind::Conflict); std::move(BazVarUse));
ImplicitFormat = Binop.getImplicitFormat(SM);
expectDiagnosticError(
"implicit format conflict between 'FOO' (%u) and 'BAZ' (%x), "
"need an explicit format specifier",
ImplicitFormat.takeError());
// All variable conflicts are reported.
ExprStr = bufferize(SM, "(FOO+BAZ)+(FOO+QUUX)");
StringRef Paren1ExprStr = ExprStr.substr(1, 7);
FooStr = Paren1ExprStr.take_front(3);
BazStr = Paren1ExprStr.take_back(3);
StringRef Paren2ExprStr = ExprStr.substr(ExprStr.rfind('(') + 1, 8);
StringRef FooStr2 = Paren2ExprStr.take_front(3);
StringRef QuuxStr = Paren2ExprStr.take_back(4);
FooVarUse = std::make_unique<NumericVariableUse>(FooStr, &FooVar);
BazVarUse = std::make_unique<NumericVariableUse>(BazStr, &BazVar);
std::unique_ptr<NumericVariableUse> FooVarUse2 =
std::make_unique<NumericVariableUse>(FooStr2, &FooVar);
NumericVariable QuuxVar(
QuuxStr, ExpressionFormat(ExpressionFormat::Kind::HexLower), 4);
std::unique_ptr<NumericVariableUse> QuuxVarUse =
std::make_unique<NumericVariableUse>(QuuxStr, &QuuxVar);
std::unique_ptr<BinaryOperation> Binop1 = std::make_unique<BinaryOperation>(
ExprStr.take_front(9), doAdd, std::move(FooVarUse), std::move(BazVarUse));
std::unique_ptr<BinaryOperation> Binop2 = std::make_unique<BinaryOperation>(
ExprStr.take_back(10), doAdd, std::move(FooVarUse2),
std::move(QuuxVarUse));
std::unique_ptr<BinaryOperation> OuterBinop =
std::make_unique<BinaryOperation>(ExprStr, doAdd, std::move(Binop1),
std::move(Binop2));
ImplicitFormat = OuterBinop->getImplicitFormat(SM);
expectSameErrors<ErrorDiagnostic>(
{"implicit format conflict between 'FOO' (%u) and 'BAZ' (%x), "
"need an explicit format specifier",
"implicit format conflict between 'FOO' (%u) and 'QUUX' (%x), "
"need an explicit format specifier"},
ImplicitFormat.takeError());
} }
TEST_F(FileCheckTest, ValidVarNameStart) { TEST_F(FileCheckTest, ValidVarNameStart) {
@ -648,7 +723,8 @@ TEST_F(FileCheckTest, ParseNumericSubstitutionBlock) {
// Implicit format conflict. // Implicit format conflict.
expectDiagnosticError( expectDiagnosticError(
"variables with conflicting format specifier: need an explicit one", "implicit format conflict between 'FOO' (%u) and "
"'VAR_LOWER_HEX' (%x), need an explicit format specifier",
Tester.parseSubst("FOO+VAR_LOWER_HEX").takeError()); Tester.parseSubst("FOO+VAR_LOWER_HEX").takeError());
} }