diff --git a/libsolidity/inlineasm/AsmAnalysis.cpp b/libsolidity/inlineasm/AsmAnalysis.cpp index e03eea2ec..9e0ade2a7 100644 --- a/libsolidity/inlineasm/AsmAnalysis.cpp +++ b/libsolidity/inlineasm/AsmAnalysis.cpp @@ -181,10 +181,21 @@ bool AsmAnalyzer::operator()(FunctionalAssignment const& _assignment) bool AsmAnalyzer::operator()(assembly::VariableDeclaration const& _varDecl) { + int const expectedItems = _varDecl.variables.size(); int const stackHeight = m_stackHeight; bool success = boost::apply_visitor(*this, *_varDecl.value); - solAssert(m_stackHeight - stackHeight == 1, "Invalid value size."); - boost::get(m_currentScope->identifiers.at(_varDecl.variable.name)).active = true; + if ((m_stackHeight - stackHeight) != expectedItems) + { + m_errors.push_back(make_shared( + Error::Type::DeclarationError, + "Variable count mismatch.", + _varDecl.location + )); + return false; + } + + for (auto const& variable: _varDecl.variables) + boost::get(m_currentScope->identifiers.at(variable.name)).active = true; m_info.stackHeightInfo[&_varDecl] = m_stackHeight; return success; } diff --git a/libsolidity/inlineasm/AsmCodeGen.cpp b/libsolidity/inlineasm/AsmCodeGen.cpp index b8af9dc61..02d5ced0a 100644 --- a/libsolidity/inlineasm/AsmCodeGen.cpp +++ b/libsolidity/inlineasm/AsmCodeGen.cpp @@ -251,11 +251,15 @@ public: void operator()(assembly::VariableDeclaration const& _varDecl) { int height = m_assembly.stackHeight(); + int expectedItems = _varDecl.variables.size(); boost::apply_visitor(*this, *_varDecl.value); - expectDeposit(1, height); - auto& var = boost::get(m_scope.identifiers.at(_varDecl.variable.name)); - var.stackHeight = height; - var.active = true; + expectDeposit(expectedItems, height); + for (auto const& variable: _varDecl.variables) + { + auto& var = boost::get(m_scope.identifiers.at(variable.name)); + var.stackHeight = height++; + var.active = true; + } } void operator()(assembly::Block const& _block) { diff --git a/libsolidity/inlineasm/AsmData.h b/libsolidity/inlineasm/AsmData.h index 8efe1f073..65dfbadfd 100644 --- a/libsolidity/inlineasm/AsmData.h +++ b/libsolidity/inlineasm/AsmData.h @@ -65,7 +65,7 @@ struct FunctionalAssignment { SourceLocation location; Identifier variableName; struct FunctionalInstruction { SourceLocation location; Instruction instruction; std::vector arguments; }; struct FunctionCall { SourceLocation location; Identifier functionName; std::vector arguments; }; /// Block-scope variable declaration ("let x:u256 := mload(20:u256)"), non-hoisted -struct VariableDeclaration { SourceLocation location; TypedName variable; std::shared_ptr value; }; +struct VariableDeclaration { SourceLocation location; TypedNameList variables; std::shared_ptr value; }; /// Block that creates a scope (frees declared stack variables) struct Block { SourceLocation location; std::vector statements; }; /// Function definition ("function f(a, b) -> (d, e) { ... }") diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp index a96984f55..73f70e3f3 100644 --- a/libsolidity/inlineasm/AsmParser.cpp +++ b/libsolidity/inlineasm/AsmParser.cpp @@ -258,7 +258,14 @@ assembly::VariableDeclaration Parser::parseVariableDeclaration() { VariableDeclaration varDecl = createWithLocation(); expectToken(Token::Let); - varDecl.variable = parseTypedName(); + while (true) + { + varDecl.variables.push_back(parseTypedName()); + if (m_scanner->currentToken() == Token::Comma) + expectToken(Token::Comma); + else + break; + } expectToken(Token::Colon); expectToken(Token::Assign); varDecl.value.reset(new Statement(parseExpression())); diff --git a/libsolidity/inlineasm/AsmPrinter.cpp b/libsolidity/inlineasm/AsmPrinter.cpp index 636e61b84..219a57ad8 100644 --- a/libsolidity/inlineasm/AsmPrinter.cpp +++ b/libsolidity/inlineasm/AsmPrinter.cpp @@ -121,7 +121,16 @@ string AsmPrinter::operator()(assembly::FunctionalAssignment const& _functionalA string AsmPrinter::operator()(assembly::VariableDeclaration const& _variableDeclaration) { - return "let " + _variableDeclaration.variable.name + appendTypeName(_variableDeclaration.variable.type) + " := " + boost::apply_visitor(*this, *_variableDeclaration.value); + string out = "let "; + out += boost::algorithm::join( + _variableDeclaration.variables | boost::adaptors::transformed( + [this](TypedName variable) { return variable.name + appendTypeName(variable.type); } + ), + ", " + ); + out += " := "; + out += boost::apply_visitor(*this, *_variableDeclaration.value); + return out; } string AsmPrinter::operator()(assembly::FunctionDefinition const& _functionDefinition) diff --git a/libsolidity/inlineasm/AsmScopeFiller.cpp b/libsolidity/inlineasm/AsmScopeFiller.cpp index eb10dbb34..05b1b2119 100644 --- a/libsolidity/inlineasm/AsmScopeFiller.cpp +++ b/libsolidity/inlineasm/AsmScopeFiller.cpp @@ -59,7 +59,10 @@ bool ScopeFiller::operator()(Label const& _item) bool ScopeFiller::operator()(assembly::VariableDeclaration const& _varDecl) { - return registerVariable(_varDecl.variable, _varDecl.location, *m_currentScope); + for (auto const& variable: _varDecl.variables) + if (!registerVariable(variable, _varDecl.location, *m_currentScope)) + return false; + return true; } bool ScopeFiller::operator()(assembly::FunctionDefinition const& _funDef) diff --git a/test/libjulia/Parser.cpp b/test/libjulia/Parser.cpp index d1081067a..3931ceb86 100644 --- a/test/libjulia/Parser.cpp +++ b/test/libjulia/Parser.cpp @@ -161,6 +161,11 @@ BOOST_AUTO_TEST_CASE(function_calls) BOOST_CHECK(successParse("{ function f(a:u256) -> b:u256 {} function g(a:u256, b:u256, c:u256) {} function x() { g(1:u256, 2:u256, f(mul(2:u256, 3:u256))) x() } }")); } +BOOST_AUTO_TEST_CASE(tuple_assignment) +{ + BOOST_CHECK(successParse("{ function f() -> a:u256, b:u256, c:u256 {} let x:u256, y:u256, z:u256 := f() }")); +} + BOOST_AUTO_TEST_CASE(label) { CHECK_ERROR("{ label: }", ParserError, "Labels are not supported."); diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index 0729c8dbd..f155cba73 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -161,6 +161,21 @@ BOOST_AUTO_TEST_CASE(vardecl) BOOST_CHECK(successParse("{ let x := 7 }")); } +BOOST_AUTO_TEST_CASE(vardecl_name_clashes) +{ + CHECK_PARSE_ERROR("{ let x := 1 let x := 2 }", DeclarationError, "Variable name x already taken in this scope."); +} + +BOOST_AUTO_TEST_CASE(vardecl_multi) +{ + BOOST_CHECK(successParse("{ function f() -> x, y {} let x, y := f() }")); +} + +BOOST_AUTO_TEST_CASE(vardecl_multi_conflict) +{ + CHECK_PARSE_ERROR("{ function f() -> x, y {} let x, x := f() }", DeclarationError, "Variable name x already taken in this scope."); +} + BOOST_AUTO_TEST_CASE(vardecl_bool) { CHECK_PARSE_ERROR("{ let x := true }", ParserError, "True and false are not valid literals."); @@ -249,6 +264,12 @@ BOOST_AUTO_TEST_CASE(variable_access_cross_functions) CHECK_PARSE_ERROR("{ let x := 2 function g() { x pop } }", DeclarationError, "Identifier not found."); } +BOOST_AUTO_TEST_CASE(invalid_tuple_assignment) +{ + /// The push(42) is added here to silence the unbalanced stack error, so that there's only one error reported. + CHECK_PARSE_ERROR("{ 42 let x, y := 1 }", DeclarationError, "Variable count mismatch."); +} + BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE(Printing) @@ -283,6 +304,11 @@ BOOST_AUTO_TEST_CASE(print_assignments) parsePrintCompare("{\n let x := mul(2, 3)\n 7\n =: x\n x := add(1, 2)\n}"); } +BOOST_AUTO_TEST_CASE(print_multi_assignments) +{ + parsePrintCompare("{\n function f() -> x, y\n {\n }\n let x, y := f()\n}"); +} + BOOST_AUTO_TEST_CASE(print_string_literals) { parsePrintCompare("{\n \"\\n'\\xab\\x95\\\"\"\n pop\n}");