diff --git a/libsolidity/AST.cpp b/libsolidity/AST.cpp index 33cf8c12d..3c6b6007c 100644 --- a/libsolidity/AST.cpp +++ b/libsolidity/AST.cpp @@ -402,10 +402,8 @@ void Assignment::checkTypeRequirements() { m_leftHandSide->checkTypeRequirements(); m_leftHandSide->requireLValue(); - //@todo later, assignments to structs might be possible, but not to mappings - if (m_leftHandSide->getType()->getCategory() != Type::Category::ByteArray && - !m_leftHandSide->getType()->isValueType() && !m_leftHandSide->isLocalLValue()) - BOOST_THROW_EXCEPTION(createTypeError("Assignment to non-local non-value lvalue.")); + if (m_leftHandSide->getType()->getCategory() == Type::Category::Mapping) + BOOST_THROW_EXCEPTION(createTypeError("Mappings cannot be assigned to.")); m_type = m_leftHandSide->getType(); if (m_assigmentOperator == Token::Assign) m_rightHandSide->expectType(*m_type); diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp index 3dbb4012d..90f860a1e 100644 --- a/libsolidity/ExpressionCompiler.cpp +++ b/libsolidity/ExpressionCompiler.cpp @@ -70,12 +70,12 @@ bool ExpressionCompiler::visit(Assignment const& _assignment) solAssert(_assignment.getType()->isValueType(), "Compound operators not implemented for non-value types."); if (m_currentLValue.storesReferenceOnStack()) m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2; - m_currentLValue.retrieveValue(_assignment.getType(), _assignment.getLocation(), true); + m_currentLValue.retrieveValue(_assignment.getLocation(), true); appendOrdinaryBinaryOperatorCode(Token::AssignmentToBinaryOp(op), *_assignment.getType()); if (m_currentLValue.storesReferenceOnStack()) m_context << eth::Instruction::SWAP1; } - m_currentLValue.storeValue(_assignment, *_assignment.getRightHandSide().getType()); + m_currentLValue.storeValue(*_assignment.getRightHandSide().getType(), _assignment.getLocation()); m_currentLValue.reset(); return false; @@ -105,13 +105,13 @@ bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation) break; case Token::Delete: // delete solAssert(m_currentLValue.isValid(), "LValue not retrieved."); - m_currentLValue.setToZero(_unaryOperation, *_unaryOperation.getSubExpression().getType()); + m_currentLValue.setToZero(_unaryOperation.getLocation()); m_currentLValue.reset(); break; case Token::Inc: // ++ (pre- or postfix) case Token::Dec: // -- (pre- or postfix) solAssert(m_currentLValue.isValid(), "LValue not retrieved."); - m_currentLValue.retrieveValue(_unaryOperation.getType(), _unaryOperation.getLocation()); + m_currentLValue.retrieveValue(_unaryOperation.getLocation()); if (!_unaryOperation.isPrefixOperation()) { if (m_currentLValue.storesReferenceOnStack()) @@ -128,7 +128,8 @@ bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation) // Stack for postfix: *ref [ref] (*ref)+-1 if (m_currentLValue.storesReferenceOnStack()) m_context << eth::Instruction::SWAP1; - m_currentLValue.storeValue(_unaryOperation, *_unaryOperation.getType(), !_unaryOperation.isPrefixOperation()); + m_currentLValue.storeValue(*_unaryOperation.getType(), _unaryOperation.getLocation(), + !_unaryOperation.isPrefixOperation()); m_currentLValue.reset(); break; case Token::Add: // + @@ -484,7 +485,7 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) { StructType const& type = dynamic_cast(*_memberAccess.getExpression().getType()); m_context << type.getStorageOffsetOfMember(member) << eth::Instruction::ADD; - m_currentLValue = LValue(m_context, LValue::LValueType::Storage, *_memberAccess.getType()); + m_currentLValue = LValue(m_context, LValue::LValueType::Storage, _memberAccess.getType()); m_currentLValue.retrieveValueIfLValueNotRequested(_memberAccess); break; } @@ -530,7 +531,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) appendTypeMoveToMemory(IntegerType(256)); m_context << u256(0) << eth::Instruction::SHA3; - m_currentLValue = LValue(m_context, LValue::LValueType::Storage, *_indexAccess.getType()); + m_currentLValue = LValue(m_context, LValue::LValueType::Storage, _indexAccess.getType()); m_currentLValue.retrieveValueIfLValueNotRequested(_indexAccess); return false; @@ -939,8 +940,8 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& m_context << eth::Instruction::DUP1 << structType->getStorageOffsetOfMember(names[i]) << eth::Instruction::ADD; - m_currentLValue = LValue(m_context, LValue::LValueType::Storage, *types[i]); - m_currentLValue.retrieveValue(types[i], Location(), true); + m_currentLValue = LValue(m_context, LValue::LValueType::Storage, types[i]); + m_currentLValue.retrieveValue(Location(), true); solAssert(types[i]->getSizeOnStack() == 1, "Returning struct elements with stack size != 1 not yet implemented."); m_context << eth::Instruction::SWAP1; retSizeOnStack += types[i]->getSizeOnStack(); @@ -951,27 +952,51 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& { // simple value solAssert(accessorType.getReturnParameterTypes().size() == 1, ""); - m_currentLValue = LValue(m_context, LValue::LValueType::Storage, *returnType); - m_currentLValue.retrieveValue(returnType, Location(), true); + m_currentLValue = LValue(m_context, LValue::LValueType::Storage, returnType); + m_currentLValue.retrieveValue(Location(), true); retSizeOnStack = returnType->getSizeOnStack(); } solAssert(retSizeOnStack <= 15, "Stack too deep."); m_context << eth::dupInstruction(retSizeOnStack + 1) << eth::Instruction::JUMP; } -ExpressionCompiler::LValue::LValue(CompilerContext& _compilerContext, LValueType _type, Type const& _dataType, - unsigned _baseStackOffset): - m_context(&_compilerContext), m_type(_type), m_baseStackOffset(_baseStackOffset) +ExpressionCompiler::LValue::LValue(CompilerContext& _compilerContext, LValueType _type, + TypePointer const& _dataType, unsigned _baseStackOffset): + m_context(&_compilerContext), m_type(_type), m_dataType(_dataType), + m_baseStackOffset(_baseStackOffset) { //@todo change the type cast for arrays - solAssert(_dataType.getStorageSize() <= numeric_limits::max(), "The storage size of " +_dataType.toString() + " should fit in unsigned"); + solAssert(m_dataType->getStorageSize() <= numeric_limits::max(), + "The storage size of " + m_dataType->toString() + " should fit in unsigned"); if (m_type == LValueType::Storage) - m_size = unsigned(_dataType.getStorageSize()); + m_size = unsigned(m_dataType->getStorageSize()); else - m_size = unsigned(_dataType.getSizeOnStack()); + m_size = unsigned(m_dataType->getSizeOnStack()); } -void ExpressionCompiler::LValue::retrieveValue(TypePointer const& _type, Location const& _location, bool _remove) const +void ExpressionCompiler::LValue::fromIdentifier(Identifier const& _identifier, Declaration const& _declaration) +{ + if (m_context->isLocalVariable(&_declaration)) + { + m_type = LValueType::Stack; + m_dataType = _identifier.getType(); + m_size = m_dataType->getSizeOnStack(); + m_baseStackOffset = m_context->getBaseStackOffsetOfVariable(_declaration); + } + else if (m_context->isStateVariable(&_declaration)) + { + *m_context << m_context->getStorageLocationOfVariable(_declaration); + m_type = LValueType::Storage; + m_dataType = _identifier.getType(); + solAssert(m_dataType->getStorageSize() <= numeric_limits::max(), + "The storage size of " + m_dataType->toString() + " should fit in an unsigned"); + m_size = unsigned(m_dataType->getStorageSize()); } + else + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_identifier.getLocation()) + << errinfo_comment("Identifier type not supported or identifier not found.")); +} + +void ExpressionCompiler::LValue::retrieveValue(Location const& _location, bool _remove) const { switch (m_type) { @@ -986,10 +1011,10 @@ void ExpressionCompiler::LValue::retrieveValue(TypePointer const& _type, Locatio break; } case LValueType::Storage: - retrieveValueFromStorage(_type, _remove); + retrieveValueFromStorage(_remove); break; case LValueType::Memory: - if (!_type->isValueType()) + if (!m_dataType->isValueType()) break; // no distinction between value and reference for non-value types BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_location) << errinfo_comment("Location type not yet implemented.")); @@ -1001,9 +1026,9 @@ void ExpressionCompiler::LValue::retrieveValue(TypePointer const& _type, Locatio } } -void ExpressionCompiler::LValue::retrieveValueFromStorage(TypePointer const& _type, bool _remove) const +void ExpressionCompiler::LValue::retrieveValueFromStorage(bool _remove) const { - if (!_type->isValueType()) + if (!m_dataType->isValueType()) return; // no distinction between value and reference for non-value types if (!_remove) *m_context << eth::Instruction::DUP1; @@ -1020,7 +1045,7 @@ void ExpressionCompiler::LValue::retrieveValueFromStorage(TypePointer const& _ty } } -void ExpressionCompiler::LValue::storeValue(Expression const& _expression, Type const& _sourceType, bool _move) const +void ExpressionCompiler::LValue::storeValue(Type const& _sourceType, Location const& _location, bool _move) const { switch (m_type) { @@ -1028,23 +1053,23 @@ void ExpressionCompiler::LValue::storeValue(Expression const& _expression, Type { unsigned stackDiff = m_context->baseToCurrentStackOffset(unsigned(m_baseStackOffset)) - m_size + 1; if (stackDiff > 16) - BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_expression.getLocation()) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_location) << errinfo_comment("Stack too deep.")); else if (stackDiff > 0) for (unsigned i = 0; i < m_size; ++i) *m_context << eth::swapInstruction(stackDiff) << eth::Instruction::POP; if (!_move) - retrieveValue(_expression.getType(), _expression.getLocation()); + retrieveValue(_location); break; } case LValueType::Storage: // stack layout: value value ... value target_ref - if (_expression.getType()->isValueType()) + if (m_dataType->isValueType()) { if (!_move) // copy values { if (m_size + 1 > 16) - BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_expression.getLocation()) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_location) << errinfo_comment("Stack too deep.")); for (unsigned i = 0; i < m_size; ++i) *m_context << eth::dupInstruction(m_size + 1) << eth::Instruction::SWAP1; @@ -1064,36 +1089,60 @@ void ExpressionCompiler::LValue::storeValue(Expression const& _expression, Type } else { - solAssert(!_move, "Move assign for non-value types not implemented."); - solAssert(_sourceType.getCategory() == _expression.getType()->getCategory(), ""); - if (_expression.getType()->getCategory() == Type::Category::ByteArray) + solAssert(_sourceType.getCategory() == m_dataType->getCategory(), ""); + if (m_dataType->getCategory() == Type::Category::ByteArray) CompilerUtils(*m_context).copyByteArrayToStorage( - dynamic_cast(*_expression.getType()), + dynamic_cast(*m_dataType), dynamic_cast(_sourceType)); - else if (_expression.getType()->getCategory() == Type::Category::Struct) + else if (m_dataType->getCategory() == Type::Category::Struct) { - //@todo - solAssert(false, "Struct copy not yet implemented."); + // stack layout: source_ref target_ref + auto const& structType = dynamic_cast(*m_dataType); + solAssert(structType == _sourceType, "Struct assignment with conversion."); + for (auto const& member: structType.getMembers()) + { + // assign each member that is not a mapping + TypePointer const& memberType = member.second; + if (memberType->getCategory() == Type::Category::Mapping) + continue; + *m_context << structType.getStorageOffsetOfMember(member.first) + << eth::Instruction::DUP3 << eth::Instruction::DUP2 + << eth::Instruction::ADD; + LValue rightHandSide(*m_context, LValueType::Storage, memberType); + rightHandSide.retrieveValue(_location, true); + // stack: source_ref target_ref offset source_value... + *m_context << eth::dupInstruction(2 + memberType->getSizeOnStack()) + << eth::dupInstruction(2 + memberType->getSizeOnStack()) + << eth::Instruction::ADD; + LValue memberLValue(*m_context, LValueType::Storage, memberType); + memberLValue.storeValue(*memberType, _location, true); + *m_context << eth::Instruction::POP; + } + if (_move) + *m_context << eth::Instruction::POP; + else + *m_context << eth::Instruction::SWAP1; + *m_context << eth::Instruction::POP; } else - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_expression.getLocation()) + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_location) << errinfo_comment("Invalid non-value type for assignment.")); } break; case LValueType::Memory: - if (!_expression.getType()->isValueType()) + if (!m_dataType->isValueType()) break; // no distinction between value and reference for non-value types - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_expression.getLocation()) + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_location) << errinfo_comment("Location type not yet implemented.")); break; default: - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_expression.getLocation()) + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_location) << errinfo_comment("Unsupported location type.")); break; } } -void ExpressionCompiler::LValue::setToZero(Expression const& _expression, Type const& _type) const +void ExpressionCompiler::LValue::setToZero(Location const& _location) const { switch (m_type) { @@ -1101,7 +1150,7 @@ void ExpressionCompiler::LValue::setToZero(Expression const& _expression, Type c { unsigned stackDiff = m_context->baseToCurrentStackOffset(unsigned(m_baseStackOffset)); if (stackDiff > 16) - BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_expression.getLocation()) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_location) << errinfo_comment("Stack too deep.")); solAssert(stackDiff >= m_size - 1, ""); for (unsigned i = 0; i < m_size; ++i) @@ -1110,8 +1159,8 @@ void ExpressionCompiler::LValue::setToZero(Expression const& _expression, Type c break; } case LValueType::Storage: - if (_type.getCategory() == Type::Category::ByteArray) - CompilerUtils(*m_context).clearByteArray(dynamic_cast(_type)); + if (m_dataType->getCategory() == Type::Category::ByteArray) + CompilerUtils(*m_context).clearByteArray(dynamic_cast(*m_dataType)); else { if (m_size == 0) @@ -1125,11 +1174,11 @@ void ExpressionCompiler::LValue::setToZero(Expression const& _expression, Type c } break; case LValueType::Memory: - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_expression.getLocation()) + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_location) << errinfo_comment("Location type not yet implemented.")); break; default: - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_expression.getLocation()) + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_location) << errinfo_comment("Unsupported location type.")); break; } @@ -1139,36 +1188,10 @@ void ExpressionCompiler::LValue::retrieveValueIfLValueNotRequested(Expression co { if (!_expression.lvalueRequested()) { - retrieveValue(_expression.getType(), _expression.getLocation(), true); + retrieveValue(_expression.getLocation(), true); reset(); } } -void ExpressionCompiler::LValue::fromStateVariable(Declaration const& _varDecl, TypePointer const& _type) -{ - m_type = LValueType::Storage; - solAssert(_type->getStorageSize() <= numeric_limits::max(), "The storage size of " + _type->toString() + " should fit in an unsigned"); - *m_context << m_context->getStorageLocationOfVariable(_varDecl); - m_size = unsigned(_type->getStorageSize()); -} - -void ExpressionCompiler::LValue::fromIdentifier(Identifier const& _identifier, Declaration const& _declaration) -{ - if (m_context->isLocalVariable(&_declaration)) - { - m_type = LValueType::Stack; - m_size = _identifier.getType()->getSizeOnStack(); - m_baseStackOffset = m_context->getBaseStackOffsetOfVariable(_declaration); - } - else if (m_context->isStateVariable(&_declaration)) - { - fromStateVariable(_declaration, _identifier.getType()); - } - else - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_identifier.getLocation()) - << errinfo_comment("Identifier type not supported or identifier not found.")); -} - - } } diff --git a/libsolidity/ExpressionCompiler.h b/libsolidity/ExpressionCompiler.h index e0cc75ce8..734da50de 100644 --- a/libsolidity/ExpressionCompiler.h +++ b/libsolidity/ExpressionCompiler.h @@ -22,8 +22,10 @@ */ #include +#include #include #include +#include #include namespace dev { @@ -37,6 +39,7 @@ namespace solidity { class CompilerContext; class Type; class IntegerType; +class ByteArrayType; class StaticStringType; /** @@ -119,14 +122,13 @@ private: enum class LValueType { None, Stack, Memory, Storage }; explicit LValue(CompilerContext& _compilerContext): m_context(&_compilerContext) { reset(); } - LValue(CompilerContext& _compilerContext, LValueType _type, Type const& _dataType, unsigned _baseStackOffset = 0); + LValue(CompilerContext& _compilerContext, LValueType _type, + std::shared_ptr const& _dataType, unsigned _baseStackOffset = 0); /// Set type according to the declaration and retrieve the reference. /// @a _expression is the current expression void fromIdentifier(Identifier const& _identifier, Declaration const& _declaration); - /// Convenience function to set type for a state variable and retrieve the reference - void fromStateVariable(Declaration const& _varDecl, TypePointer const& _type); - void reset() { m_type = LValueType::None; m_baseStackOffset = 0; m_size = 0; } + void reset() { m_type = LValueType::None; m_dataType.reset(); m_baseStackOffset = 0; m_size = 0; } bool isValid() const { return m_type != LValueType::None; } bool isInOnStack() const { return m_type == LValueType::Stack; } @@ -138,30 +140,29 @@ private: /// Copies the value of the current lvalue to the top of the stack and, if @a _remove is true, /// also removes the reference from the stack (note that is does not reset the type to @a NONE). - /// @a _type is the type of the current expression and @ _location its location, used for error reporting. - /// @a _location can be a nullptr for expressions that don't have an actual ASTNode equivalent - void retrieveValue(TypePointer const& _type, Location const& _location, bool _remove = false) const; - /// Stores a value (from the stack directly beneath the reference, which is assumed to - /// be on the top of the stack, if any) in the lvalue and removes the reference. - /// Also removes the stored value from the stack if @a _move is - /// true. @a _expression is the current expression, used for error reporting. - /// @a _sourceType is the type of the expression that is assigned. - void storeValue(Expression const& _expression, Type const& _sourceType, bool _move = false) const; + /// @a _location source location of the current expression, used for error reporting. + void retrieveValue(Location const& _location, bool _remove = false) const; + /// Moves a value from the stack to the lvalue. Removes the value if @a _move is true. + /// @a _location is the source location of the expression that caused this operation. + /// Stack pre: [lvalue_ref] value + /// Stack post if !_move: value_of(lvalue_ref) + void storeValue(Type const& _sourceType, Location const& _location = Location(), bool _move = false) const; /// Stores zero in the lvalue. - /// @a _expression is the current expression, used for error reporting. - void setToZero(Expression const& _expression, Type const& _type) const; + /// @a _location is the source location of the requested operation + void setToZero(Location const& _location = Location()) const; /// Convenience function to convert the stored reference to a value and reset type to NONE if /// the reference was not requested by @a _expression. void retrieveValueIfLValueNotRequested(Expression const& _expression); private: /// Convenience function to retrieve Value from Storage. Specific version of @ref retrieveValue - void retrieveValueFromStorage(TypePointer const& _type, bool _remove = false) const; + void retrieveValueFromStorage(bool _remove = false) const; /// Copies from a byte array to a byte array in storage, both references on the stack. void copyByteArrayToStorage(ByteArrayType const& _targetType, ByteArrayType const& _sourceType) const; CompilerContext* m_context; LValueType m_type = LValueType::None; + std::shared_ptr m_dataType; /// If m_type is STACK, this is base stack offset (@see /// CompilerContext::getBaseStackOffsetOfVariable) of a local variable. unsigned m_baseStackOffset = 0; diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp index 33cc8a1ec..16514b148 100644 --- a/libsolidity/Types.cpp +++ b/libsolidity/Types.cpp @@ -653,7 +653,7 @@ u256 StructType::getStorageOffsetOfMember(string const& _name) const { //@todo cache member offset? u256 offset; - for (ASTPointer variable: m_struct.getMembers()) + for (ASTPointer const& variable: m_struct.getMembers()) { if (variable->getName() == _name) return offset; diff --git a/test/SolidityEndToEndTest.cpp b/test/SolidityEndToEndTest.cpp index 587d4193f..abfc97b73 100644 --- a/test/SolidityEndToEndTest.cpp +++ b/test/SolidityEndToEndTest.cpp @@ -2424,6 +2424,66 @@ BOOST_AUTO_TEST_CASE(bytes_length_member) BOOST_CHECK(callContractFunction("getLength()") == encodeArgs(4+32+32)); } +BOOST_AUTO_TEST_CASE(struct_copy) +{ + char const* sourceCode = R"( + contract c { + struct Nested { uint x; uint y; } + struct Struct { uint a; mapping(uint => Struct) b; Nested nested; uint c; } + mapping(uint => Struct) public data; + function set(uint k) returns (bool) { + data[k].a = 1; + data[k].nested.x = 3; + data[k].nested.y = 4; + data[k].c = 2; + return true; + } + function copy(uint from, uint to) returns (bool) { + data[to] = data[from]; + return true; + } + function retrieve(uint k) returns (uint a, uint x, uint y, uint c) + { + a = data[k].a; + x = data[k].nested.x; + y = data[k].nested.y; + c = data[k].c; + } + } + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("set(uint256)", 7) == encodeArgs(true)); + BOOST_CHECK(callContractFunction("retrieve(uint256)", 7) == encodeArgs(1, 3, 4, 2)); + BOOST_CHECK(callContractFunction("copy(uint256,uint256)", 7, 8) == encodeArgs(true)); + BOOST_CHECK(callContractFunction("retrieve(uint256)", 7) == encodeArgs(1, 3, 4, 2)); + BOOST_CHECK(callContractFunction("retrieve(uint256)", 8) == encodeArgs(1, 3, 4, 2)); + BOOST_CHECK(callContractFunction("copy(uint256,uint256)", 0, 7) == encodeArgs(true)); + BOOST_CHECK(callContractFunction("retrieve(uint256)", 7) == encodeArgs(0, 0, 0, 0)); + BOOST_CHECK(callContractFunction("retrieve(uint256)", 8) == encodeArgs(1, 3, 4, 2)); + BOOST_CHECK(callContractFunction("copy(uint256,uint256)", 7, 8) == encodeArgs(true)); + BOOST_CHECK(callContractFunction("retrieve(uint256)", 8) == encodeArgs(0, 0, 0, 0)); +} + +BOOST_AUTO_TEST_CASE(struct_copy_via_local) +{ + char const* sourceCode = R"( + contract c { + struct Struct { uint a; uint b; } + Struct data1; + Struct data2; + function test() returns (bool) { + data1.a = 1; + data1.b = 2; + var x = data1; + data2 = x; + return data2.a == data1.a && data2.b == data1.b; + } + } + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("test()") == encodeArgs(true)); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/SolidityNameAndTypeResolution.cpp b/test/SolidityNameAndTypeResolution.cpp index d013f5c5e..3ead6f1c5 100644 --- a/test/SolidityNameAndTypeResolution.cpp +++ b/test/SolidityNameAndTypeResolution.cpp @@ -332,7 +332,7 @@ BOOST_AUTO_TEST_CASE(assignment_to_struct) " data = a;\n" " }\n" "}\n"; - BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError); + BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text)); } BOOST_AUTO_TEST_CASE(returns_in_constructor)