From deebc7e8601185006b74a5ee78b83c50bdf37abc Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 9 Oct 2015 20:44:56 +0200 Subject: Multi-variable declarations. --- libsolidity/AST.cpp | 7 ++ libsolidity/AST.h | 2 + libsolidity/ASTAnnotations.h | 7 ++ libsolidity/Compiler.cpp | 26 +++++-- libsolidity/ExpressionCompiler.cpp | 35 ++++------ libsolidity/Parser.cpp | 15 ++-- libsolidity/TypeChecker.cpp | 80 ++++++++++++---------- test/libsolidity/SolidityNameAndTypeResolution.cpp | 59 ++++++++++++++++ test/libsolidity/SolidityParser.cpp | 12 ---- 9 files changed, 166 insertions(+), 77 deletions(-) diff --git a/libsolidity/AST.cpp b/libsolidity/AST.cpp index d55bc13c..490ae29a 100644 --- a/libsolidity/AST.cpp +++ b/libsolidity/AST.cpp @@ -325,6 +325,13 @@ ReturnAnnotation& Return::annotation() const return static_cast(*m_annotation); } +VariableDeclarationStatementAnnotation& VariableDeclarationStatement::annotation() const +{ + if (!m_annotation) + m_annotation = new VariableDeclarationStatementAnnotation(); + return static_cast(*m_annotation); +} + ExpressionAnnotation& Expression::annotation() const { if (!m_annotation) diff --git a/libsolidity/AST.h b/libsolidity/AST.h index 5c4c889d..f257dfb2 100644 --- a/libsolidity/AST.h +++ b/libsolidity/AST.h @@ -982,6 +982,8 @@ public: virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTConstVisitor& _visitor) const override; + VariableDeclarationStatementAnnotation& annotation() const override; + std::vector> const& declarations() const { return m_variables; } Expression const* initialValue() const { return m_initialValue.get(); } diff --git a/libsolidity/ASTAnnotations.h b/libsolidity/ASTAnnotations.h index be9b164c..32602628 100644 --- a/libsolidity/ASTAnnotations.h +++ b/libsolidity/ASTAnnotations.h @@ -84,6 +84,13 @@ struct UserDefinedTypeNameAnnotation: TypeNameAnnotation Declaration const* referencedDeclaration = nullptr; }; +struct VariableDeclarationStatementAnnotation: ASTAnnotation +{ + /// Information about which component of the vaule is assigned to which variable. + /// The pointer can be null to signify that the component is discarded. + std::vector assignments; +}; + struct ExpressionAnnotation: ASTAnnotation { /// Inferred type of the expression. diff --git a/libsolidity/Compiler.cpp b/libsolidity/Compiler.cpp index 64a67a4f..7c6c7831 100644 --- a/libsolidity/Compiler.cpp +++ b/libsolidity/Compiler.cpp @@ -623,13 +623,29 @@ bool Compiler::visit(VariableDeclarationStatement const& _variableDeclarationSta { StackHeightChecker checker(m_context); CompilerContext::LocationSetter locationSetter(m_context, _variableDeclarationStatement); - solAssert(_variableDeclarationStatement.declarations().size() == 1, "To be implemented."); - solAssert(!!_variableDeclarationStatement.declarations().front(), ""); - VariableDeclaration const& varDecl = *_variableDeclarationStatement.declarations().front(); if (Expression const* expression = _variableDeclarationStatement.initialValue()) { - compileExpression(*expression, varDecl.annotation().type); - CompilerUtils(m_context).moveToStackVariable(varDecl); + CompilerUtils utils(m_context); + compileExpression(*expression); + TypePointers valueTypes; + if (auto tupleType = dynamic_cast(expression->annotation().type.get())) + valueTypes = tupleType->components(); + else + valueTypes = TypePointers{expression->annotation().type}; + auto const& assignments = _variableDeclarationStatement.annotation().assignments; + solAssert(assignments.size() == valueTypes.size(), ""); + for (size_t i = 0; i < assignments.size(); ++i) + { + size_t j = assignments.size() - i - 1; + VariableDeclaration const* varDecl = assignments[j]; + if (!varDecl) + utils.popStackElement(*valueTypes[j]); + else + { + utils.convertType(*valueTypes[j], *varDecl->annotation().type); + utils.moveToStackVariable(*varDecl); + } + } } checker.check(); return false; diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp index c11ef29e..f6eed0de 100644 --- a/libsolidity/ExpressionCompiler.cpp +++ b/libsolidity/ExpressionCompiler.cpp @@ -427,11 +427,6 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) unsigned returnParametersSize = CompilerUtils::sizeOnStack(function.returnParameterTypes()); // callee adds return parameters, but removes arguments and return label m_context.adjustStackOffset(returnParametersSize - CompilerUtils::sizeOnStack(function.parameterTypes()) - 1); - - // @todo for now, the return value of a function is its first return value, so remove - // all others - for (unsigned i = 1; i < function.returnParameterTypes().size(); ++i) - utils().popStackElement(*function.returnParameterTypes()[i]); break; } case Location::External: @@ -1123,19 +1118,15 @@ void ExpressionCompiler::appendExternalFunctionCall( bool returnSuccessCondition = funKind == FunctionKind::Bare || funKind == FunctionKind::BareCallCode; bool isCallCode = funKind == FunctionKind::BareCallCode || funKind == FunctionKind::CallCode; - //@todo only return the first return value for now - Type const* firstReturnType = - _functionType.returnParameterTypes().empty() ? - nullptr : - _functionType.returnParameterTypes().front().get(); unsigned retSize = 0; if (returnSuccessCondition) retSize = 0; // return value actually is success condition - else if (firstReturnType) - { - retSize = firstReturnType->calldataEncodedSize(); - solAssert(retSize > 0, "Unable to return dynamic type from external call."); - } + else + for (auto const& retType: _functionType.returnParameterTypes()) + { + solAssert(retType->calldataEncodedSize() > 0, "Unable to return dynamic type from external call."); + retSize += retType->calldataEncodedSize(); + } // Evaluate arguments. TypePointers argumentTypes; @@ -1255,16 +1246,20 @@ void ExpressionCompiler::appendExternalFunctionCall( utils().loadFromMemoryDynamic(IntegerType(160), false, true, false); utils().convertType(IntegerType(160), FixedBytesType(20)); } - else if (firstReturnType) + else if (!_functionType.returnParameterTypes().empty()) { utils().fetchFreeMemoryPointer(); - if (dynamic_cast(firstReturnType)) + bool memoryNeeded = false; + for (auto const& retType: _functionType.returnParameterTypes()) { - utils().loadFromMemoryDynamic(*firstReturnType, false, true, true); - utils().storeFreeMemoryPointer(); + utils().loadFromMemoryDynamic(*retType, false, true, true); + if (dynamic_cast(retType.get())) + memoryNeeded = true; } + if (memoryNeeded) + utils().storeFreeMemoryPointer(); else - utils().loadFromMemoryDynamic(*firstReturnType, false, true, false); + m_context << eth::Instruction::POP; } } diff --git a/libsolidity/Parser.cpp b/libsolidity/Parser.cpp index e02a70d5..98e1fbf8 100644 --- a/libsolidity/Parser.cpp +++ b/libsolidity/Parser.cpp @@ -785,12 +785,13 @@ ASTPointer Parser::parseVariableDeclarationStateme // Parse `var (a, b, ,, c) = ...` into a single VariableDeclarationStatement with multiple variables. m_scanner->next(); m_scanner->next(); - do + while (true) { ASTPointer var; - if (m_scanner->currentToken() == Token::Comma) - m_scanner->next(); - else + if ( + m_scanner->currentToken() != Token::Comma && + m_scanner->currentToken() != Token::RParen + ) { ASTNodeFactory varDeclNodeFactory(*this); ASTPointer name = expectIdentifierToken(); @@ -802,7 +803,11 @@ ASTPointer Parser::parseVariableDeclarationStateme ); } variables.push_back(var); - } while (m_scanner->currentToken() != Token::RParen); + if (m_scanner->currentToken() == Token::RParen) + break; + else + expectToken(Token::Comma); + } nodeFactory.markEndPosition(); m_scanner->next(); } diff --git a/libsolidity/TypeChecker.cpp b/libsolidity/TypeChecker.cpp index b1007276..db13e6f2 100644 --- a/libsolidity/TypeChecker.cpp +++ b/libsolidity/TypeChecker.cpp @@ -617,47 +617,57 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) // the variable declaration(s). _statement.initialValue()->accept(*this); - shared_ptr valueType = dynamic_pointer_cast(_statement.initialValue()->annotation().type); - if (!valueType) - valueType = make_shared(TypePointers{_statement.initialValue()->annotation().type}); + TypePointers valueTypes; + if (auto tupleType = dynamic_cast(_statement.initialValue()->annotation().type.get())) + valueTypes = tupleType->components(); + else + valueTypes = TypePointers{_statement.initialValue()->annotation().type}; - vector> variables = _statement.declarations(); + // Determine which component is assigned to which variable. // If numbers do not match, fill up if variables begin or end empty (not both). - if (valueType->components().size() != variables.size()) - { - if (!variables.front() && !variables.back()) - fatalTypeError( - _statement, - "Wildcard both at beginning and end of variable declaration list is only allowed " - "if the number of components is equal." - ); - while (valueType->components().size() > variables.size()) - if (!variables.front()) - variables.insert(variables.begin(), shared_ptr()); - else - variables.push_back(shared_ptr()); - while (valueType->components().size() < variables.size()) - if (!variables.empty() && !variables.front()) - variables.erase(variables.begin()); - else if (!variables.empty() && !variables.back()) - variables.pop_back(); - else - break; - if (valueType->components().size() != variables.size()) - fatalTypeError( - _statement, - "Unable to match the number of variables to the number of values." - ); - } - solAssert(variables.size() == valueType->components().size(), ""); + vector& assignments = _statement.annotation().assignments; + assignments.resize(valueTypes.size(), nullptr); + vector> const& variables = _statement.declarations(); + if (valueTypes.size() != variables.size() && !variables.front() && !variables.back()) + fatalTypeError( + _statement, + "Wildcard both at beginning and end of variable declaration list is only allowed " + "if the number of components is equal." + ); + size_t minNumValues = variables.size(); + if (!variables.back() || !variables.front()) + --minNumValues; + if (valueTypes.size() < minNumValues) + fatalTypeError( + _statement, + "Not enough components (" + + toString(valueTypes.size()) + + ") in value to assign all variables (" + + toString(minNumValues) + ")." + ); + if (valueTypes.size() > variables.size() && variables.front() && variables.back()) + fatalTypeError( + _statement, + "Too many components (" + + toString(valueTypes.size()) + + ") in value for variable assignment (" + + toString(minNumValues) + + " needed)." + ); + bool fillRight = (!variables.back() || variables.front()); + for (size_t i = 0; i < min(variables.size(), valueTypes.size()); ++i) + if (fillRight) + assignments[i] = variables[i].get(); + else + assignments[assignments.size() - i - 1] = variables[variables.size() - i - 1].get(); - for (size_t i = 0; i < variables.size(); ++i) + for (size_t i = 0; i < assignments.size(); ++i) { - if (!variables[i]) + if (!assignments[i]) continue; - VariableDeclaration const& var = *variables[i]; + VariableDeclaration const& var = *assignments[i]; solAssert(!var.value(), "Value has to be tied to statement."); - TypePointer const& valueComponentType = valueType->components()[i]; + TypePointer const& valueComponentType = valueTypes[i]; solAssert(!!valueComponentType, ""); if (!var.annotation().type) { diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index fc6d54a8..4316c6a8 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -2405,6 +2405,65 @@ BOOST_AUTO_TEST_CASE(multi_variable_declaration_fail) SOLIDITY_CHECK_ERROR_TYPE(parseAndAnalyseReturnError(text), TypeError); } +BOOST_AUTO_TEST_CASE(multi_variable_declaration_wildcards_fine) +{ + char const* text = R"( + contract C { + function three() returns (uint, uint, uint); + function two() returns (uint, uint); + function f() { + var (a,) = three(); + var (b,c,) = two(); + var (,d) = three(); + var (,e,g) = two(); + } + )"; + BOOST_CHECK_NO_THROW(parseAndAnalyseReturnError(text)); +} + +BOOST_AUTO_TEST_CASE(multi_variable_declaration_wildcards_fail_1) +{ + char const* text = R"( + contract C { + function one() returns (uint); + function f() { var (a, b, ) = one(); } + } + )"; + SOLIDITY_CHECK_ERROR_TYPE(parseAndAnalyseReturnError(text), TypeError); +} +BOOST_AUTO_TEST_CASE(multi_variable_declaration_wildcards_fail_2) +{ + char const* text = R"( + contract C { + function one() returns (uint); + function f() { var (a, , ) = one(); } + } + )"; + SOLIDITY_CHECK_ERROR_TYPE(parseAndAnalyseReturnError(text), TypeError); +} + +BOOST_AUTO_TEST_CASE(multi_variable_declaration_wildcards_fail_3) +{ + char const* text = R"( + contract C { + function one() returns (uint); + function f() { var (, , a) = one(); } + } + )"; + SOLIDITY_CHECK_ERROR_TYPE(parseAndAnalyseReturnError(text), TypeError); +} + +BOOST_AUTO_TEST_CASE(multi_variable_declaration_wildcards_fail_4) +{ + char const* text = R"( + contract C { + function one() returns (uint); + function f() { var (, a, b) = one(); } + } + )"; + SOLIDITY_CHECK_ERROR_TYPE(parseAndAnalyseReturnError(text), TypeError); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityParser.cpp b/test/libsolidity/SolidityParser.cpp index 0e69bfa8..569530b9 100644 --- a/test/libsolidity/SolidityParser.cpp +++ b/test/libsolidity/SolidityParser.cpp @@ -952,18 +952,6 @@ BOOST_AUTO_TEST_CASE(multi_variable_declaration) BOOST_CHECK_NO_THROW(parseText(text)); } -BOOST_AUTO_TEST_CASE(multi_variable_declaration_invalid) -{ - char const* text = R"( - library Lib { - function f() { - var () = g(); - } - } - )"; - BOOST_CHECK_THROW(parseText(text), ParserError); -} - BOOST_AUTO_TEST_SUITE_END() } -- cgit v1.2.3