Merge pull request #4054 from ethereum/token-names

Improve parser error messages
This commit is contained in:
chriseth 2018-05-09 11:06:23 +02:00 committed by GitHub
commit 54aa10ce36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 71 additions and 70 deletions

View File

@ -5,6 +5,7 @@ Features:
* Code Generator: Use native shift instructions on target Constantinople. * Code Generator: Use native shift instructions on target Constantinople.
* Gas Estimator: Only explore paths with higher gas costs. This reduces accuracy but greatly improves the speed of gas estimation. * Gas Estimator: Only explore paths with higher gas costs. This reduces accuracy but greatly improves the speed of gas estimation.
* Optimizer: Remove unnecessary masking of the result of known short instructions (``ADDRESS``, ``CALLER``, ``ORIGIN`` and ``COINBASE``). * Optimizer: Remove unnecessary masking of the result of known short instructions (``ADDRESS``, ``CALLER``, ``ORIGIN`` and ``COINBASE``).
* Parser: Display nicer error messages by showing the actual tokens and not internal names.
* Type Checker: Deprecate the ``years`` unit denomination and raise a warning for it (or an error as experimental 0.5.0 feature). * Type Checker: Deprecate the ``years`` unit denomination and raise a warning for it (or an error as experimental 0.5.0 feature).
* Type Checker: Make literals (without explicit type casting) an error for tight packing as experimental 0.5.0 feature. * Type Checker: Make literals (without explicit type casting) an error for tight packing as experimental 0.5.0 feature.
* Type Checker: Warn about wildcard tuple assignments (this will turn into an error with version 0.5.0). * Type Checker: Warn about wildcard tuple assignments (this will turn into an error with version 0.5.0).

View File

@ -276,7 +276,7 @@ assembly::Expression Parser::parseExpression()
int args = instructionInfo(instr.instruction).args; int args = instructionInfo(instr.instruction).args;
if (args > 0 && currentToken() != Token::LParen) if (args > 0 && currentToken() != Token::LParen)
fatalParserError(string( fatalParserError(string(
"Expected token \"(\" (\"" + "Expected '(' (instruction \"" +
instructionNames().at(instr.instruction) + instructionNames().at(instr.instruction) +
"\" expects " + "\" expects " +
boost::lexical_cast<string>(args) + boost::lexical_cast<string>(args) +
@ -504,7 +504,7 @@ assembly::Expression Parser::parseCall(Parser::ElementaryOperation&& _initialOp)
/// check for premature closing parentheses /// check for premature closing parentheses
if (currentToken() == Token::RParen) if (currentToken() == Token::RParen)
fatalParserError(string( fatalParserError(string(
"Expected expression (\"" + "Expected expression (instruction \"" +
instructionNames().at(instr) + instructionNames().at(instr) +
"\" expects " + "\" expects " +
boost::lexical_cast<string>(args) + boost::lexical_cast<string>(args) +
@ -516,7 +516,7 @@ assembly::Expression Parser::parseCall(Parser::ElementaryOperation&& _initialOp)
{ {
if (currentToken() != Token::Comma) if (currentToken() != Token::Comma)
fatalParserError(string( fatalParserError(string(
"Expected comma (\"" + "Expected ',' (instruction \"" +
instructionNames().at(instr) + instructionNames().at(instr) +
"\" expects " + "\" expects " +
boost::lexical_cast<string>(args) + boost::lexical_cast<string>(args) +
@ -529,7 +529,7 @@ assembly::Expression Parser::parseCall(Parser::ElementaryOperation&& _initialOp)
ret.location.end = endPosition(); ret.location.end = endPosition();
if (currentToken() == Token::Comma) if (currentToken() == Token::Comma)
fatalParserError(string( fatalParserError(string(
"Expected ')' (\"" + "Expected ')' (instruction \"" +
instructionNames().at(instr) + instructionNames().at(instr) +
"\" expects " + "\" expects " +
boost::lexical_cast<string>(args) + boost::lexical_cast<string>(args) +

View File

@ -528,7 +528,7 @@ ASTPointer<EnumDefinition> Parser::parseEnumDefinition()
break; break;
expectToken(Token::Comma); expectToken(Token::Comma);
if (m_scanner->currentToken() != Token::Identifier) if (m_scanner->currentToken() != Token::Identifier)
fatalParserError(string("Expected Identifier after ','")); fatalParserError(string("Expected identifier after ','"));
} }
if (members.size() == 0) if (members.size() == 0)
parserError({"enum with no members is not allowed."}); parserError({"enum with no members is not allowed."});

View File

@ -68,35 +68,24 @@ void ParserBase::expectToken(Token::Value _value, bool _advance)
Token::Value tok = m_scanner->currentToken(); Token::Value tok = m_scanner->currentToken();
if (tok != _value) if (tok != _value)
{ {
if (Token::isReservedKeyword(tok)) auto tokenName = [this](Token::Value _token)
{ {
fatalParserError( if (_token == Token::Identifier)
string("Expected token ") + return string("identifier");
string(Token::name(_value)) + else if (_token == Token::EOS)
string(" got reserved keyword '") + return string("end of source");
string(Token::name(tok)) + else if (Token::isReservedKeyword(_token))
string("'") return string("reserved keyword '") + Token::friendlyName(_token) + "'";
); else if (Token::isElementaryTypeName(_token)) //for the sake of accuracy in reporting
} {
else if (Token::isElementaryTypeName(tok)) //for the sake of accuracy in reporting ElementaryTypeNameToken elemTypeName = m_scanner->currentElementaryTypeNameToken();
{ return string("'") + elemTypeName.toString() + "'";
ElementaryTypeNameToken elemTypeName = m_scanner->currentElementaryTypeNameToken(); }
fatalParserError( else
string("Expected token ") + return string("'") + Token::friendlyName(_token) + "'";
string(Token::name(_value)) + };
string(" got '") +
elemTypeName.toString() + fatalParserError(string("Expected ") + tokenName(_value) + string(" but got ") + tokenName(tok));
string("'")
);
}
else
fatalParserError(
string("Expected token ") +
string(Token::name(_value)) +
string(" got '") +
string(Token::name(m_scanner->currentToken())) +
string("'")
);
} }
if (_advance) if (_advance)
m_scanner->next(); m_scanner->next();

View File

@ -304,6 +304,17 @@ public:
return m_string[tok]; return m_string[tok];
} }
static std::string friendlyName(Value tok)
{
char const* ret = toString(tok);
if (ret == nullptr)
{
ret = name(tok);
solAssert(ret != nullptr, "");
}
return std::string(ret);
}
// @returns the precedence > 0 for binary and compare // @returns the precedence > 0 for binary and compare
// operators; returns 0 otherwise. // operators; returns 0 otherwise.
static int precedence(Value tok) static int precedence(Value tok)

View File

@ -212,10 +212,10 @@ BOOST_AUTO_TEST_CASE(tokens_as_identifers)
BOOST_AUTO_TEST_CASE(lacking_types) BOOST_AUTO_TEST_CASE(lacking_types)
{ {
CHECK_ERROR("{ let x := 1:u256 }", ParserError, "Expected token Identifier got 'Assign'"); CHECK_ERROR("{ let x := 1:u256 }", ParserError, "Expected identifier but got '='");
CHECK_ERROR("{ let x:u256 := 1 }", ParserError, "Expected token Colon got 'RBrace'"); CHECK_ERROR("{ let x:u256 := 1 }", ParserError, "Expected ':' but got '}'");
CHECK_ERROR("{ function f(a) {} }", ParserError, "Expected token Colon got 'RParen'"); CHECK_ERROR("{ function f(a) {} }", ParserError, "Expected ':' but got ')'");
CHECK_ERROR("{ function f(a:u256) -> b {} }", ParserError, "Expected token Colon got 'LBrace'"); CHECK_ERROR("{ function f(a:u256) -> b {} }", ParserError, "Expected ':' but got '{'");
} }
BOOST_AUTO_TEST_CASE(invalid_types) BOOST_AUTO_TEST_CASE(invalid_types)
@ -294,7 +294,7 @@ BOOST_AUTO_TEST_CASE(if_statement)
BOOST_AUTO_TEST_CASE(if_statement_invalid) BOOST_AUTO_TEST_CASE(if_statement_invalid)
{ {
CHECK_ERROR("{ if let x:u256 {} }", ParserError, "Literal or identifier expected."); CHECK_ERROR("{ if let x:u256 {} }", ParserError, "Literal or identifier expected.");
CHECK_ERROR("{ if true:bool let x:u256 := 3:u256 }", ParserError, "Expected token LBrace"); CHECK_ERROR("{ if true:bool let x:u256 := 3:u256 }", ParserError, "Expected '{' but got reserved keyword 'let'");
// TODO change this to an error once we check types. // TODO change this to an error once we check types.
BOOST_CHECK(successParse("{ if 42:u256 { } }")); BOOST_CHECK(successParse("{ if 42:u256 { } }"));
} }

View File

@ -170,7 +170,7 @@ BOOST_AUTO_TEST_CASE(smoke_test)
BOOST_AUTO_TEST_CASE(surplus_input) BOOST_AUTO_TEST_CASE(surplus_input)
{ {
CHECK_PARSE_ERROR("{ } { }", ParserError, "Expected token EOS"); CHECK_PARSE_ERROR("{ } { }", ParserError, "Expected end of source but got '{'");
} }
BOOST_AUTO_TEST_CASE(simple_instructions) BOOST_AUTO_TEST_CASE(simple_instructions)
@ -246,7 +246,7 @@ BOOST_AUTO_TEST_CASE(functional)
BOOST_AUTO_TEST_CASE(functional_partial) BOOST_AUTO_TEST_CASE(functional_partial)
{ {
CHECK_PARSE_ERROR("{ let x := byte }", ParserError, "Expected token \"(\""); CHECK_PARSE_ERROR("{ let x := byte }", ParserError, "Expected '(' (instruction \"byte\" expects 2 arguments)");
} }
BOOST_AUTO_TEST_CASE(functional_partial_success) BOOST_AUTO_TEST_CASE(functional_partial_success)
@ -290,10 +290,10 @@ BOOST_AUTO_TEST_CASE(if_statement_scope)
BOOST_AUTO_TEST_CASE(if_statement_invalid) BOOST_AUTO_TEST_CASE(if_statement_invalid)
{ {
CHECK_PARSE_ERROR("{ if mload {} }", ParserError, "Expected token \"(\""); CHECK_PARSE_ERROR("{ if mload {} }", ParserError, "Expected '(' (instruction \"mload\" expects 1 arguments)");
BOOST_CHECK("{ if calldatasize() {}"); BOOST_CHECK("{ if calldatasize() {}");
CHECK_PARSE_ERROR("{ if mstore(1, 1) {} }", ParserError, "Instruction \"mstore\" not allowed in this context"); CHECK_PARSE_ERROR("{ if mstore(1, 1) {} }", ParserError, "Instruction \"mstore\" not allowed in this context");
CHECK_PARSE_ERROR("{ if 32 let x := 3 }", ParserError, "Expected token LBrace"); CHECK_PARSE_ERROR("{ if 32 let x := 3 }", ParserError, "Expected '{' but got reserved keyword 'let'");
} }
BOOST_AUTO_TEST_CASE(switch_statement) BOOST_AUTO_TEST_CASE(switch_statement)
@ -320,7 +320,7 @@ BOOST_AUTO_TEST_CASE(switch_duplicate_case)
BOOST_AUTO_TEST_CASE(switch_invalid_expression) BOOST_AUTO_TEST_CASE(switch_invalid_expression)
{ {
CHECK_PARSE_ERROR("{ switch {} default {} }", ParserError, "Literal, identifier or instruction expected."); CHECK_PARSE_ERROR("{ switch {} default {} }", ParserError, "Literal, identifier or instruction expected.");
CHECK_PARSE_ERROR("{ switch mload default {} }", ParserError, "Expected token \"(\""); CHECK_PARSE_ERROR("{ switch mload default {} }", ParserError, "Expected '(' (instruction \"mload\" expects 1 arguments)");
CHECK_PARSE_ERROR("{ switch mstore(1, 1) default {} }", ParserError, "Instruction \"mstore\" not allowed in this context"); CHECK_PARSE_ERROR("{ switch mstore(1, 1) default {} }", ParserError, "Instruction \"mstore\" not allowed in this context");
} }
@ -341,7 +341,7 @@ BOOST_AUTO_TEST_CASE(switch_invalid_case)
BOOST_AUTO_TEST_CASE(switch_invalid_body) BOOST_AUTO_TEST_CASE(switch_invalid_body)
{ {
CHECK_PARSE_ERROR("{ switch 42 case 1 mul case 2 {} default {} }", ParserError, "Expected token LBrace got 'Identifier'"); CHECK_PARSE_ERROR("{ switch 42 case 1 mul case 2 {} default {} }", ParserError, "Expected '{' but got identifier");
} }
BOOST_AUTO_TEST_CASE(for_statement) BOOST_AUTO_TEST_CASE(for_statement)
@ -353,10 +353,10 @@ BOOST_AUTO_TEST_CASE(for_statement)
BOOST_AUTO_TEST_CASE(for_invalid_expression) BOOST_AUTO_TEST_CASE(for_invalid_expression)
{ {
CHECK_PARSE_ERROR("{ for {} {} {} {} }", ParserError, "Literal, identifier or instruction expected."); CHECK_PARSE_ERROR("{ for {} {} {} {} }", ParserError, "Literal, identifier or instruction expected.");
CHECK_PARSE_ERROR("{ for 1 1 {} {} }", ParserError, "Expected token LBrace got 'Number'"); CHECK_PARSE_ERROR("{ for 1 1 {} {} }", ParserError, "Expected '{' but got 'Number'");
CHECK_PARSE_ERROR("{ for {} 1 1 {} }", ParserError, "Expected token LBrace got 'Number'"); CHECK_PARSE_ERROR("{ for {} 1 1 {} }", ParserError, "Expected '{' but got 'Number'");
CHECK_PARSE_ERROR("{ for {} 1 {} 1 }", ParserError, "Expected token LBrace got 'Number'"); CHECK_PARSE_ERROR("{ for {} 1 {} 1 }", ParserError, "Expected '{' but got 'Number'");
CHECK_PARSE_ERROR("{ for {} mload {} {} }", ParserError, "Expected token \"(\""); CHECK_PARSE_ERROR("{ for {} mload {} {} }", ParserError, "Expected '(' (instruction \"mload\" expects 1 arguments)");
CHECK_PARSE_ERROR("{ for {} mstore(1, 1) {} {} }", ParserError, "Instruction \"mstore\" not allowed in this context"); CHECK_PARSE_ERROR("{ for {} mstore(1, 1) {} {} }", ParserError, "Instruction \"mstore\" not allowed in this context");
} }
@ -437,13 +437,13 @@ BOOST_AUTO_TEST_CASE(invalid_tuple_assignment)
BOOST_AUTO_TEST_CASE(instruction_too_few_arguments) BOOST_AUTO_TEST_CASE(instruction_too_few_arguments)
{ {
CHECK_PARSE_ERROR("{ mul() }", ParserError, "Expected expression (\"mul\" expects 2 arguments)"); CHECK_PARSE_ERROR("{ mul() }", ParserError, "Expected expression (instruction \"mul\" expects 2 arguments)");
CHECK_PARSE_ERROR("{ mul(1) }", ParserError, "Expected comma (\"mul\" expects 2 arguments)"); CHECK_PARSE_ERROR("{ mul(1) }", ParserError, "Expected ',' (instruction \"mul\" expects 2 arguments)");
} }
BOOST_AUTO_TEST_CASE(instruction_too_many_arguments) BOOST_AUTO_TEST_CASE(instruction_too_many_arguments)
{ {
CHECK_PARSE_ERROR("{ mul(1, 2, 3) }", ParserError, "Expected ')' (\"mul\" expects 2 arguments)"); CHECK_PARSE_ERROR("{ mul(1, 2, 3) }", ParserError, "Expected ')' (instruction \"mul\" expects 2 arguments)");
} }
BOOST_AUTO_TEST_CASE(recursion_depth) BOOST_AUTO_TEST_CASE(recursion_depth)

View File

@ -992,7 +992,7 @@ BOOST_AUTO_TEST_CASE(keyword_is_reserved)
for (const auto& keyword: keywords) for (const auto& keyword: keywords)
{ {
auto text = std::string("contract ") + keyword + " {}"; auto text = std::string("contract ") + keyword + " {}";
CHECK_PARSE_ERROR(text.c_str(), "Expected token Identifier got reserved keyword"); CHECK_PARSE_ERROR(text.c_str(), "Expected identifier but got reserved keyword");
} }
} }

View File

@ -326,8 +326,8 @@ BOOST_AUTO_TEST_CASE(compilation_error)
{ {
BOOST_CHECK_EQUAL( BOOST_CHECK_EQUAL(
dev::jsonCompactPrint(error), dev::jsonCompactPrint(error),
"{\"component\":\"general\",\"formattedMessage\":\"fileA:1:23: ParserError: Expected token Identifier got 'RBrace'\\n" "{\"component\":\"general\",\"formattedMessage\":\"fileA:1:23: ParserError: Expected identifier but got '}'\\n"
"contract A { function }\\n ^\\n\",\"message\":\"Expected token Identifier got 'RBrace'\"," "contract A { function }\\n ^\\n\",\"message\":\"Expected identifier but got '}'\","
"\"severity\":\"error\",\"sourceLocation\":{\"end\":22,\"file\":\"fileA\",\"start\":22},\"type\":\"ParserError\"}" "\"severity\":\"error\",\"sourceLocation\":{\"end\":22,\"file\":\"fileA\",\"start\":22},\"type\":\"ParserError\"}"
); );
} }

View File

@ -5,4 +5,4 @@ contract C {
} }
} }
// ---- // ----
// ParserError: (118-118): Expected token Semicolon got 'Identifier' // ParserError: (118-118): Expected ';' but got identifier

View File

@ -2,4 +2,4 @@ contract Foo {
uint constant = 4; uint constant = 4;
} }
// ---- // ----
// ParserError: (30-30): Expected token Identifier got 'Assign' // ParserError: (30-30): Expected identifier but got '='

View File

@ -5,4 +5,4 @@ contract C {
} }
} }
// ---- // ----
// ParserError: (49-49): Expected token LParen got 'Semicolon' // ParserError: (49-49): Expected '(' but got ';'

View File

@ -2,4 +2,4 @@ contract c {
event e; event e;
} }
// ---- // ----
// ParserError: (21-21): Expected token LParen got 'Semicolon' // ParserError: (21-21): Expected '(' but got ';'

View File

@ -2,4 +2,4 @@ contract c {
uint external x; uint external x;
} }
// ---- // ----
// ParserError: (19-19): Expected token Identifier got 'External' // ParserError: (19-19): Expected identifier but got 'external'

View File

@ -2,4 +2,4 @@ contract A {
fixed40x40 pi = 3.14.15; fixed40x40 pi = 3.14.15;
} }
// ---- // ----
// ParserError: (34-34): Expected token Semicolon got 'Number' // ParserError: (34-34): Expected ';' but got 'Number'

View File

@ -2,4 +2,4 @@ contract test {
function (uint, uint) modifier1() returns (uint) f1; function (uint, uint) modifier1() returns (uint) f1;
} }
// ---- // ----
// ParserError: (66-66): Expected token LBrace got 'Identifier' // ParserError: (66-66): Expected '{' but got identifier

View File

@ -6,4 +6,4 @@ contract Foo {
} }
} }
// ---- // ----
// ParserError: (67-67): Expected token Semicolon got 'Constant' // ParserError: (67-67): Expected ';' but got 'constant'

View File

@ -2,4 +2,4 @@ contract Foo {
uint[] memory x; uint[] memory x;
} }
// ---- // ----
// ParserError: (23-23): Expected token Identifier got 'Memory' // ParserError: (23-23): Expected identifier but got 'memory'

View File

@ -2,4 +2,4 @@ contract c {
enum foo { WARNING,} enum foo { WARNING,}
} }
// ---- // ----
// ParserError: (33-33): Expected Identifier after ',' // ParserError: (33-33): Expected identifier after ','

View File

@ -3,4 +3,4 @@ contract test {
function b() returns (uint r) { r = a({: 1, : 2, : 3}); } function b() returns (uint r) { r = a({: 1, : 2, : 3}); }
} }
// ---- // ----
// ParserError: (143-143): Expected token Identifier got 'Colon' // ParserError: (143-143): Expected identifier but got ':'

View File

@ -2,4 +2,4 @@ contract test {
uint256 ; uint256 ;
} }
// ---- // ----
// ParserError: (28-28): Expected token Identifier got 'Semicolon' // ParserError: (28-28): Expected identifier but got ';'

View File

@ -2,4 +2,4 @@ contract c {
modifier mod { if (msg.sender == 0) _ } modifier mod { if (msg.sender == 0) _ }
} }
// ---- // ----
// ParserError: (52-52): Expected token Semicolon got 'RBrace' // ParserError: (52-52): Expected ';' but got '}'

View File

@ -2,4 +2,4 @@ contract test {
uint payable x; uint payable x;
} }
// ---- // ----
// ParserError: (22-22): Expected token Identifier got 'Payable' // ParserError: (22-22): Expected identifier but got 'payable'

View File

@ -4,4 +4,4 @@ contract C {
} }
} }
// ---- // ----
// ParserError: (42-42): Expected token Comma got 'Number' // ParserError: (42-42): Expected ',' but got 'Number'

View File

@ -2,4 +2,4 @@ contract Foo {
function f() { var[] a; } function f() { var[] a; }
} }
// ---- // ----
// ParserError: (34-34): Expected token Identifier got 'LBrack' // ParserError: (34-34): Expected identifier but got '['