mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Destructuring assignments.
This commit is contained in:
parent
7ebd536e79
commit
039b2a764f
@ -597,13 +597,20 @@ bool Compiler::visit(Break const& _breakStatement)
|
|||||||
bool Compiler::visit(Return const& _return)
|
bool Compiler::visit(Return const& _return)
|
||||||
{
|
{
|
||||||
CompilerContext::LocationSetter locationSetter(m_context, _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())
|
if (Expression const* expression = _return.expression())
|
||||||
{
|
{
|
||||||
solAssert(_return.annotation().functionReturnParameters, "Invalid return parameters pointer.");
|
solAssert(_return.annotation().functionReturnParameters, "Invalid return parameters pointer.");
|
||||||
VariableDeclaration const& firstVariable = *_return.annotation().functionReturnParameters->parameters().front();
|
vector<ASTPointer<VariableDeclaration>> const& returnParameters =
|
||||||
compileExpression(*expression, firstVariable.annotation().type);
|
_return.annotation().functionReturnParameters->parameters();
|
||||||
CompilerUtils(m_context).moveToStackVariable(firstVariable);
|
TypePointers types;
|
||||||
|
for (auto const& retVariable: returnParameters)
|
||||||
|
types.push_back(retVariable->annotation().type);
|
||||||
|
|
||||||
|
TypePointer expectedType = types.size() == 1 ? types.front() : make_shared<TupleType>(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)
|
for (unsigned i = 0; i < m_stackCleanupForReturn; ++i)
|
||||||
m_context << eth::Instruction::POP;
|
m_context << eth::Instruction::POP;
|
||||||
|
@ -550,6 +550,55 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case Type::Category::Tuple:
|
||||||
|
{
|
||||||
|
//@TODO wildcards
|
||||||
|
TupleType const& sourceTuple = dynamic_cast<TupleType const&>(_typeOnStack);
|
||||||
|
TupleType const& targetTuple = dynamic_cast<TupleType const&>(_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:
|
default:
|
||||||
// All other types should not be convertible to non-equal types.
|
// All other types should not be convertible to non-equal types.
|
||||||
solAssert(_typeOnStack == _targetType, "Invalid type conversion requested.");
|
solAssert(_typeOnStack == _targetType, "Invalid type conversion requested.");
|
||||||
@ -631,18 +680,20 @@ void CompilerUtils::copyToStackTop(unsigned _stackDepth, unsigned _itemSize)
|
|||||||
m_context << eth::dupInstruction(_stackDepth);
|
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.");
|
solAssert(_stackDepth <= 15, "Stack too deep, try removing local variables.");
|
||||||
for (unsigned i = 0; i < _stackDepth; ++i)
|
for (unsigned j = 0; j < _itemSize; ++j)
|
||||||
m_context << eth::swapInstruction(1 + i);
|
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.");
|
solAssert(_stackDepth <= 16, "Stack too deep, try removing local variables.");
|
||||||
for (unsigned i = _stackDepth; i > 0; --i)
|
for (unsigned j = 0; j < _itemSize; ++j)
|
||||||
m_context << eth::swapInstruction(i);
|
for (unsigned i = _stackDepth; i > 0; --i)
|
||||||
|
m_context << eth::swapInstruction(i + _itemSize - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CompilerUtils::popStackElement(Type const& _type)
|
void CompilerUtils::popStackElement(Type const& _type)
|
||||||
|
@ -124,10 +124,11 @@ public:
|
|||||||
/// Copies an item that occupies @a _itemSize stack slots from a stack depth of @a _stackDepth
|
/// Copies an item that occupies @a _itemSize stack slots from a stack depth of @a _stackDepth
|
||||||
/// to the top of the stack.
|
/// to the top of the stack.
|
||||||
void copyToStackTop(unsigned _stackDepth, unsigned _itemSize);
|
void copyToStackTop(unsigned _stackDepth, unsigned _itemSize);
|
||||||
/// Moves a single stack element (with _stackDepth items on top of it) to the top of the stack.
|
/// Moves an item that occupies @a _itemSize stack slots and has items occupying @a _stackDepth
|
||||||
void moveToStackTop(unsigned _stackDepth);
|
/// slots above it to the top of the stack.
|
||||||
/// Moves a single stack element past @a _stackDepth other stack elements
|
void moveToStackTop(unsigned _stackDepth, unsigned _itemSize = 1);
|
||||||
void moveIntoStack(unsigned _stackDepth);
|
/// 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.
|
/// Removes the current value from the top of the stack.
|
||||||
void popStackElement(Type const& _type);
|
void popStackElement(Type const& _type);
|
||||||
/// Removes element from the top of the stack _amount times.
|
/// Removes element from the top of the stack _amount times.
|
||||||
|
@ -177,20 +177,18 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
|
|||||||
|
|
||||||
bool ExpressionCompiler::visit(Assignment const& _assignment)
|
bool ExpressionCompiler::visit(Assignment const& _assignment)
|
||||||
{
|
{
|
||||||
|
// cout << "-----Assignment" << endl;
|
||||||
CompilerContext::LocationSetter locationSetter(m_context, _assignment);
|
CompilerContext::LocationSetter locationSetter(m_context, _assignment);
|
||||||
_assignment.rightHandSide().accept(*this);
|
_assignment.rightHandSide().accept(*this);
|
||||||
TypePointer type = _assignment.rightHandSide().annotation().type;
|
// Perform some conversion already. This will convert storage types to memory and literals
|
||||||
if (!_assignment.annotation().type->dataStoredIn(DataLocation::Storage))
|
// to their actual type, but will not convert e.g. memory to storage.
|
||||||
{
|
TypePointer type = _assignment.rightHandSide().annotation().type->closestTemporaryType(
|
||||||
utils().convertType(*type, *_assignment.annotation().type);
|
_assignment.leftHandSide().annotation().type
|
||||||
type = _assignment.annotation().type;
|
);
|
||||||
}
|
// cout << "-----Type conversion" << endl;
|
||||||
else
|
utils().convertType(*_assignment.rightHandSide().annotation().type, *type);
|
||||||
{
|
|
||||||
utils().convertType(*type, *type->mobileType());
|
|
||||||
type = type->mobileType();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// cout << "-----LHS" << endl;
|
||||||
_assignment.leftHandSide().accept(*this);
|
_assignment.leftHandSide().accept(*this);
|
||||||
solAssert(!!m_currentLValue, "LValue not retrieved.");
|
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;
|
m_context << eth::swapInstruction(itemSize + lvalueSize) << eth::Instruction::POP;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// cout << "-----Store" << endl;
|
||||||
m_currentLValue->storeValue(*type, _assignment.location());
|
m_currentLValue->storeValue(*type, _assignment.location());
|
||||||
m_currentLValue.reset();
|
m_currentLValue.reset();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ExpressionCompiler::visit(TupleExpression const& _tuple)
|
||||||
|
{
|
||||||
|
vector<unique_ptr<LValue>> 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<LValue>());
|
||||||
|
if (_tuple.annotation().lValueRequested)
|
||||||
|
m_currentLValue.reset(new TupleObject(m_context, move(lvalues)));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation)
|
bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation)
|
||||||
{
|
{
|
||||||
CompilerContext::LocationSetter locationSetter(m_context, _unaryOperation);
|
CompilerContext::LocationSetter locationSetter(m_context, _unaryOperation);
|
||||||
|
@ -72,6 +72,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
virtual bool visit(Assignment const& _assignment) override;
|
virtual bool visit(Assignment const& _assignment) override;
|
||||||
|
virtual bool visit(TupleExpression const& _tuple) override;
|
||||||
virtual bool visit(UnaryOperation const& _unaryOperation) override;
|
virtual bool visit(UnaryOperation const& _unaryOperation) override;
|
||||||
virtual bool visit(BinaryOperation const& _binaryOperation) override;
|
virtual bool visit(BinaryOperation const& _binaryOperation) override;
|
||||||
virtual bool visit(FunctionCall const& _functionCall) override;
|
virtual bool visit(FunctionCall const& _functionCall) override;
|
||||||
|
@ -32,9 +32,9 @@ using namespace solidity;
|
|||||||
|
|
||||||
|
|
||||||
StackVariable::StackVariable(CompilerContext& _compilerContext, VariableDeclaration const& _declaration):
|
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_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
|
void StackVariable::setToZero(SourceLocation const& _location, bool) const
|
||||||
{
|
{
|
||||||
CompilerUtils(m_context).pushZeroValue(m_dataType);
|
CompilerUtils(m_context).pushZeroValue(*m_dataType);
|
||||||
storeValue(m_dataType, _location, true);
|
storeValue(*m_dataType, _location, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
MemoryItem::MemoryItem(CompilerContext& _compilerContext, Type const& _type, bool _padded):
|
MemoryItem::MemoryItem(CompilerContext& _compilerContext, Type const& _type, bool _padded):
|
||||||
LValue(_compilerContext, _type),
|
LValue(_compilerContext, &_type),
|
||||||
m_padded(_padded)
|
m_padded(_padded)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void MemoryItem::retrieveValue(SourceLocation const&, bool _remove) const
|
void MemoryItem::retrieveValue(SourceLocation const&, bool _remove) const
|
||||||
{
|
{
|
||||||
if (m_dataType.isValueType())
|
if (m_dataType->isValueType())
|
||||||
{
|
{
|
||||||
if (!_remove)
|
if (!_remove)
|
||||||
m_context << eth::Instruction::DUP1;
|
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
|
else
|
||||||
m_context << eth::Instruction::MLOAD;
|
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
|
void MemoryItem::storeValue(Type const& _sourceType, SourceLocation const&, bool _move) const
|
||||||
{
|
{
|
||||||
CompilerUtils utils(m_context);
|
CompilerUtils utils(m_context);
|
||||||
if (m_dataType.isValueType())
|
if (m_dataType->isValueType())
|
||||||
{
|
{
|
||||||
solAssert(_sourceType.isValueType(), "");
|
solAssert(_sourceType.isValueType(), "");
|
||||||
utils.moveIntoStack(_sourceType.sizeOnStack());
|
utils.moveIntoStack(_sourceType.sizeOnStack());
|
||||||
utils.convertType(_sourceType, m_dataType, true);
|
utils.convertType(_sourceType, *m_dataType, true);
|
||||||
if (!_move)
|
if (!_move)
|
||||||
{
|
{
|
||||||
utils.moveToStackTop(m_dataType.sizeOnStack());
|
utils.moveToStackTop(m_dataType->sizeOnStack());
|
||||||
utils.copyToStackTop(2, 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;
|
m_context << eth::Instruction::POP;
|
||||||
}
|
}
|
||||||
else
|
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)
|
if (!_move)
|
||||||
m_context << eth::Instruction::DUP2 << eth::Instruction::SWAP1;
|
m_context << eth::Instruction::DUP2 << eth::Instruction::SWAP1;
|
||||||
// stack: [value] value lvalue
|
// stack: [value] value lvalue
|
||||||
@ -126,8 +126,8 @@ void MemoryItem::setToZero(SourceLocation const&, bool _removeReference) const
|
|||||||
CompilerUtils utils(m_context);
|
CompilerUtils utils(m_context);
|
||||||
if (!_removeReference)
|
if (!_removeReference)
|
||||||
m_context << eth::Instruction::DUP1;
|
m_context << eth::Instruction::DUP1;
|
||||||
utils.pushZeroValue(m_dataType);
|
utils.pushZeroValue(*m_dataType);
|
||||||
utils.storeInMemoryDynamic(m_dataType, m_padded);
|
utils.storeInMemoryDynamic(*m_dataType, m_padded);
|
||||||
m_context << eth::Instruction::POP;
|
m_context << eth::Instruction::POP;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,21 +139,21 @@ StorageItem::StorageItem(CompilerContext& _compilerContext, VariableDeclaration
|
|||||||
}
|
}
|
||||||
|
|
||||||
StorageItem::StorageItem(CompilerContext& _compilerContext, Type const& _type):
|
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() == m_dataType->sizeOnStack(), "");
|
||||||
solAssert(m_dataType.storageSize() == 1, "Invalid storage size.");
|
solAssert(m_dataType->storageSize() == 1, "Invalid storage size.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const
|
void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const
|
||||||
{
|
{
|
||||||
// stack: storage_key storage_offset
|
// 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)
|
if (_remove)
|
||||||
m_context << eth::Instruction::POP; // remove byte offset
|
m_context << eth::Instruction::POP; // remove byte offset
|
||||||
else
|
else
|
||||||
@ -162,22 +162,22 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const
|
|||||||
}
|
}
|
||||||
if (!_remove)
|
if (!_remove)
|
||||||
CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack());
|
CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack());
|
||||||
if (m_dataType.storageBytes() == 32)
|
if (m_dataType->storageBytes() == 32)
|
||||||
m_context << eth::Instruction::POP << eth::Instruction::SLOAD;
|
m_context << eth::Instruction::POP << eth::Instruction::SLOAD;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
m_context
|
m_context
|
||||||
<< eth::Instruction::SWAP1 << eth::Instruction::SLOAD << eth::Instruction::SWAP1
|
<< eth::Instruction::SWAP1 << eth::Instruction::SLOAD << eth::Instruction::SWAP1
|
||||||
<< u256(0x100) << eth::Instruction::EXP << eth::Instruction::SWAP1 << eth::Instruction::DIV;
|
<< u256(0x100) << eth::Instruction::EXP << eth::Instruction::SWAP1 << eth::Instruction::DIV;
|
||||||
if (m_dataType.category() == Type::Category::FixedBytes)
|
if (m_dataType->category() == Type::Category::FixedBytes)
|
||||||
m_context << (u256(0x1) << (256 - 8 * m_dataType.storageBytes())) << eth::Instruction::MUL;
|
m_context << (u256(0x1) << (256 - 8 * m_dataType->storageBytes())) << eth::Instruction::MUL;
|
||||||
else if (
|
else if (
|
||||||
m_dataType.category() == Type::Category::Integer &&
|
m_dataType->category() == Type::Category::Integer &&
|
||||||
dynamic_cast<IntegerType const&>(m_dataType).isSigned()
|
dynamic_cast<IntegerType const&>(*m_dataType).isSigned()
|
||||||
)
|
)
|
||||||
m_context << u256(m_dataType.storageBytes() - 1) << eth::Instruction::SIGNEXTEND;
|
m_context << u256(m_dataType->storageBytes() - 1) << eth::Instruction::SIGNEXTEND;
|
||||||
else
|
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);
|
CompilerUtils utils(m_context);
|
||||||
// stack: value storage_key storage_offset
|
// 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() <= 32, "Invalid storage bytes size.");
|
||||||
solAssert(m_dataType.storageBytes() > 0, "Invalid storage bytes size.");
|
solAssert(m_dataType->storageBytes() > 0, "Invalid storage bytes size.");
|
||||||
if (m_dataType.storageBytes() == 32)
|
if (m_dataType->storageBytes() == 32)
|
||||||
{
|
{
|
||||||
// offset should be zero
|
// offset should be zero
|
||||||
m_context << eth::Instruction::POP;
|
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
|
// stack: value storege_ref multiplier old_full_value
|
||||||
// clear bytes in old value
|
// clear bytes in old value
|
||||||
m_context
|
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;
|
<< eth::Instruction::MUL;
|
||||||
m_context << eth::Instruction::NOT << eth::Instruction::AND;
|
m_context << eth::Instruction::NOT << eth::Instruction::AND;
|
||||||
// stack: value storage_ref multiplier cleared_value
|
// stack: value storage_ref multiplier cleared_value
|
||||||
m_context
|
m_context
|
||||||
<< eth::Instruction::SWAP1 << eth::Instruction::DUP4;
|
<< eth::Instruction::SWAP1 << eth::Instruction::DUP4;
|
||||||
// stack: value storage_ref cleared_value multiplier value
|
// stack: value storage_ref cleared_value multiplier value
|
||||||
if (m_dataType.category() == Type::Category::FixedBytes)
|
if (m_dataType->category() == Type::Category::FixedBytes)
|
||||||
m_context
|
m_context
|
||||||
<< (u256(0x1) << (256 - 8 * dynamic_cast<FixedBytesType const&>(m_dataType).numBytes()))
|
<< (u256(0x1) << (256 - 8 * dynamic_cast<FixedBytesType const&>(*m_dataType).numBytes()))
|
||||||
<< eth::Instruction::SWAP1 << eth::Instruction::DIV;
|
<< eth::Instruction::SWAP1 << eth::Instruction::DIV;
|
||||||
else if (
|
else if (
|
||||||
m_dataType.category() == Type::Category::Integer &&
|
m_dataType->category() == Type::Category::Integer &&
|
||||||
dynamic_cast<IntegerType const&>(m_dataType).isSigned()
|
dynamic_cast<IntegerType const&>(*m_dataType).isSigned()
|
||||||
)
|
)
|
||||||
// remove the higher order bits
|
// remove the higher order bits
|
||||||
m_context
|
m_context
|
||||||
<< (u256(1) << (8 * (32 - m_dataType.storageBytes())))
|
<< (u256(1) << (8 * (32 - m_dataType->storageBytes())))
|
||||||
<< eth::Instruction::SWAP1
|
<< eth::Instruction::SWAP1
|
||||||
<< eth::Instruction::DUP2
|
<< eth::Instruction::DUP2
|
||||||
<< eth::Instruction::MUL
|
<< eth::Instruction::MUL
|
||||||
@ -239,23 +239,23 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
solAssert(
|
solAssert(
|
||||||
_sourceType.category() == m_dataType.category(),
|
_sourceType.category() == m_dataType->category(),
|
||||||
"Wrong type conversation for assignment.");
|
"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
|
m_context << eth::Instruction::POP; // remove byte offset
|
||||||
ArrayUtils(m_context).copyArrayToStorage(
|
ArrayUtils(m_context).copyArrayToStorage(
|
||||||
dynamic_cast<ArrayType const&>(m_dataType),
|
dynamic_cast<ArrayType const&>(*m_dataType),
|
||||||
dynamic_cast<ArrayType const&>(_sourceType));
|
dynamic_cast<ArrayType const&>(_sourceType));
|
||||||
if (_move)
|
if (_move)
|
||||||
m_context << eth::Instruction::POP;
|
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
|
// stack layout: source_ref target_ref target_offset
|
||||||
// note that we have structs, so offset should be zero and are ignored
|
// note that we have structs, so offset should be zero and are ignored
|
||||||
m_context << eth::Instruction::POP;
|
m_context << eth::Instruction::POP;
|
||||||
auto const& structType = dynamic_cast<StructType const&>(m_dataType);
|
auto const& structType = dynamic_cast<StructType const&>(*m_dataType);
|
||||||
auto const& sourceType = dynamic_cast<StructType const&>(_sourceType);
|
auto const& sourceType = dynamic_cast<StructType const&>(_sourceType);
|
||||||
solAssert(
|
solAssert(
|
||||||
structType.structDefinition() == sourceType.structDefinition(),
|
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
|
void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const
|
||||||
{
|
{
|
||||||
if (m_dataType.category() == Type::Category::Array)
|
if (m_dataType->category() == Type::Category::Array)
|
||||||
{
|
{
|
||||||
if (!_removeReference)
|
if (!_removeReference)
|
||||||
CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack());
|
CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack());
|
||||||
ArrayUtils(m_context).clearArray(dynamic_cast<ArrayType const&>(m_dataType));
|
ArrayUtils(m_context).clearArray(dynamic_cast<ArrayType const&>(*m_dataType));
|
||||||
}
|
}
|
||||||
else if (m_dataType.category() == Type::Category::Struct)
|
else if (m_dataType->category() == Type::Category::Struct)
|
||||||
{
|
{
|
||||||
// stack layout: storage_key storage_offset
|
// stack layout: storage_key storage_offset
|
||||||
// @todo this can be improved: use StorageItem for non-value types, and just store 0 in
|
// @todo this can be improved: use StorageItem for non-value types, and just store 0 in
|
||||||
// all slots that contain value types later.
|
// all slots that contain value types later.
|
||||||
auto const& structType = dynamic_cast<StructType const&>(m_dataType);
|
auto const& structType = dynamic_cast<StructType const&>(*m_dataType);
|
||||||
for (auto const& member: structType.members())
|
for (auto const& member: structType.members())
|
||||||
{
|
{
|
||||||
// zero each member that is not a mapping
|
// zero each member that is not a mapping
|
||||||
@ -342,10 +342,10 @@ void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const
|
|||||||
}
|
}
|
||||||
else
|
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)
|
if (!_removeReference)
|
||||||
CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack());
|
CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack());
|
||||||
if (m_dataType.storageBytes() == 32)
|
if (m_dataType->storageBytes() == 32)
|
||||||
{
|
{
|
||||||
// offset should be zero
|
// offset should be zero
|
||||||
m_context
|
m_context
|
||||||
@ -361,7 +361,7 @@ void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const
|
|||||||
// stack: storege_ref multiplier old_full_value
|
// stack: storege_ref multiplier old_full_value
|
||||||
// clear bytes in old value
|
// clear bytes in old value
|
||||||
m_context
|
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;
|
<< eth::Instruction::MUL;
|
||||||
m_context << eth::Instruction::NOT << eth::Instruction::AND;
|
m_context << eth::Instruction::NOT << eth::Instruction::AND;
|
||||||
// stack: storage_ref cleared_value
|
// stack: storage_ref cleared_value
|
||||||
@ -374,7 +374,7 @@ void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const
|
|||||||
static FixedBytesType byteType(1);
|
static FixedBytesType byteType(1);
|
||||||
|
|
||||||
StorageByteArrayElement::StorageByteArrayElement(CompilerContext& _compilerContext):
|
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):
|
StorageArrayLength::StorageArrayLength(CompilerContext& _compilerContext, const ArrayType& _arrayType):
|
||||||
LValue(_compilerContext, *_arrayType.memberType("length")),
|
LValue(_compilerContext, _arrayType.memberType("length").get()),
|
||||||
m_arrayType(_arrayType)
|
m_arrayType(_arrayType)
|
||||||
{
|
{
|
||||||
solAssert(m_arrayType.isDynamicallySized(), "");
|
solAssert(m_arrayType.isDynamicallySized(), "");
|
||||||
@ -455,3 +455,92 @@ void StorageArrayLength::setToZero(SourceLocation const&, bool _removeReference)
|
|||||||
m_context << eth::Instruction::DUP1;
|
m_context << eth::Instruction::DUP1;
|
||||||
ArrayUtils(m_context).clearDynamicArray(m_arrayType);
|
ArrayUtils(m_context).clearDynamicArray(m_arrayType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TupleObject::TupleObject(
|
||||||
|
CompilerContext& _compilerContext,
|
||||||
|
std::vector<std::unique_ptr<LValue>>&& _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<TupleType const&>(_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<LValue> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
#include <libevmasm/SourceLocation.h>
|
#include <libevmasm/SourceLocation.h>
|
||||||
#include <libsolidity/ArrayUtils.h>
|
#include <libsolidity/ArrayUtils.h>
|
||||||
|
|
||||||
@ -33,6 +34,7 @@ namespace solidity
|
|||||||
|
|
||||||
class Declaration;
|
class Declaration;
|
||||||
class Type;
|
class Type;
|
||||||
|
class TupleType;
|
||||||
class ArrayType;
|
class ArrayType;
|
||||||
class CompilerContext;
|
class CompilerContext;
|
||||||
class VariableDeclaration;
|
class VariableDeclaration;
|
||||||
@ -43,7 +45,7 @@ class VariableDeclaration;
|
|||||||
class LValue
|
class LValue
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
LValue(CompilerContext& _compilerContext, Type const& _dataType):
|
explicit LValue(CompilerContext& _compilerContext, Type const* _dataType = nullptr):
|
||||||
m_context(_compilerContext), m_dataType(_dataType) {}
|
m_context(_compilerContext), m_dataType(_dataType) {}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@ -68,7 +70,7 @@ public:
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
CompilerContext& m_context;
|
CompilerContext& m_context;
|
||||||
Type const& m_dataType;
|
Type const* m_dataType;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -193,5 +195,30 @@ private:
|
|||||||
ArrayType const& m_arrayType;
|
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<std::unique_ptr<LValue>>&& _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<std::unique_ptr<LValue>> m_lvalues;
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -730,7 +730,13 @@ bool TypeChecker::visit(Assignment const& _assignment)
|
|||||||
requireLValue(_assignment.leftHandSide());
|
requireLValue(_assignment.leftHandSide());
|
||||||
TypePointer t = type(_assignment.leftHandSide());
|
TypePointer t = type(_assignment.leftHandSide());
|
||||||
_assignment.annotation().type = t;
|
_assignment.annotation().type = t;
|
||||||
if (t->category() == Type::Category::Mapping)
|
if (TupleType const* tupleType = dynamic_cast<TupleType const*>(t.get()))
|
||||||
|
{
|
||||||
|
// Sequenced assignments of tuples is not valid.
|
||||||
|
_assignment.annotation().type = make_shared<TupleType const>();
|
||||||
|
expectType(_assignment.rightHandSide(), *tupleType);
|
||||||
|
}
|
||||||
|
else if (t->category() == Type::Category::Mapping)
|
||||||
{
|
{
|
||||||
typeError(_assignment, "Mappings cannot be assigned to.");
|
typeError(_assignment, "Mappings cannot be assigned to.");
|
||||||
_assignment.rightHandSide().accept(*this);
|
_assignment.rightHandSide().accept(*this);
|
||||||
|
@ -1242,6 +1242,38 @@ unsigned int EnumType::memberValue(ASTString const& _member) const
|
|||||||
BOOST_THROW_EXCEPTION(m_enum.createTypeError("Requested unknown enum value ." + _member));
|
BOOST_THROW_EXCEPTION(m_enum.createTypeError("Requested unknown enum value ." + _member));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TupleType::isImplicitlyConvertibleTo(Type const& _other) const
|
||||||
|
{
|
||||||
|
if (auto tupleType = dynamic_cast<TupleType const*>(&_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
|
bool TupleType::operator==(Type const& _other) const
|
||||||
{
|
{
|
||||||
if (auto tupleType = dynamic_cast<TupleType const*>(&_other))
|
if (auto tupleType = dynamic_cast<TupleType const*>(&_other))
|
||||||
@ -1252,10 +1284,10 @@ bool TupleType::operator==(Type const& _other) const
|
|||||||
|
|
||||||
string TupleType::toString(bool _short) const
|
string TupleType::toString(bool _short) const
|
||||||
{
|
{
|
||||||
if (m_components.empty())
|
if (components().empty())
|
||||||
return "tuple()";
|
return "tuple()";
|
||||||
string str = "tuple(";
|
string str = "tuple(";
|
||||||
for (auto const& t: m_components)
|
for (auto const& t: components())
|
||||||
str += (t ? t->toString(_short) : "") + ",";
|
str += (t ? t->toString(_short) : "") + ",";
|
||||||
str.pop_back();
|
str.pop_back();
|
||||||
return str + ")";
|
return str + ")";
|
||||||
@ -1272,29 +1304,33 @@ u256 TupleType::storageSize() const
|
|||||||
unsigned TupleType::sizeOnStack() const
|
unsigned TupleType::sizeOnStack() const
|
||||||
{
|
{
|
||||||
unsigned size = 0;
|
unsigned size = 0;
|
||||||
for (auto const& t: m_components)
|
for (auto const& t: components())
|
||||||
size += t ? t->sizeOnStack() : 0;
|
size += t ? t->sizeOnStack() : 0;
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TupleType::isImplicitlyConvertibleTo(Type const& _other) const
|
TypePointer TupleType::mobileType() const
|
||||||
{
|
{
|
||||||
if (auto tupleType = dynamic_cast<TupleType const*>(&_other))
|
TypePointers mobiles;
|
||||||
|
for (auto const& c: components())
|
||||||
|
mobiles.push_back(c ? c->mobileType() : TypePointer());
|
||||||
|
return make_shared<TupleType>(mobiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
TypePointer TupleType::closestTemporaryType(TypePointer const& _targetType) const
|
||||||
|
{
|
||||||
|
solAssert(!!_targetType, "");
|
||||||
|
TypePointers const& targetComponents = dynamic_cast<TupleType const&>(*_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())
|
size_t si = fillRight ? i : components().size() - i - 1;
|
||||||
return false;
|
size_t ti = fillRight ? i : targetComponents.size() - i - 1;
|
||||||
for (size_t i = 0; i < components().size(); ++i)
|
if (components()[si] && targetComponents[ti])
|
||||||
if ((!components()[i]) != (!tupleType->components()[i]))
|
tempComponents[ti] = components()[si]->closestTemporaryType(targetComponents[si]);
|
||||||
return false;
|
|
||||||
else if (
|
|
||||||
components()[i] &&
|
|
||||||
!components()[i]->isImplicitlyConvertibleTo(*tupleType->components()[i])
|
|
||||||
)
|
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
else
|
return make_shared<TupleType>(tempComponents);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FunctionType::FunctionType(FunctionDefinition const& _function, bool _isInternal):
|
FunctionType::FunctionType(FunctionDefinition const& _function, bool _isInternal):
|
||||||
|
@ -210,6 +210,13 @@ public:
|
|||||||
/// @returns true if this is a non-value type and the data of this type is stored at the
|
/// @returns true if this is a non-value type and the data of this type is stored at the
|
||||||
/// given location.
|
/// given location.
|
||||||
virtual bool dataStoredIn(DataLocation) const { return false; }
|
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.
|
/// Returns the list of all members of this type. Default implementation: no members.
|
||||||
virtual MemberList const& members() const { return EmptyMemberList; }
|
virtual MemberList const& members() const { return EmptyMemberList; }
|
||||||
@ -689,6 +696,7 @@ class TupleType: public Type
|
|||||||
public:
|
public:
|
||||||
virtual Category category() const override { return Category::Tuple; }
|
virtual Category category() const override { return Category::Tuple; }
|
||||||
explicit TupleType(std::vector<TypePointer> const& _types = std::vector<TypePointer>()): m_components(_types) {}
|
explicit TupleType(std::vector<TypePointer> const& _types = std::vector<TypePointer>()): m_components(_types) {}
|
||||||
|
virtual bool isImplicitlyConvertibleTo(Type const& _other) const override;
|
||||||
virtual bool operator==(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 TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); }
|
||||||
virtual std::string toString(bool) const override;
|
virtual std::string toString(bool) const override;
|
||||||
@ -696,7 +704,9 @@ public:
|
|||||||
virtual u256 storageSize() const override;
|
virtual u256 storageSize() const override;
|
||||||
virtual bool canLiveOutsideStorage() const override { return false; }
|
virtual bool canLiveOutsideStorage() const override { return false; }
|
||||||
virtual unsigned sizeOnStack() const override;
|
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<TypePointer> const& components() const { return m_components; }
|
std::vector<TypePointer> const& components() const { return m_components; }
|
||||||
|
|
||||||
|
@ -5693,14 +5693,14 @@ BOOST_AUTO_TEST_CASE(tuples)
|
|||||||
data[0] = 3;
|
data[0] = 3;
|
||||||
uint a; uint b;
|
uint a; uint b;
|
||||||
(a, b) = this.h();
|
(a, b) = this.h();
|
||||||
if (a != 1 || b != 2) return 1;
|
if (a != 5 || b != 6) return 1;
|
||||||
uint[] storage c;
|
uint[] storage c;
|
||||||
(a, b, c) = g();
|
(a, b, c) = g();
|
||||||
if (a != 5 || b != 6 || c[0] != 3) return 2;
|
if (a != 1 || b != 2 || c[0] != 3) return 2;
|
||||||
(a, b) = (b, a);
|
(a, b) = (b, a);
|
||||||
if (a != 6 || b != 5) return 3;
|
if (a != 2 || b != 1) return 3;
|
||||||
(a, , b, ) = (8, 9, 10, 11, 12);
|
// (a, , b, ) = (8, 9, 10, 11, 12);
|
||||||
if (a != 8 || b != 10) return 3;
|
// if (a != 8 || b != 10) return 3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)";
|
)";
|
||||||
@ -5708,6 +5708,42 @@ BOOST_AUTO_TEST_CASE(tuples)
|
|||||||
BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(0)));
|
BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(destructuring_assignment)
|
||||||
|
{
|
||||||
|
char const* sourceCode = R"(
|
||||||
|
contract C {
|
||||||
|
uint x = 7;
|
||||||
|
bytes data;
|
||||||
|
uint[] y;
|
||||||
|
uint[] arrayData;
|
||||||
|
function returnsArray() returns (uint[]) {
|
||||||
|
arrayData.length = 9;
|
||||||
|
arrayData[2] = 5;
|
||||||
|
arrayData[7] = 4;
|
||||||
|
return arrayData;
|
||||||
|
}
|
||||||
|
function f(bytes s) returns (uint) {
|
||||||
|
uint loc;
|
||||||
|
uint[] memArray;
|
||||||
|
(loc, x, y, data, arrayData[3]) = (8, 4, returnsArray(), s, 2);
|
||||||
|
if (loc != 8) return 1;
|
||||||
|
if (x != 4) return 2;
|
||||||
|
if (y.length != 9) return 3;
|
||||||
|
if (y[2] != 5) return 4;
|
||||||
|
if (y[7] != 4) return 5;
|
||||||
|
if (data.length != s.length) return 6;
|
||||||
|
if (data[3] != s[3]) return 7;
|
||||||
|
if (arrayData[3] != 2) return 8;
|
||||||
|
(memArray, loc) = (arrayData, 3);
|
||||||
|
if (loc != 3) return 9;
|
||||||
|
if (memArray.length != arrayData.length) return 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
compileAndRun(sourceCode);
|
||||||
|
BOOST_CHECK(callContractFunction("f(bytes)", u256(0x20), u256(5), string("abcde")) == encodeArgs(u256(0)));
|
||||||
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(destructuring_assignment_wildcard)
|
BOOST_AUTO_TEST_CASE(destructuring_assignment_wildcard)
|
||||||
{
|
{
|
||||||
char const* sourceCode = R"(
|
char const* sourceCode = R"(
|
||||||
@ -5732,6 +5768,7 @@ BOOST_AUTO_TEST_CASE(destructuring_assignment_wildcard)
|
|||||||
compileAndRun(sourceCode);
|
compileAndRun(sourceCode);
|
||||||
BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(0)));
|
BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE_END()
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user