From 7ebd536e79215f06f5ce7e14591aa494d06032b6 Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 12 Oct 2015 23:02:35 +0200 Subject: Tuple expressions. --- libsolidity/AST.h | 24 +++++++++++++ libsolidity/ASTForward.h | 1 + libsolidity/ASTJsonConverter.cpp | 11 ++++++ libsolidity/ASTJsonConverter.h | 2 ++ libsolidity/ASTPrinter.cpp | 13 +++++++ libsolidity/ASTPrinter.h | 2 ++ libsolidity/ASTVisitor.h | 4 +++ libsolidity/AST_accept.h | 18 ++++++++++ libsolidity/Compiler.cpp | 1 + libsolidity/Parser.cpp | 20 +++++++++-- libsolidity/TypeChecker.cpp | 75 ++++++++++++++++++++++++++++++++++++---- libsolidity/TypeChecker.h | 1 + libsolidity/Types.cpp | 39 ++++++++++++++++----- libsolidity/Types.h | 2 ++ 14 files changed, 196 insertions(+), 17 deletions(-) (limited to 'libsolidity') diff --git a/libsolidity/AST.h b/libsolidity/AST.h index 9dd67cc2..fc1db3f3 100644 --- a/libsolidity/AST.h +++ b/libsolidity/AST.h @@ -1067,6 +1067,30 @@ private: ASTPointer m_rightHandSide; }; +/** + * Tuple or just parenthesized expression. + * Examples: (1, 2), (x,), (x), () + * Individual components might be empty shared pointers (as in the second example). + * The respective types in lvalue context are: 2-tuple, 2-tuple (with wildcard), type of x, 0-tuple + * Not in lvalue context: 2-tuple, _1_-tuple, type of x, 0-tuple. + */ +class TupleExpression: public Expression +{ +public: + TupleExpression( + SourceLocation const& _location, + std::vector> const& _components + ): + Expression(_location), m_components(_components) {} + virtual void accept(ASTVisitor& _visitor) override; + virtual void accept(ASTConstVisitor& _visitor) const override; + + std::vector> const& components() const { return m_components; } + +private: + std::vector> m_components; +}; + /** * Operation involving a unary operator, pre- or postfix. * Examples: ++i, delete x or !true diff --git a/libsolidity/ASTForward.h b/libsolidity/ASTForward.h index 396cf50a..02dd054a 100644 --- a/libsolidity/ASTForward.h +++ b/libsolidity/ASTForward.h @@ -69,6 +69,7 @@ class VariableDeclarationStatement; class ExpressionStatement; class Expression; class Assignment; +class TupleExpression; class UnaryOperation; class BinaryOperation; class FunctionCall; diff --git a/libsolidity/ASTJsonConverter.cpp b/libsolidity/ASTJsonConverter.cpp index 4c14f2b2..34012c73 100644 --- a/libsolidity/ASTJsonConverter.cpp +++ b/libsolidity/ASTJsonConverter.cpp @@ -226,6 +226,12 @@ bool ASTJsonConverter::visit(Assignment const& _node) return true; } +bool ASTJsonConverter::visit(TupleExpression const&) +{ + addJsonNode("TupleExpression",{}, true); + return true; +} + bool ASTJsonConverter::visit(UnaryOperation const& _node) { addJsonNode("UnaryOperation", @@ -396,6 +402,11 @@ void ASTJsonConverter::endVisit(Assignment const&) goUp(); } +void ASTJsonConverter::endVisit(TupleExpression const&) +{ + goUp(); +} + void ASTJsonConverter::endVisit(UnaryOperation const&) { goUp(); diff --git a/libsolidity/ASTJsonConverter.h b/libsolidity/ASTJsonConverter.h index 61f87860..a62259e2 100644 --- a/libsolidity/ASTJsonConverter.h +++ b/libsolidity/ASTJsonConverter.h @@ -68,6 +68,7 @@ public: bool visit(VariableDeclarationStatement const& _node) override; bool visit(ExpressionStatement const& _node) override; bool visit(Assignment const& _node) override; + bool visit(TupleExpression const& _node) override; bool visit(UnaryOperation const& _node) override; bool visit(BinaryOperation const& _node) override; bool visit(FunctionCall const& _node) override; @@ -99,6 +100,7 @@ public: void endVisit(VariableDeclarationStatement const&) override; void endVisit(ExpressionStatement const&) override; void endVisit(Assignment const&) override; + void endVisit(TupleExpression const&) override; void endVisit(UnaryOperation const&) override; void endVisit(BinaryOperation const&) override; void endVisit(FunctionCall const&) override; diff --git a/libsolidity/ASTPrinter.cpp b/libsolidity/ASTPrinter.cpp index 534f7c78..cb231842 100644 --- a/libsolidity/ASTPrinter.cpp +++ b/libsolidity/ASTPrinter.cpp @@ -256,6 +256,14 @@ bool ASTPrinter::visit(Assignment const& _node) return goDeeper(); } +bool ASTPrinter::visit(TupleExpression const& _node) +{ + writeLine(string("TupleExpression")); + printType(_node); + printSourcePart(_node); + return goDeeper(); +} + bool ASTPrinter::visit(UnaryOperation const& _node) { writeLine(string("UnaryOperation (") + (_node.isPrefixOperation() ? "prefix" : "postfix") + @@ -477,6 +485,11 @@ void ASTPrinter::endVisit(Assignment const&) m_indentation--; } +void ASTPrinter::endVisit(TupleExpression const&) +{ + m_indentation--; +} + void ASTPrinter::endVisit(UnaryOperation const&) { m_indentation--; diff --git a/libsolidity/ASTPrinter.h b/libsolidity/ASTPrinter.h index a12ec0aa..95656436 100644 --- a/libsolidity/ASTPrinter.h +++ b/libsolidity/ASTPrinter.h @@ -76,6 +76,7 @@ public: bool visit(VariableDeclarationStatement const& _node) override; bool visit(ExpressionStatement const& _node) override; bool visit(Assignment const& _node) override; + bool visit(TupleExpression const& _node) override; bool visit(UnaryOperation const& _node) override; bool visit(BinaryOperation const& _node) override; bool visit(FunctionCall const& _node) override; @@ -115,6 +116,7 @@ public: void endVisit(VariableDeclarationStatement const&) override; void endVisit(ExpressionStatement const&) override; void endVisit(Assignment const&) override; + void endVisit(TupleExpression const&) override; void endVisit(UnaryOperation const&) override; void endVisit(BinaryOperation const&) override; void endVisit(FunctionCall const&) override; diff --git a/libsolidity/ASTVisitor.h b/libsolidity/ASTVisitor.h index e665396c..3e50fb28 100644 --- a/libsolidity/ASTVisitor.h +++ b/libsolidity/ASTVisitor.h @@ -73,6 +73,7 @@ public: virtual bool visit(VariableDeclarationStatement& _node) { return visitNode(_node); } virtual bool visit(ExpressionStatement& _node) { return visitNode(_node); } virtual bool visit(Assignment& _node) { return visitNode(_node); } + virtual bool visit(TupleExpression& _node) { return visitNode(_node); } virtual bool visit(UnaryOperation& _node) { return visitNode(_node); } virtual bool visit(BinaryOperation& _node) { return visitNode(_node); } virtual bool visit(FunctionCall& _node) { return visitNode(_node); } @@ -113,6 +114,7 @@ public: virtual void endVisit(VariableDeclarationStatement& _node) { endVisitNode(_node); } virtual void endVisit(ExpressionStatement& _node) { endVisitNode(_node); } virtual void endVisit(Assignment& _node) { endVisitNode(_node); } + virtual void endVisit(TupleExpression& _node) { endVisitNode(_node); } virtual void endVisit(UnaryOperation& _node) { endVisitNode(_node); } virtual void endVisit(BinaryOperation& _node) { endVisitNode(_node); } virtual void endVisit(FunctionCall& _node) { endVisitNode(_node); } @@ -165,6 +167,7 @@ public: virtual bool visit(VariableDeclarationStatement const& _node) { return visitNode(_node); } virtual bool visit(ExpressionStatement const& _node) { return visitNode(_node); } virtual bool visit(Assignment const& _node) { return visitNode(_node); } + virtual bool visit(TupleExpression const& _node) { return visitNode(_node); } virtual bool visit(UnaryOperation const& _node) { return visitNode(_node); } virtual bool visit(BinaryOperation const& _node) { return visitNode(_node); } virtual bool visit(FunctionCall const& _node) { return visitNode(_node); } @@ -205,6 +208,7 @@ public: virtual void endVisit(VariableDeclarationStatement const& _node) { endVisitNode(_node); } virtual void endVisit(ExpressionStatement const& _node) { endVisitNode(_node); } virtual void endVisit(Assignment const& _node) { endVisitNode(_node); } + virtual void endVisit(TupleExpression const& _node) { endVisitNode(_node); } virtual void endVisit(UnaryOperation const& _node) { endVisitNode(_node); } virtual void endVisit(BinaryOperation const& _node) { endVisitNode(_node); } virtual void endVisit(FunctionCall const& _node) { endVisitNode(_node); } diff --git a/libsolidity/AST_accept.h b/libsolidity/AST_accept.h index 994bfc8d..eb1f6098 100644 --- a/libsolidity/AST_accept.h +++ b/libsolidity/AST_accept.h @@ -559,6 +559,24 @@ void Assignment::accept(ASTConstVisitor& _visitor) const _visitor.endVisit(*this); } +void TupleExpression::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + for (auto const& component: m_components) + if (component) + component->accept(_visitor); + _visitor.endVisit(*this); +} + +void TupleExpression::accept(ASTConstVisitor& _visitor) const +{ + if (_visitor.visit(*this)) + for (auto const& component: m_components) + if (component) + component->accept(_visitor); + _visitor.endVisit(*this); +} + void UnaryOperation::accept(ASTVisitor& _visitor) { if (_visitor.visit(*this)) diff --git a/libsolidity/Compiler.cpp b/libsolidity/Compiler.cpp index 7c6c7831..cc1228a1 100644 --- a/libsolidity/Compiler.cpp +++ b/libsolidity/Compiler.cpp @@ -637,6 +637,7 @@ bool Compiler::visit(VariableDeclarationStatement const& _variableDeclarationSta for (size_t i = 0; i < assignments.size(); ++i) { size_t j = assignments.size() - i - 1; + solAssert(!!valueTypes[j], ""); VariableDeclaration const* varDecl = assignments[j]; if (!varDecl) utils.popStackElement(*valueTypes[j]); diff --git a/libsolidity/Parser.cpp b/libsolidity/Parser.cpp index ab2d0094..1bb16b84 100644 --- a/libsolidity/Parser.cpp +++ b/libsolidity/Parser.cpp @@ -806,6 +806,7 @@ ASTPointer Parser::parseVariableDeclarationStateme ) { ASTNodeFactory varDeclNodeFactory(*this); + varDeclNodeFactory.markEndPosition(); ASTPointer name = expectIdentifierToken(); var = varDeclNodeFactory.createNode( ASTPointer(), @@ -1009,10 +1010,25 @@ ASTPointer Parser::parsePrimaryExpression() break; case Token::LParen: { + // Tuple or parenthesized expression. + // Special cases: () is empty tuple type, (x) is not a real tuple, (x,) is one-dimensional tuple m_scanner->next(); - ASTPointer expression = parseExpression(); + vector> components; + if (m_scanner->currentToken() != Token::RParen) + while (true) + { + if (m_scanner->currentToken() != Token::Comma && m_scanner->currentToken() != Token::RParen) + components.push_back(parseExpression()); + else + components.push_back(ASTPointer()); + if (m_scanner->currentToken() == Token::RParen) + break; + else if (m_scanner->currentToken() == Token::Comma) + m_scanner->next(); + } + nodeFactory.markEndPosition(); expectToken(Token::RParen); - return expression; + return nodeFactory.createNode(components); } default: if (Token::isElementaryTypeName(token)) diff --git a/libsolidity/TypeChecker.cpp b/libsolidity/TypeChecker.cpp index ca5b1eb7..c7b693bb 100644 --- a/libsolidity/TypeChecker.cpp +++ b/libsolidity/TypeChecker.cpp @@ -560,13 +560,31 @@ void TypeChecker::endVisit(Return const& _return) return; ParameterList const* params = _return.annotation().functionReturnParameters; if (!params) + { typeError(_return, "Return arguments not allowed."); + return; + } + TypePointers returnTypes; + for (auto const& var: params->parameters()) + returnTypes.push_back(type(*var)); + if (auto tupleType = dynamic_cast(type(*_return.expression()).get())) + { + if (tupleType->components().size() != params->parameters().size()) + typeError(_return, "Different number of arguments in return statement than in returns declaration."); + else if (!tupleType->isImplicitlyConvertibleTo(TupleType(returnTypes))) + typeError( + *_return.expression(), + "Return argument type " + + type(*_return.expression())->toString() + + " is not implicitly convertible to expected type " + + TupleType(returnTypes).toString(false) + + "." + ); + } else if (params->parameters().size() != 1) typeError(_return, "Different number of arguments in return statement than in returns declaration."); else { - // this could later be changed such that the paramaters type is an anonymous struct type, - // but for now, we only allow one return parameter TypePointer const& expected = type(*params->parameters().front()); if (!type(*_return.expression())->isImplicitlyConvertibleTo(*expected)) typeError( @@ -590,7 +608,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) VariableDeclaration const& varDecl = *_statement.declarations().front(); if (!varDecl.annotation().type) fatalTypeError(_statement, "Assignment necessary for type detection."); - if (auto ref = dynamic_cast(varDecl.annotation().type.get())) + if (auto ref = dynamic_cast(type(varDecl).get())) { if (ref->dataStoredIn(DataLocation::Storage)) { @@ -610,10 +628,10 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) _statement.initialValue()->accept(*this); TypePointers valueTypes; - if (auto tupleType = dynamic_cast(_statement.initialValue()->annotation().type.get())) + if (auto tupleType = dynamic_cast(type(*_statement.initialValue()).get())) valueTypes = tupleType->components(); else - valueTypes = TypePointers{_statement.initialValue()->annotation().type}; + valueTypes = TypePointers{type(*_statement.initialValue())}; // Determine which component is assigned to which variable. // If numbers do not match, fill up if variables begin or end empty (not both). @@ -741,6 +759,51 @@ bool TypeChecker::visit(Assignment const& _assignment) return false; } +bool TypeChecker::visit(TupleExpression const& _tuple) +{ + vector> const& components = _tuple.components(); + TypePointers types; + if (_tuple.annotation().lValueRequested) + { + for (auto const& component: components) + if (component) + { + requireLValue(*component); + types.push_back(type(*component)); + } + else + types.push_back(TypePointer()); + _tuple.annotation().type = make_shared(types); + // If some of the components are not LValues, the error is reported above. + _tuple.annotation().isLValue = true; + } + else + { + for (size_t i = 0; i < components.size(); ++i) + { + // Outside of an lvalue-context, the only situation where a component can be empty is (x,). + if (!components[i] && !(i == 1 && components.size() == 2)) + fatalTypeError(_tuple, "Tuple component cannot be empty."); + else if (components[i]) + { + components[i]->accept(*this); + types.push_back(type(*components[i])); + } + else + types.push_back(TypePointer()); + } + if (components.size() == 1) + _tuple.annotation().type = type(*components[0]); + else + { + if (components.size() == 2 && !components[1]) + types.pop_back(); + _tuple.annotation().type = make_shared(types); + } + } + return false; +} + bool TypeChecker::visit(UnaryOperation const& _operation) { // Inc, Dec, Add, Sub, Not, BitNot, Delete @@ -1236,10 +1299,10 @@ void TypeChecker::expectType(Expression const& _expression, Type const& _expecte void TypeChecker::requireLValue(Expression const& _expression) { + _expression.annotation().lValueRequested = true; _expression.accept(*this); if (!_expression.annotation().isLValue) typeError(_expression, "Expression has to be an lvalue."); - _expression.annotation().lValueRequested = true; } void TypeChecker::typeError(ASTNode const& _node, string const& _description) diff --git a/libsolidity/TypeChecker.h b/libsolidity/TypeChecker.h index d9cb39ae..7af5473b 100644 --- a/libsolidity/TypeChecker.h +++ b/libsolidity/TypeChecker.h @@ -90,6 +90,7 @@ private: virtual bool visit(VariableDeclarationStatement const& _variable) override; virtual void endVisit(ExpressionStatement const& _statement) override; virtual bool visit(Assignment const& _assignment) override; + virtual bool visit(TupleExpression const& _tuple) override; virtual void endVisit(BinaryOperation const& _operation) override; virtual bool visit(UnaryOperation const& _operation) override; virtual bool visit(FunctionCall const& _functionCall) override; diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp index 51df5fbf..d7beab26 100644 --- a/libsolidity/Types.cpp +++ b/libsolidity/Types.cpp @@ -1256,8 +1256,8 @@ string TupleType::toString(bool _short) const return "tuple()"; string str = "tuple("; for (auto const& t: m_components) - str += t->toString(_short) + ", "; - str.resize(str.size() - 2); + str += (t ? t->toString(_short) : "") + ","; + str.pop_back(); return str + ")"; } @@ -1273,10 +1273,30 @@ unsigned TupleType::sizeOnStack() const { unsigned size = 0; for (auto const& t: m_components) - size += t->sizeOnStack(); + size += t ? t->sizeOnStack() : 0; return size; } +bool TupleType::isImplicitlyConvertibleTo(Type const& _other) const +{ + if (auto tupleType = dynamic_cast(&_other)) + { + if (components().size() != tupleType->components().size()) + return false; + for (size_t i = 0; i < components().size(); ++i) + if ((!components()[i]) != (!tupleType->components()[i])) + return false; + else if ( + components()[i] && + !components()[i]->isImplicitlyConvertibleTo(*tupleType->components()[i]) + ) + return false; + return true; + } + else + return false; +} + FunctionType::FunctionType(FunctionDefinition const& _function, bool _isInternal): m_location(_isInternal ? Location::Internal : Location::External), m_isConstant(_function.isDeclaredConst()), @@ -1638,14 +1658,15 @@ FunctionTypePointer FunctionType::asMemberFunction(bool _inLibrary) const parameterTypes.push_back(t); } - //@todo make this more intelligent once we support destructuring assignments + // Removes dynamic types. TypePointers returnParameterTypes; vector returnParameterNames; - if (!m_returnParameterTypes.empty() && m_returnParameterTypes.front()->calldataEncodedSize() > 0) - { - returnParameterTypes.push_back(m_returnParameterTypes.front()); - returnParameterNames.push_back(m_returnParameterNames.front()); - } + for (size_t i = 0; i < m_returnParameterTypes.size(); ++i) + if (m_returnParameterTypes[i]->calldataEncodedSize() > 0) + { + returnParameterTypes.push_back(m_returnParameterTypes[i]); + returnParameterNames.push_back(m_returnParameterNames[i]); + } return make_shared( parameterTypes, returnParameterTypes, diff --git a/libsolidity/Types.h b/libsolidity/Types.h index 5c4aacdc..e8d65c41 100644 --- a/libsolidity/Types.h +++ b/libsolidity/Types.h @@ -682,6 +682,7 @@ private: /** * Type that can hold a finite sequence of values of different types. + * In some cases, the components are empty pointers (when used as placeholders). */ class TupleType: public Type { @@ -695,6 +696,7 @@ public: virtual u256 storageSize() const override; virtual bool canLiveOutsideStorage() const override { return false; } virtual unsigned sizeOnStack() const override; + virtual bool isImplicitlyConvertibleTo(Type const& _other) const override; std::vector const& components() const { return m_components; } -- cgit From 039b2a764f3944768bb253102f4c4b788f2dca9c Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 14 Oct 2015 15:19:50 +0200 Subject: Destructuring assignments. --- libsolidity/Compiler.cpp | 15 ++- libsolidity/CompilerUtils.cpp | 63 ++++++++++-- libsolidity/CompilerUtils.h | 9 +- libsolidity/ExpressionCompiler.cpp | 41 +++++--- libsolidity/ExpressionCompiler.h | 1 + libsolidity/LValue.cpp | 195 +++++++++++++++++++++++++++---------- libsolidity/LValue.h | 31 +++++- libsolidity/TypeChecker.cpp | 8 +- libsolidity/Types.cpp | 72 ++++++++++---- libsolidity/Types.h | 12 ++- 10 files changed, 347 insertions(+), 100 deletions(-) (limited to 'libsolidity') diff --git a/libsolidity/Compiler.cpp b/libsolidity/Compiler.cpp index cc1228a1..679704ba 100644 --- a/libsolidity/Compiler.cpp +++ b/libsolidity/Compiler.cpp @@ -597,13 +597,20 @@ bool Compiler::visit(Break const& _breakStatement) bool Compiler::visit(Return const& _return) { CompilerContext::LocationSetter locationSetter(m_context, _return); - //@todo modifications are needed to make this work with functions returning multiple values if (Expression const* expression = _return.expression()) { solAssert(_return.annotation().functionReturnParameters, "Invalid return parameters pointer."); - VariableDeclaration const& firstVariable = *_return.annotation().functionReturnParameters->parameters().front(); - compileExpression(*expression, firstVariable.annotation().type); - CompilerUtils(m_context).moveToStackVariable(firstVariable); + vector> const& returnParameters = + _return.annotation().functionReturnParameters->parameters(); + TypePointers types; + for (auto const& retVariable: returnParameters) + types.push_back(retVariable->annotation().type); + + TypePointer expectedType = types.size() == 1 ? types.front() : make_shared(types); + compileExpression(*expression, expectedType); + + for (auto const& retVariable: boost::adaptors::reverse(returnParameters)) + CompilerUtils(m_context).moveToStackVariable(*retVariable); } for (unsigned i = 0; i < m_stackCleanupForReturn; ++i) m_context << eth::Instruction::POP; diff --git a/libsolidity/CompilerUtils.cpp b/libsolidity/CompilerUtils.cpp index e1152202..34bb08ba 100644 --- a/libsolidity/CompilerUtils.cpp +++ b/libsolidity/CompilerUtils.cpp @@ -550,6 +550,55 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp } break; } + case Type::Category::Tuple: + { + //@TODO wildcards + TupleType const& sourceTuple = dynamic_cast(_typeOnStack); + TupleType const& targetTuple = dynamic_cast(_targetType); + solAssert(sourceTuple.components().size() == targetTuple.components().size(), ""); + unsigned depth = sourceTuple.sizeOnStack(); + for (size_t i = 0; i < sourceTuple.components().size(); ++i) + { + TypePointer const& sourceType = sourceTuple.components()[i]; + TypePointer const& targetType = targetTuple.components()[i]; + if (!sourceType) + { + solAssert(!targetType, ""); + continue; + } + unsigned sourceSize = sourceType->sizeOnStack(); + unsigned targetSize = targetType->sizeOnStack(); + if (*sourceType != *targetType || _cleanupNeeded) + { + if (sourceSize > 0) + copyToStackTop(depth, sourceSize); + + convertType(*sourceType, *targetType, _cleanupNeeded); + + if (sourceSize > 0 || targetSize > 0) + { + // Move it back into its place. + for (unsigned j = 0; j < min(sourceSize, targetSize); ++j) + m_context << + eth::swapInstruction(depth + targetSize - sourceSize) << + eth::Instruction::POP; + if (targetSize < sourceSize) + moveToStackTop(sourceSize - targetSize, depth ); + // Value shrank + for (unsigned j = targetSize; j < sourceSize; ++j) + { + moveToStackTop(depth - 1, 1); + m_context << eth::Instruction::POP; + } + // Value grew + if (targetSize > sourceSize) + moveIntoStack(depth + targetSize - sourceSize, targetSize - sourceSize); + } + } + depth -= sourceSize; + } + break; + } default: // All other types should not be convertible to non-equal types. solAssert(_typeOnStack == _targetType, "Invalid type conversion requested."); @@ -631,18 +680,20 @@ void CompilerUtils::copyToStackTop(unsigned _stackDepth, unsigned _itemSize) m_context << eth::dupInstruction(_stackDepth); } -void CompilerUtils::moveToStackTop(unsigned _stackDepth) +void CompilerUtils::moveToStackTop(unsigned _stackDepth, unsigned _itemSize) { solAssert(_stackDepth <= 15, "Stack too deep, try removing local variables."); - for (unsigned i = 0; i < _stackDepth; ++i) - m_context << eth::swapInstruction(1 + i); + for (unsigned j = 0; j < _itemSize; ++j) + for (unsigned i = 0; i < _stackDepth + _itemSize - 1; ++i) + m_context << eth::swapInstruction(1 + i); } -void CompilerUtils::moveIntoStack(unsigned _stackDepth) +void CompilerUtils::moveIntoStack(unsigned _stackDepth, unsigned _itemSize) { solAssert(_stackDepth <= 16, "Stack too deep, try removing local variables."); - for (unsigned i = _stackDepth; i > 0; --i) - m_context << eth::swapInstruction(i); + for (unsigned j = 0; j < _itemSize; ++j) + for (unsigned i = _stackDepth; i > 0; --i) + m_context << eth::swapInstruction(i + _itemSize - 1); } void CompilerUtils::popStackElement(Type const& _type) diff --git a/libsolidity/CompilerUtils.h b/libsolidity/CompilerUtils.h index f335eed5..01b9f422 100644 --- a/libsolidity/CompilerUtils.h +++ b/libsolidity/CompilerUtils.h @@ -124,10 +124,11 @@ public: /// Copies an item that occupies @a _itemSize stack slots from a stack depth of @a _stackDepth /// to the top of the stack. void copyToStackTop(unsigned _stackDepth, unsigned _itemSize); - /// Moves a single stack element (with _stackDepth items on top of it) to the top of the stack. - void moveToStackTop(unsigned _stackDepth); - /// Moves a single stack element past @a _stackDepth other stack elements - void moveIntoStack(unsigned _stackDepth); + /// Moves an item that occupies @a _itemSize stack slots and has items occupying @a _stackDepth + /// slots above it to the top of the stack. + void moveToStackTop(unsigned _stackDepth, unsigned _itemSize = 1); + /// Moves @a _itemSize elements past @a _stackDepth other stack elements + void moveIntoStack(unsigned _stackDepth, unsigned _itemSize = 1); /// Removes the current value from the top of the stack. void popStackElement(Type const& _type); /// Removes element from the top of the stack _amount times. diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp index 85302afc..8109c03b 100644 --- a/libsolidity/ExpressionCompiler.cpp +++ b/libsolidity/ExpressionCompiler.cpp @@ -177,20 +177,18 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& bool ExpressionCompiler::visit(Assignment const& _assignment) { +// cout << "-----Assignment" << endl; CompilerContext::LocationSetter locationSetter(m_context, _assignment); _assignment.rightHandSide().accept(*this); - TypePointer type = _assignment.rightHandSide().annotation().type; - if (!_assignment.annotation().type->dataStoredIn(DataLocation::Storage)) - { - utils().convertType(*type, *_assignment.annotation().type); - type = _assignment.annotation().type; - } - else - { - utils().convertType(*type, *type->mobileType()); - type = type->mobileType(); - } + // Perform some conversion already. This will convert storage types to memory and literals + // to their actual type, but will not convert e.g. memory to storage. + TypePointer type = _assignment.rightHandSide().annotation().type->closestTemporaryType( + _assignment.leftHandSide().annotation().type + ); +// cout << "-----Type conversion" << endl; + utils().convertType(*_assignment.rightHandSide().annotation().type, *type); +// cout << "-----LHS" << endl; _assignment.leftHandSide().accept(*this); solAssert(!!m_currentLValue, "LValue not retrieved."); @@ -216,11 +214,32 @@ bool ExpressionCompiler::visit(Assignment const& _assignment) m_context << eth::swapInstruction(itemSize + lvalueSize) << eth::Instruction::POP; } } +// cout << "-----Store" << endl; m_currentLValue->storeValue(*type, _assignment.location()); m_currentLValue.reset(); return false; } +bool ExpressionCompiler::visit(TupleExpression const& _tuple) +{ + vector> lvalues; + for (auto const& component: _tuple.components()) + if (component) + { + component->accept(*this); + if (_tuple.annotation().lValueRequested) + { + solAssert(!!m_currentLValue, ""); + lvalues.push_back(move(m_currentLValue)); + } + } + else if (_tuple.annotation().lValueRequested) + lvalues.push_back(unique_ptr()); + if (_tuple.annotation().lValueRequested) + m_currentLValue.reset(new TupleObject(m_context, move(lvalues))); + return false; +} + bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation) { CompilerContext::LocationSetter locationSetter(m_context, _unaryOperation); diff --git a/libsolidity/ExpressionCompiler.h b/libsolidity/ExpressionCompiler.h index d8fef5af..44d27ea2 100644 --- a/libsolidity/ExpressionCompiler.h +++ b/libsolidity/ExpressionCompiler.h @@ -72,6 +72,7 @@ public: private: virtual bool visit(Assignment const& _assignment) override; + virtual bool visit(TupleExpression const& _tuple) override; virtual bool visit(UnaryOperation const& _unaryOperation) override; virtual bool visit(BinaryOperation const& _binaryOperation) override; virtual bool visit(FunctionCall const& _functionCall) override; diff --git a/libsolidity/LValue.cpp b/libsolidity/LValue.cpp index 9f33e846..20aa59e1 100644 --- a/libsolidity/LValue.cpp +++ b/libsolidity/LValue.cpp @@ -32,9 +32,9 @@ using namespace solidity; StackVariable::StackVariable(CompilerContext& _compilerContext, VariableDeclaration const& _declaration): - LValue(_compilerContext, *_declaration.annotation().type), + LValue(_compilerContext, _declaration.annotation().type.get()), m_baseStackOffset(m_context.baseStackOffsetOfVariable(_declaration)), - m_size(m_dataType.sizeOnStack()) + m_size(m_dataType->sizeOnStack()) { } @@ -70,23 +70,23 @@ void StackVariable::storeValue(Type const&, SourceLocation const& _location, boo void StackVariable::setToZero(SourceLocation const& _location, bool) const { - CompilerUtils(m_context).pushZeroValue(m_dataType); - storeValue(m_dataType, _location, true); + CompilerUtils(m_context).pushZeroValue(*m_dataType); + storeValue(*m_dataType, _location, true); } MemoryItem::MemoryItem(CompilerContext& _compilerContext, Type const& _type, bool _padded): - LValue(_compilerContext, _type), + LValue(_compilerContext, &_type), m_padded(_padded) { } void MemoryItem::retrieveValue(SourceLocation const&, bool _remove) const { - if (m_dataType.isValueType()) + if (m_dataType->isValueType()) { if (!_remove) m_context << eth::Instruction::DUP1; - CompilerUtils(m_context).loadFromMemoryDynamic(m_dataType, false, m_padded, false); + CompilerUtils(m_context).loadFromMemoryDynamic(*m_dataType, false, m_padded, false); } else m_context << eth::Instruction::MLOAD; @@ -95,24 +95,24 @@ void MemoryItem::retrieveValue(SourceLocation const&, bool _remove) const void MemoryItem::storeValue(Type const& _sourceType, SourceLocation const&, bool _move) const { CompilerUtils utils(m_context); - if (m_dataType.isValueType()) + if (m_dataType->isValueType()) { solAssert(_sourceType.isValueType(), ""); utils.moveIntoStack(_sourceType.sizeOnStack()); - utils.convertType(_sourceType, m_dataType, true); + utils.convertType(_sourceType, *m_dataType, true); if (!_move) { - utils.moveToStackTop(m_dataType.sizeOnStack()); - utils.copyToStackTop(2, m_dataType.sizeOnStack()); + utils.moveToStackTop(m_dataType->sizeOnStack()); + utils.copyToStackTop(2, m_dataType->sizeOnStack()); } - utils.storeInMemoryDynamic(m_dataType, m_padded); + utils.storeInMemoryDynamic(*m_dataType, m_padded); m_context << eth::Instruction::POP; } else { - solAssert(_sourceType == m_dataType, "Conversion not implemented for assignment to memory."); + solAssert(_sourceType == *m_dataType, "Conversion not implemented for assignment to memory."); - solAssert(m_dataType.sizeOnStack() == 1, ""); + solAssert(m_dataType->sizeOnStack() == 1, ""); if (!_move) m_context << eth::Instruction::DUP2 << eth::Instruction::SWAP1; // stack: [value] value lvalue @@ -126,8 +126,8 @@ void MemoryItem::setToZero(SourceLocation const&, bool _removeReference) const CompilerUtils utils(m_context); if (!_removeReference) m_context << eth::Instruction::DUP1; - utils.pushZeroValue(m_dataType); - utils.storeInMemoryDynamic(m_dataType, m_padded); + utils.pushZeroValue(*m_dataType); + utils.storeInMemoryDynamic(*m_dataType, m_padded); m_context << eth::Instruction::POP; } @@ -139,21 +139,21 @@ StorageItem::StorageItem(CompilerContext& _compilerContext, VariableDeclaration } StorageItem::StorageItem(CompilerContext& _compilerContext, Type const& _type): - LValue(_compilerContext, _type) + LValue(_compilerContext, &_type) { - if (m_dataType.isValueType()) + if (m_dataType->isValueType()) { - solAssert(m_dataType.storageSize() == m_dataType.sizeOnStack(), ""); - solAssert(m_dataType.storageSize() == 1, "Invalid storage size."); + solAssert(m_dataType->storageSize() == m_dataType->sizeOnStack(), ""); + solAssert(m_dataType->storageSize() == 1, "Invalid storage size."); } } void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const { // stack: storage_key storage_offset - if (!m_dataType.isValueType()) + if (!m_dataType->isValueType()) { - solAssert(m_dataType.sizeOnStack() == 1, "Invalid storage ref size."); + solAssert(m_dataType->sizeOnStack() == 1, "Invalid storage ref size."); if (_remove) m_context << eth::Instruction::POP; // remove byte offset else @@ -162,22 +162,22 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const } if (!_remove) CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack()); - if (m_dataType.storageBytes() == 32) + if (m_dataType->storageBytes() == 32) m_context << eth::Instruction::POP << eth::Instruction::SLOAD; else { m_context << eth::Instruction::SWAP1 << eth::Instruction::SLOAD << eth::Instruction::SWAP1 << u256(0x100) << eth::Instruction::EXP << eth::Instruction::SWAP1 << eth::Instruction::DIV; - if (m_dataType.category() == Type::Category::FixedBytes) - m_context << (u256(0x1) << (256 - 8 * m_dataType.storageBytes())) << eth::Instruction::MUL; + if (m_dataType->category() == Type::Category::FixedBytes) + m_context << (u256(0x1) << (256 - 8 * m_dataType->storageBytes())) << eth::Instruction::MUL; else if ( - m_dataType.category() == Type::Category::Integer && - dynamic_cast(m_dataType).isSigned() + m_dataType->category() == Type::Category::Integer && + dynamic_cast(*m_dataType).isSigned() ) - m_context << u256(m_dataType.storageBytes() - 1) << eth::Instruction::SIGNEXTEND; + m_context << u256(m_dataType->storageBytes() - 1) << eth::Instruction::SIGNEXTEND; else - m_context << ((u256(0x1) << (8 * m_dataType.storageBytes())) - 1) << eth::Instruction::AND; + m_context << ((u256(0x1) << (8 * m_dataType->storageBytes())) - 1) << eth::Instruction::AND; } } @@ -185,11 +185,11 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc { CompilerUtils utils(m_context); // stack: value storage_key storage_offset - if (m_dataType.isValueType()) + if (m_dataType->isValueType()) { - solAssert(m_dataType.storageBytes() <= 32, "Invalid storage bytes size."); - solAssert(m_dataType.storageBytes() > 0, "Invalid storage bytes size."); - if (m_dataType.storageBytes() == 32) + solAssert(m_dataType->storageBytes() <= 32, "Invalid storage bytes size."); + solAssert(m_dataType->storageBytes() > 0, "Invalid storage bytes size."); + if (m_dataType->storageBytes() == 32) { // offset should be zero m_context << eth::Instruction::POP; @@ -207,24 +207,24 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc // stack: value storege_ref multiplier old_full_value // clear bytes in old value m_context - << eth::Instruction::DUP2 << ((u256(1) << (8 * m_dataType.storageBytes())) - 1) + << eth::Instruction::DUP2 << ((u256(1) << (8 * m_dataType->storageBytes())) - 1) << eth::Instruction::MUL; m_context << eth::Instruction::NOT << eth::Instruction::AND; // stack: value storage_ref multiplier cleared_value m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP4; // stack: value storage_ref cleared_value multiplier value - if (m_dataType.category() == Type::Category::FixedBytes) + if (m_dataType->category() == Type::Category::FixedBytes) m_context - << (u256(0x1) << (256 - 8 * dynamic_cast(m_dataType).numBytes())) + << (u256(0x1) << (256 - 8 * dynamic_cast(*m_dataType).numBytes())) << eth::Instruction::SWAP1 << eth::Instruction::DIV; else if ( - m_dataType.category() == Type::Category::Integer && - dynamic_cast(m_dataType).isSigned() + m_dataType->category() == Type::Category::Integer && + dynamic_cast(*m_dataType).isSigned() ) // remove the higher order bits m_context - << (u256(1) << (8 * (32 - m_dataType.storageBytes()))) + << (u256(1) << (8 * (32 - m_dataType->storageBytes()))) << eth::Instruction::SWAP1 << eth::Instruction::DUP2 << eth::Instruction::MUL @@ -239,23 +239,23 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc else { solAssert( - _sourceType.category() == m_dataType.category(), + _sourceType.category() == m_dataType->category(), "Wrong type conversation for assignment."); - if (m_dataType.category() == Type::Category::Array) + if (m_dataType->category() == Type::Category::Array) { m_context << eth::Instruction::POP; // remove byte offset ArrayUtils(m_context).copyArrayToStorage( - dynamic_cast(m_dataType), + dynamic_cast(*m_dataType), dynamic_cast(_sourceType)); if (_move) m_context << eth::Instruction::POP; } - else if (m_dataType.category() == Type::Category::Struct) + else if (m_dataType->category() == Type::Category::Struct) { // stack layout: source_ref target_ref target_offset // note that we have structs, so offset should be zero and are ignored m_context << eth::Instruction::POP; - auto const& structType = dynamic_cast(m_dataType); + auto const& structType = dynamic_cast(*m_dataType); auto const& sourceType = dynamic_cast(_sourceType); solAssert( structType.structDefinition() == sourceType.structDefinition(), @@ -313,18 +313,18 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const { - if (m_dataType.category() == Type::Category::Array) + if (m_dataType->category() == Type::Category::Array) { if (!_removeReference) CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack()); - ArrayUtils(m_context).clearArray(dynamic_cast(m_dataType)); + ArrayUtils(m_context).clearArray(dynamic_cast(*m_dataType)); } - else if (m_dataType.category() == Type::Category::Struct) + else if (m_dataType->category() == Type::Category::Struct) { // stack layout: storage_key storage_offset // @todo this can be improved: use StorageItem for non-value types, and just store 0 in // all slots that contain value types later. - auto const& structType = dynamic_cast(m_dataType); + auto const& structType = dynamic_cast(*m_dataType); for (auto const& member: structType.members()) { // zero each member that is not a mapping @@ -342,10 +342,10 @@ void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const } else { - solAssert(m_dataType.isValueType(), "Clearing of unsupported type requested: " + m_dataType.toString()); + solAssert(m_dataType->isValueType(), "Clearing of unsupported type requested: " + m_dataType->toString()); if (!_removeReference) CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack()); - if (m_dataType.storageBytes() == 32) + if (m_dataType->storageBytes() == 32) { // offset should be zero m_context @@ -361,7 +361,7 @@ void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const // stack: storege_ref multiplier old_full_value // clear bytes in old value m_context - << eth::Instruction::SWAP1 << ((u256(1) << (8 * m_dataType.storageBytes())) - 1) + << eth::Instruction::SWAP1 << ((u256(1) << (8 * m_dataType->storageBytes())) - 1) << eth::Instruction::MUL; m_context << eth::Instruction::NOT << eth::Instruction::AND; // stack: storage_ref cleared_value @@ -374,7 +374,7 @@ void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const static FixedBytesType byteType(1); StorageByteArrayElement::StorageByteArrayElement(CompilerContext& _compilerContext): - LValue(_compilerContext, byteType) + LValue(_compilerContext, &byteType) { } @@ -427,7 +427,7 @@ void StorageByteArrayElement::setToZero(SourceLocation const&, bool _removeRefer } StorageArrayLength::StorageArrayLength(CompilerContext& _compilerContext, const ArrayType& _arrayType): - LValue(_compilerContext, *_arrayType.memberType("length")), + LValue(_compilerContext, _arrayType.memberType("length").get()), m_arrayType(_arrayType) { solAssert(m_arrayType.isDynamicallySized(), ""); @@ -455,3 +455,92 @@ void StorageArrayLength::setToZero(SourceLocation const&, bool _removeReference) m_context << eth::Instruction::DUP1; ArrayUtils(m_context).clearDynamicArray(m_arrayType); } + + +TupleObject::TupleObject( + CompilerContext& _compilerContext, + std::vector>&& _lvalues +): + LValue(_compilerContext), m_lvalues(move(_lvalues)) +{ +} + +unsigned TupleObject::sizeOnStack() const +{ + unsigned size = 0; + for (auto const& lv: m_lvalues) + if (lv) + size += lv->sizeOnStack(); + return size; +} + +void TupleObject::retrieveValue(SourceLocation const& _location, bool _remove) const +{ + unsigned initialDepth = sizeOnStack(); + unsigned initialStack = m_context.stackHeight(); + for (auto const& lv: m_lvalues) + if (lv) + { + solAssert(initialDepth + m_context.stackHeight() >= initialStack, ""); + unsigned depth = initialDepth + m_context.stackHeight() - initialStack; + if (lv->sizeOnStack() > 0) + if (_remove && depth > lv->sizeOnStack()) + CompilerUtils(m_context).moveToStackTop(depth, depth - lv->sizeOnStack()); + else if (!_remove && depth > 0) + CompilerUtils(m_context).copyToStackTop(depth, lv->sizeOnStack()); + lv->retrieveValue(_location, true); + } +} + +void TupleObject::storeValue(Type const& _sourceType, SourceLocation const& _location, bool _move) const +{ + // values are below the lvalue references + unsigned valuePos = sizeOnStack(); + + //@TODO wildcards + + TypePointers const& valueTypes = dynamic_cast(_sourceType).components(); + // valuePos .... refPos ... + // We will assign from right to left to optimize stack layout. + for (size_t i = 0; i < m_lvalues.size(); ++i) + { + unique_ptr const& lvalue = m_lvalues[m_lvalues.size() - i - 1]; + TypePointer const& valType = valueTypes[valueTypes.size() - i - 1]; + unsigned stackHeight = m_context.stackHeight(); + solAssert(!!valType, ""); + valuePos += valType->sizeOnStack(); + if (lvalue) + { + // copy value to top + CompilerUtils(m_context).copyToStackTop(valuePos, valType->sizeOnStack()); + // move lvalue ref above value + CompilerUtils(m_context).moveToStackTop(valType->sizeOnStack(), lvalue->sizeOnStack()); + lvalue->storeValue(*valType, _location, true); + } + valuePos += m_context.stackHeight() - stackHeight; + } + // As the type of an assignment to a tuple type is the empty tuple, we always move. + CompilerUtils(m_context).popStackElement(_sourceType); +} + +void TupleObject::setToZero(SourceLocation const& _location, bool _removeReference) const +{ + if (_removeReference) + { + for (size_t i = 0; i < m_lvalues.size(); ++i) + if (m_lvalues[m_lvalues.size() - i]) + m_lvalues[m_lvalues.size() - i]->setToZero(_location, true); + } + else + { + unsigned depth = sizeOnStack(); + for (auto const& val: m_lvalues) + if (val) + { + if (val->sizeOnStack() > 0) + CompilerUtils(m_context).copyToStackTop(depth, val->sizeOnStack()); + val->setToZero(_location, false); + depth -= val->sizeOnStack(); + } + } +} diff --git a/libsolidity/LValue.h b/libsolidity/LValue.h index cbbfb102..94c8d3b8 100644 --- a/libsolidity/LValue.h +++ b/libsolidity/LValue.h @@ -23,6 +23,7 @@ #pragma once #include +#include #include #include @@ -33,6 +34,7 @@ namespace solidity class Declaration; class Type; +class TupleType; class ArrayType; class CompilerContext; class VariableDeclaration; @@ -43,7 +45,7 @@ class VariableDeclaration; class LValue { protected: - LValue(CompilerContext& _compilerContext, Type const& _dataType): + explicit LValue(CompilerContext& _compilerContext, Type const* _dataType = nullptr): m_context(_compilerContext), m_dataType(_dataType) {} public: @@ -68,7 +70,7 @@ public: protected: CompilerContext& m_context; - Type const& m_dataType; + Type const* m_dataType; }; /** @@ -193,5 +195,30 @@ private: ArrayType const& m_arrayType; }; +/** + * Tuple object that can itself hold several LValues. + */ +class TupleObject: public LValue +{ +public: + /// Constructs the LValue assuming that the other LValues are present on the stack. + /// Empty unique_ptrs are possible if e.g. some values should be ignored during assignment. + TupleObject(CompilerContext& _compilerContext, std::vector>&& _lvalues); + virtual unsigned sizeOnStack() const; + virtual void retrieveValue(SourceLocation const& _location, bool _remove = false) const override; + virtual void storeValue( + Type const& _sourceType, + SourceLocation const& _location = SourceLocation(), + bool _move = false + ) const override; + virtual void setToZero( + SourceLocation const& _location = SourceLocation(), + bool _removeReference = true + ) const override; + +private: + std::vector> m_lvalues; +}; + } } diff --git a/libsolidity/TypeChecker.cpp b/libsolidity/TypeChecker.cpp index c7b693bb..e9d01a84 100644 --- a/libsolidity/TypeChecker.cpp +++ b/libsolidity/TypeChecker.cpp @@ -730,7 +730,13 @@ bool TypeChecker::visit(Assignment const& _assignment) requireLValue(_assignment.leftHandSide()); TypePointer t = type(_assignment.leftHandSide()); _assignment.annotation().type = t; - if (t->category() == Type::Category::Mapping) + if (TupleType const* tupleType = dynamic_cast(t.get())) + { + // Sequenced assignments of tuples is not valid. + _assignment.annotation().type = make_shared(); + expectType(_assignment.rightHandSide(), *tupleType); + } + else if (t->category() == Type::Category::Mapping) { typeError(_assignment, "Mappings cannot be assigned to."); _assignment.rightHandSide().accept(*this); diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp index d7beab26..aca959b3 100644 --- a/libsolidity/Types.cpp +++ b/libsolidity/Types.cpp @@ -1242,6 +1242,38 @@ unsigned int EnumType::memberValue(ASTString const& _member) const BOOST_THROW_EXCEPTION(m_enum.createTypeError("Requested unknown enum value ." + _member)); } +bool TupleType::isImplicitlyConvertibleTo(Type const& _other) const +{ + if (auto tupleType = dynamic_cast(&_other)) + { + TypePointers const& targets = tupleType->components(); + if (targets.empty()) + return components().empty(); + if (components().size() != targets.size() && !targets.front() && !targets.back()) + return false; // (,a,) = (1,2,3,4) - unable to position `a` in the tuple. + size_t minNumValues = targets.size(); + if (!targets.back() || !targets.front()) + --minNumValues; // wildcards can also match 0 components + if (components().size() < minNumValues) + return false; + if (components().size() > targets.size() && targets.front() && targets.back()) + return false; // larger source and no wildcard + bool fillRight = !targets.back() || targets.front(); + for (size_t i = 0; i < min(targets.size(), components().size()); ++i) + { + auto const& s = components()[fillRight ? i : components().size() - i - 1]; + auto const& t = targets[fillRight ? i : targets.size() - i - 1]; + if (!s && t) + return false; + else if (s && t && !s->isImplicitlyConvertibleTo(*t)) + return false; + } + return true; + } + else + return false; +} + bool TupleType::operator==(Type const& _other) const { if (auto tupleType = dynamic_cast(&_other)) @@ -1252,10 +1284,10 @@ bool TupleType::operator==(Type const& _other) const string TupleType::toString(bool _short) const { - if (m_components.empty()) + if (components().empty()) return "tuple()"; string str = "tuple("; - for (auto const& t: m_components) + for (auto const& t: components()) str += (t ? t->toString(_short) : "") + ","; str.pop_back(); return str + ")"; @@ -1272,29 +1304,33 @@ u256 TupleType::storageSize() const unsigned TupleType::sizeOnStack() const { unsigned size = 0; - for (auto const& t: m_components) + for (auto const& t: components()) size += t ? t->sizeOnStack() : 0; return size; } -bool TupleType::isImplicitlyConvertibleTo(Type const& _other) const +TypePointer TupleType::mobileType() const { - if (auto tupleType = dynamic_cast(&_other)) + TypePointers mobiles; + for (auto const& c: components()) + mobiles.push_back(c ? c->mobileType() : TypePointer()); + return make_shared(mobiles); +} + +TypePointer TupleType::closestTemporaryType(TypePointer const& _targetType) const +{ + solAssert(!!_targetType, ""); + TypePointers const& targetComponents = dynamic_cast(*_targetType).components(); + bool fillRight = !targetComponents.empty() && (!targetComponents.back() || targetComponents.front()); + TypePointers tempComponents(targetComponents.size()); + for (size_t i = 0; i < min(targetComponents.size(), components().size()); ++i) { - if (components().size() != tupleType->components().size()) - return false; - for (size_t i = 0; i < components().size(); ++i) - if ((!components()[i]) != (!tupleType->components()[i])) - return false; - else if ( - components()[i] && - !components()[i]->isImplicitlyConvertibleTo(*tupleType->components()[i]) - ) - return false; - return true; + size_t si = fillRight ? i : components().size() - i - 1; + size_t ti = fillRight ? i : targetComponents.size() - i - 1; + if (components()[si] && targetComponents[ti]) + tempComponents[ti] = components()[si]->closestTemporaryType(targetComponents[si]); } - else - return false; + return make_shared(tempComponents); } FunctionType::FunctionType(FunctionDefinition const& _function, bool _isInternal): diff --git a/libsolidity/Types.h b/libsolidity/Types.h index e8d65c41..626ebbe4 100644 --- a/libsolidity/Types.h +++ b/libsolidity/Types.h @@ -210,6 +210,13 @@ public: /// @returns true if this is a non-value type and the data of this type is stored at the /// given location. virtual bool dataStoredIn(DataLocation) const { return false; } + /// @returns the type of a temporary during assignment to a variable of the given type. + /// Specifically, returns the requested itself if it can be dynamically allocated (or is a value type) + /// and the mobile type otherwise. + virtual TypePointer closestTemporaryType(TypePointer const& _targetType) const + { + return _targetType->dataStoredIn(DataLocation::Storage) ? mobileType() : _targetType; + } /// Returns the list of all members of this type. Default implementation: no members. virtual MemberList const& members() const { return EmptyMemberList; } @@ -689,6 +696,7 @@ class TupleType: public Type public: virtual Category category() const override { return Category::Tuple; } explicit TupleType(std::vector const& _types = std::vector()): m_components(_types) {} + virtual bool isImplicitlyConvertibleTo(Type const& _other) const override; virtual bool operator==(Type const& _other) const override; virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } virtual std::string toString(bool) const override; @@ -696,7 +704,9 @@ public: virtual u256 storageSize() const override; virtual bool canLiveOutsideStorage() const override { return false; } virtual unsigned sizeOnStack() const override; - virtual bool isImplicitlyConvertibleTo(Type const& _other) const override; + virtual TypePointer mobileType() const override; + /// Converts components to their temporary types and performs some wildcard matching. + virtual TypePointer closestTemporaryType(TypePointer const& _targetType) const override; std::vector const& components() const { return m_components; } -- cgit From 029b8194892b6b08ce70075bd66f43f66c40e301 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 15 Oct 2015 00:42:36 +0200 Subject: Wildcards. --- libsolidity/CompilerUtils.cpp | 32 +++++++++++++++++++------------- libsolidity/ExpressionCompiler.cpp | 4 ---- libsolidity/LValue.cpp | 21 +++++++++------------ libsolidity/Types.cpp | 2 +- 4 files changed, 29 insertions(+), 30 deletions(-) (limited to 'libsolidity') diff --git a/libsolidity/CompilerUtils.cpp b/libsolidity/CompilerUtils.cpp index 34bb08ba..f0dea708 100644 --- a/libsolidity/CompilerUtils.cpp +++ b/libsolidity/CompilerUtils.cpp @@ -552,29 +552,37 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp } case Type::Category::Tuple: { - //@TODO wildcards TupleType const& sourceTuple = dynamic_cast(_typeOnStack); TupleType const& targetTuple = dynamic_cast(_targetType); - solAssert(sourceTuple.components().size() == targetTuple.components().size(), ""); + // fillRight: remove excess values at right side, !fillRight: remove eccess values at left side + bool fillRight = !targetTuple.components().empty() && ( + !targetTuple.components().back() || + targetTuple.components().front() + ); unsigned depth = sourceTuple.sizeOnStack(); for (size_t i = 0; i < sourceTuple.components().size(); ++i) { - TypePointer const& sourceType = sourceTuple.components()[i]; - TypePointer const& targetType = targetTuple.components()[i]; + TypePointer sourceType = sourceTuple.components()[i]; + TypePointer targetType; + if (fillRight && i < targetTuple.components().size()) + targetType = targetTuple.components()[i]; + else if (!fillRight && targetTuple.components().size() + i >= sourceTuple.components().size()) + targetType = targetTuple.components()[targetTuple.components().size() - (sourceTuple.components().size() - i)]; if (!sourceType) { solAssert(!targetType, ""); continue; } unsigned sourceSize = sourceType->sizeOnStack(); - unsigned targetSize = targetType->sizeOnStack(); - if (*sourceType != *targetType || _cleanupNeeded) + unsigned targetSize = targetType ? targetType->sizeOnStack() : 0; + if (!targetType || *sourceType != *targetType || _cleanupNeeded) { - if (sourceSize > 0) - copyToStackTop(depth, sourceSize); - - convertType(*sourceType, *targetType, _cleanupNeeded); - + if (targetType) + { + if (sourceSize > 0) + copyToStackTop(depth, sourceSize); + convertType(*sourceType, *targetType, _cleanupNeeded); + } if (sourceSize > 0 || targetSize > 0) { // Move it back into its place. @@ -582,8 +590,6 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp m_context << eth::swapInstruction(depth + targetSize - sourceSize) << eth::Instruction::POP; - if (targetSize < sourceSize) - moveToStackTop(sourceSize - targetSize, depth ); // Value shrank for (unsigned j = targetSize; j < sourceSize; ++j) { diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp index 8109c03b..909d3fe5 100644 --- a/libsolidity/ExpressionCompiler.cpp +++ b/libsolidity/ExpressionCompiler.cpp @@ -177,7 +177,6 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& bool ExpressionCompiler::visit(Assignment const& _assignment) { -// cout << "-----Assignment" << endl; CompilerContext::LocationSetter locationSetter(m_context, _assignment); _assignment.rightHandSide().accept(*this); // Perform some conversion already. This will convert storage types to memory and literals @@ -185,10 +184,8 @@ bool ExpressionCompiler::visit(Assignment const& _assignment) TypePointer type = _assignment.rightHandSide().annotation().type->closestTemporaryType( _assignment.leftHandSide().annotation().type ); -// cout << "-----Type conversion" << endl; utils().convertType(*_assignment.rightHandSide().annotation().type, *type); -// cout << "-----LHS" << endl; _assignment.leftHandSide().accept(*this); solAssert(!!m_currentLValue, "LValue not retrieved."); @@ -214,7 +211,6 @@ bool ExpressionCompiler::visit(Assignment const& _assignment) m_context << eth::swapInstruction(itemSize + lvalueSize) << eth::Instruction::POP; } } -// cout << "-----Store" << endl; m_currentLValue->storeValue(*type, _assignment.location()); m_currentLValue.reset(); return false; diff --git a/libsolidity/LValue.cpp b/libsolidity/LValue.cpp index 20aa59e1..52441836 100644 --- a/libsolidity/LValue.cpp +++ b/libsolidity/LValue.cpp @@ -496,10 +496,8 @@ void TupleObject::storeValue(Type const& _sourceType, SourceLocation const& _loc { // values are below the lvalue references unsigned valuePos = sizeOnStack(); - - //@TODO wildcards - TypePointers const& valueTypes = dynamic_cast(_sourceType).components(); + solAssert(valueTypes.size() == m_lvalues.size(), ""); // valuePos .... refPos ... // We will assign from right to left to optimize stack layout. for (size_t i = 0; i < m_lvalues.size(); ++i) @@ -507,16 +505,15 @@ void TupleObject::storeValue(Type const& _sourceType, SourceLocation const& _loc unique_ptr const& lvalue = m_lvalues[m_lvalues.size() - i - 1]; TypePointer const& valType = valueTypes[valueTypes.size() - i - 1]; unsigned stackHeight = m_context.stackHeight(); - solAssert(!!valType, ""); + solAssert(!valType == !lvalue, ""); + if (!lvalue) + continue; valuePos += valType->sizeOnStack(); - if (lvalue) - { - // copy value to top - CompilerUtils(m_context).copyToStackTop(valuePos, valType->sizeOnStack()); - // move lvalue ref above value - CompilerUtils(m_context).moveToStackTop(valType->sizeOnStack(), lvalue->sizeOnStack()); - lvalue->storeValue(*valType, _location, true); - } + // copy value to top + CompilerUtils(m_context).copyToStackTop(valuePos, valType->sizeOnStack()); + // move lvalue ref above value + CompilerUtils(m_context).moveToStackTop(valType->sizeOnStack(), lvalue->sizeOnStack()); + lvalue->storeValue(*valType, _location, true); valuePos += m_context.stackHeight() - stackHeight; } // As the type of an assignment to a tuple type is the empty tuple, we always move. diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp index aca959b3..02b86a7f 100644 --- a/libsolidity/Types.cpp +++ b/libsolidity/Types.cpp @@ -1328,7 +1328,7 @@ TypePointer TupleType::closestTemporaryType(TypePointer const& _targetType) cons size_t si = fillRight ? i : components().size() - i - 1; size_t ti = fillRight ? i : targetComponents.size() - i - 1; if (components()[si] && targetComponents[ti]) - tempComponents[ti] = components()[si]->closestTemporaryType(targetComponents[si]); + tempComponents[ti] = components()[si]->closestTemporaryType(targetComponents[ti]); } return make_shared(tempComponents); } -- cgit From ae5b12f54b43b7793600ad9b2c785fd0f9a5ed9e Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 15 Oct 2015 14:03:56 +0200 Subject: Fix compiler warnings. --- libsolidity/LValue.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'libsolidity') diff --git a/libsolidity/LValue.cpp b/libsolidity/LValue.cpp index 52441836..bc069efd 100644 --- a/libsolidity/LValue.cpp +++ b/libsolidity/LValue.cpp @@ -484,15 +484,17 @@ void TupleObject::retrieveValue(SourceLocation const& _location, bool _remove) c solAssert(initialDepth + m_context.stackHeight() >= initialStack, ""); unsigned depth = initialDepth + m_context.stackHeight() - initialStack; if (lv->sizeOnStack() > 0) + { if (_remove && depth > lv->sizeOnStack()) CompilerUtils(m_context).moveToStackTop(depth, depth - lv->sizeOnStack()); else if (!_remove && depth > 0) CompilerUtils(m_context).copyToStackTop(depth, lv->sizeOnStack()); + } lv->retrieveValue(_location, true); } } -void TupleObject::storeValue(Type const& _sourceType, SourceLocation const& _location, bool _move) const +void TupleObject::storeValue(Type const& _sourceType, SourceLocation const& _location, bool) const { // values are below the lvalue references unsigned valuePos = sizeOnStack(); -- cgit From e21df35416169a6804f6019c2fbb487c74bad596 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 15 Oct 2015 16:02:00 +0200 Subject: MacOS fix. --- libsolidity/TypeChecker.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'libsolidity') diff --git a/libsolidity/TypeChecker.cpp b/libsolidity/TypeChecker.cpp index e9d01a84..dcaecdfb 100644 --- a/libsolidity/TypeChecker.cpp +++ b/libsolidity/TypeChecker.cpp @@ -732,8 +732,8 @@ bool TypeChecker::visit(Assignment const& _assignment) _assignment.annotation().type = t; if (TupleType const* tupleType = dynamic_cast(t.get())) { - // Sequenced assignments of tuples is not valid. - _assignment.annotation().type = make_shared(); + // Sequenced assignments of tuples is not valid, make the result a "void" type. + _assignment.annotation().type = make_shared(); expectType(_assignment.rightHandSide(), *tupleType); } else if (t->category() == Type::Category::Mapping) -- cgit From 1d4219d43d2839c41e56307f9db04cc23d4741e5 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 15 Oct 2015 18:14:14 +0200 Subject: Some fixes taking other pull requests into account. --- libsolidity/ExpressionCompiler.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'libsolidity') diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp index 909d3fe5..fde88a00 100644 --- a/libsolidity/ExpressionCompiler.cpp +++ b/libsolidity/ExpressionCompiler.cpp @@ -664,9 +664,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) // stack: newLength storageSlot slotOffset arguments[0]->accept(*this); // stack: newLength storageSlot slotOffset argValue - TypePointer type = arguments[0]->annotation().type; - utils().convertType(*type, *arrayType->baseType()); - type = arrayType->baseType(); + TypePointer type = arguments[0]->annotation().type->closestTemporaryType(arrayType->baseType()); + utils().convertType(*arguments[0]->annotation().type, *type); utils().moveToStackTop(1 + type->sizeOnStack()); utils().moveToStackTop(1 + type->sizeOnStack()); // stack: newLength argValue storageSlot slotOffset -- cgit From 2920a32ae81539a10af54e275da72111b792568e Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 16 Oct 2015 10:01:22 +0200 Subject: Fixed indentation. --- libsolidity/LValue.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'libsolidity') diff --git a/libsolidity/LValue.cpp b/libsolidity/LValue.cpp index bc069efd..ac04ebef 100644 --- a/libsolidity/LValue.cpp +++ b/libsolidity/LValue.cpp @@ -245,8 +245,9 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc { m_context << eth::Instruction::POP; // remove byte offset ArrayUtils(m_context).copyArrayToStorage( - dynamic_cast(*m_dataType), - dynamic_cast(_sourceType)); + dynamic_cast(*m_dataType), + dynamic_cast(_sourceType) + ); if (_move) m_context << eth::Instruction::POP; } -- cgit