mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #132 from chriseth/tupleExpression
Tuple expressions and destructuring assignments
This commit is contained in:
commit
52eaa477d4
@ -1067,6 +1067,30 @@ private:
|
||||
ASTPointer<Expression> 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<ASTPointer<Expression>> const& _components
|
||||
):
|
||||
Expression(_location), m_components(_components) {}
|
||||
virtual void accept(ASTVisitor& _visitor) override;
|
||||
virtual void accept(ASTConstVisitor& _visitor) const override;
|
||||
|
||||
std::vector<ASTPointer<Expression>> const& components() const { return m_components; }
|
||||
|
||||
private:
|
||||
std::vector<ASTPointer<Expression>> m_components;
|
||||
};
|
||||
|
||||
/**
|
||||
* Operation involving a unary operator, pre- or postfix.
|
||||
* Examples: ++i, delete x or !true
|
||||
|
@ -69,6 +69,7 @@ class VariableDeclarationStatement;
|
||||
class ExpressionStatement;
|
||||
class Expression;
|
||||
class Assignment;
|
||||
class TupleExpression;
|
||||
class UnaryOperation;
|
||||
class BinaryOperation;
|
||||
class FunctionCall;
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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--;
|
||||
|
@ -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;
|
||||
|
@ -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); }
|
||||
|
@ -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))
|
||||
|
@ -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<ASTPointer<VariableDeclaration>> 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<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)
|
||||
m_context << eth::Instruction::POP;
|
||||
@ -637,6 +644,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]);
|
||||
|
@ -550,6 +550,61 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Type::Category::Tuple:
|
||||
{
|
||||
TupleType const& sourceTuple = dynamic_cast<TupleType const&>(_typeOnStack);
|
||||
TupleType const& targetTuple = dynamic_cast<TupleType const&>(_targetType);
|
||||
// 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 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 ? targetType->sizeOnStack() : 0;
|
||||
if (!targetType || *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.
|
||||
for (unsigned j = 0; j < min(sourceSize, targetSize); ++j)
|
||||
m_context <<
|
||||
eth::swapInstruction(depth + targetSize - sourceSize) <<
|
||||
eth::Instruction::POP;
|
||||
// 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 +686,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)
|
||||
|
@ -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.
|
||||
|
@ -179,17 +179,12 @@ bool ExpressionCompiler::visit(Assignment const& _assignment)
|
||||
{
|
||||
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
|
||||
);
|
||||
utils().convertType(*_assignment.rightHandSide().annotation().type, *type);
|
||||
|
||||
_assignment.leftHandSide().accept(*this);
|
||||
solAssert(!!m_currentLValue, "LValue not retrieved.");
|
||||
@ -221,6 +216,26 @@ bool ExpressionCompiler::visit(Assignment const& _assignment)
|
||||
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)
|
||||
{
|
||||
CompilerContext::LocationSetter locationSetter(m_context, _unaryOperation);
|
||||
@ -649,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
|
||||
|
@ -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;
|
||||
|
@ -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<IntegerType const&>(m_dataType).isSigned()
|
||||
m_dataType->category() == Type::Category::Integer &&
|
||||
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
|
||||
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<FixedBytesType const&>(m_dataType).numBytes()))
|
||||
<< (u256(0x1) << (256 - 8 * dynamic_cast<FixedBytesType const&>(*m_dataType).numBytes()))
|
||||
<< eth::Instruction::SWAP1 << eth::Instruction::DIV;
|
||||
else if (
|
||||
m_dataType.category() == Type::Category::Integer &&
|
||||
dynamic_cast<IntegerType const&>(m_dataType).isSigned()
|
||||
m_dataType->category() == Type::Category::Integer &&
|
||||
dynamic_cast<IntegerType const&>(*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,24 @@ 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<ArrayType const&>(m_dataType),
|
||||
dynamic_cast<ArrayType const&>(_sourceType));
|
||||
dynamic_cast<ArrayType const&>(*m_dataType),
|
||||
dynamic_cast<ArrayType const&>(_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<StructType const&>(m_dataType);
|
||||
auto const& structType = dynamic_cast<StructType const&>(*m_dataType);
|
||||
auto const& sourceType = dynamic_cast<StructType const&>(_sourceType);
|
||||
solAssert(
|
||||
structType.structDefinition() == sourceType.structDefinition(),
|
||||
@ -313,18 +314,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<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
|
||||
// @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<StructType const&>(m_dataType);
|
||||
auto const& structType = dynamic_cast<StructType const&>(*m_dataType);
|
||||
for (auto const& member: structType.members())
|
||||
{
|
||||
// zero each member that is not a mapping
|
||||
@ -342,10 +343,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 +362,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 +375,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 +428,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 +456,91 @@ void StorageArrayLength::setToZero(SourceLocation const&, bool _removeReference)
|
||||
m_context << eth::Instruction::DUP1;
|
||||
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) const
|
||||
{
|
||||
// values are below the lvalue references
|
||||
unsigned valuePos = sizeOnStack();
|
||||
TypePointers const& valueTypes = dynamic_cast<TupleType const&>(_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)
|
||||
{
|
||||
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 == !lvalue, "");
|
||||
if (!lvalue)
|
||||
continue;
|
||||
valuePos += valType->sizeOnStack();
|
||||
// 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
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <libevmasm/SourceLocation.h>
|
||||
#include <libsolidity/ArrayUtils.h>
|
||||
|
||||
@ -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<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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -806,6 +806,7 @@ ASTPointer<VariableDeclarationStatement> Parser::parseVariableDeclarationStateme
|
||||
)
|
||||
{
|
||||
ASTNodeFactory varDeclNodeFactory(*this);
|
||||
varDeclNodeFactory.markEndPosition();
|
||||
ASTPointer<ASTString> name = expectIdentifierToken();
|
||||
var = varDeclNodeFactory.createNode<VariableDeclaration>(
|
||||
ASTPointer<TypeName>(),
|
||||
@ -1009,10 +1010,25 @@ ASTPointer<Expression> 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> expression = parseExpression();
|
||||
vector<ASTPointer<Expression>> 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<Expression>());
|
||||
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<TupleExpression>(components);
|
||||
}
|
||||
default:
|
||||
if (Token::isElementaryTypeName(token))
|
||||
|
@ -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<TupleType const*>(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<ReferenceType const*>(varDecl.annotation().type.get()))
|
||||
if (auto ref = dynamic_cast<ReferenceType const*>(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<TupleType const*>(_statement.initialValue()->annotation().type.get()))
|
||||
if (auto tupleType = dynamic_cast<TupleType const*>(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).
|
||||
@ -712,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<TupleType const*>(t.get()))
|
||||
{
|
||||
// Sequenced assignments of tuples is not valid, make the result a "void" type.
|
||||
_assignment.annotation().type = make_shared<TupleType>();
|
||||
expectType(_assignment.rightHandSide(), *tupleType);
|
||||
}
|
||||
else if (t->category() == Type::Category::Mapping)
|
||||
{
|
||||
typeError(_assignment, "Mappings cannot be assigned to.");
|
||||
_assignment.rightHandSide().accept(*this);
|
||||
@ -741,6 +765,51 @@ bool TypeChecker::visit(Assignment const& _assignment)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TypeChecker::visit(TupleExpression const& _tuple)
|
||||
{
|
||||
vector<ASTPointer<Expression>> 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<TupleType>(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<TupleType>(types);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TypeChecker::visit(UnaryOperation const& _operation)
|
||||
{
|
||||
// Inc, Dec, Add, Sub, Not, BitNot, Delete
|
||||
@ -1236,10 +1305,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)
|
||||
|
@ -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;
|
||||
|
@ -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<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
|
||||
{
|
||||
if (auto tupleType = dynamic_cast<TupleType const*>(&_other))
|
||||
@ -1252,12 +1284,12 @@ 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)
|
||||
str += t->toString(_short) + ", ";
|
||||
str.resize(str.size() - 2);
|
||||
for (auto const& t: components())
|
||||
str += (t ? t->toString(_short) : "") + ",";
|
||||
str.pop_back();
|
||||
return str + ")";
|
||||
}
|
||||
|
||||
@ -1272,11 +1304,35 @@ u256 TupleType::storageSize() const
|
||||
unsigned TupleType::sizeOnStack() const
|
||||
{
|
||||
unsigned size = 0;
|
||||
for (auto const& t: m_components)
|
||||
size += t->sizeOnStack();
|
||||
for (auto const& t: components())
|
||||
size += t ? t->sizeOnStack() : 0;
|
||||
return size;
|
||||
}
|
||||
|
||||
TypePointer TupleType::mobileType() const
|
||||
{
|
||||
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)
|
||||
{
|
||||
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[ti]);
|
||||
}
|
||||
return make_shared<TupleType>(tempComponents);
|
||||
}
|
||||
|
||||
FunctionType::FunctionType(FunctionDefinition const& _function, bool _isInternal):
|
||||
m_location(_isInternal ? Location::Internal : Location::External),
|
||||
m_isConstant(_function.isDeclaredConst()),
|
||||
@ -1638,14 +1694,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<string> 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<FunctionType>(
|
||||
parameterTypes,
|
||||
returnParameterTypes,
|
||||
|
@ -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; }
|
||||
@ -682,12 +689,14 @@ 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
|
||||
{
|
||||
public:
|
||||
virtual Category category() const override { return Category::Tuple; }
|
||||
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 TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); }
|
||||
virtual std::string toString(bool) const override;
|
||||
@ -695,6 +704,9 @@ public:
|
||||
virtual u256 storageSize() const override;
|
||||
virtual bool canLiveOutsideStorage() const override { return false; }
|
||||
virtual unsigned sizeOnStack() 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; }
|
||||
|
||||
|
@ -5677,6 +5677,101 @@ BOOST_AUTO_TEST_CASE(multi_variable_declaration)
|
||||
BOOST_CHECK(callContractFunction("f()", encodeArgs()) == encodeArgs(true));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(tuples)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
contract C {
|
||||
uint[] data;
|
||||
function g() internal returns (uint a, uint b, uint[] storage c) {
|
||||
return (1, 2, data);
|
||||
}
|
||||
function h() external returns (uint a, uint b) {
|
||||
return (5, 6);
|
||||
}
|
||||
function f() returns (uint) {
|
||||
data.length = 1;
|
||||
data[0] = 3;
|
||||
uint a; uint b;
|
||||
(a, b) = this.h();
|
||||
if (a != 5 || b != 6) return 1;
|
||||
uint[] storage c;
|
||||
(a, b, c) = g();
|
||||
if (a != 1 || b != 2 || c[0] != 3) return 2;
|
||||
(a, b) = (b, a);
|
||||
if (a != 2 || b != 1) return 3;
|
||||
(a, , b, ) = (8, 9, 10, 11, 12);
|
||||
if (a != 8 || b != 10) return 4;
|
||||
}
|
||||
}
|
||||
)";
|
||||
compileAndRun(sourceCode);
|
||||
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[] memory 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;
|
||||
bytes memory memBytes;
|
||||
(x, memBytes, y[2], ) = (456, s, 789, 101112, 131415);
|
||||
if (x != 456 || memBytes.length != s.length || y[2] != 789) return 11;
|
||||
}
|
||||
}
|
||||
)";
|
||||
compileAndRun(sourceCode);
|
||||
BOOST_CHECK(callContractFunction("f(bytes)", u256(0x20), u256(5), string("abcde")) == encodeArgs(u256(0)));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(destructuring_assignment_wildcard)
|
||||
{
|
||||
char const* sourceCode = R"(
|
||||
contract C {
|
||||
function f() returns (uint) {
|
||||
uint a;
|
||||
uint b;
|
||||
uint c;
|
||||
(a,) = (1,);
|
||||
if (a != 1) return 1;
|
||||
(,b) = (2,3,4);
|
||||
if (b != 4) return 2;
|
||||
(, c,) = (5,6,7);
|
||||
if (c != 6) return 3;
|
||||
(a, b,) = (11, 12, 13);
|
||||
if (a != 11 || b != 12) return 4;
|
||||
(, a, b) = (11, 12, 13);
|
||||
if (a != 12 || b != 13) return 5;
|
||||
}
|
||||
}
|
||||
)";
|
||||
compileAndRun(sourceCode);
|
||||
BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(0)));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
}
|
||||
|
@ -109,13 +109,9 @@ ASTPointer<SourceUnit> parseAndAnalyse(string const& _source)
|
||||
return sourceAndError.first;
|
||||
}
|
||||
|
||||
bool success(std::string const& _source)
|
||||
bool success(string const& _source)
|
||||
{
|
||||
auto sourceAndError = parseAnalyseAndReturnError(_source);
|
||||
|
||||
if (sourceAndError.second && *sourceAndError.second == Error::Type::TypeError)
|
||||
return false;
|
||||
return true;
|
||||
return !parseAnalyseAndReturnError(_source).second;
|
||||
}
|
||||
|
||||
Error::Type expectError(std::string const& _source, bool _warning = false)
|
||||
@ -2460,6 +2456,33 @@ BOOST_AUTO_TEST_CASE(multi_variable_declaration_wildcards_fail_4)
|
||||
BOOST_CHECK(expectError(text) == Error::Type::TypeError);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(tuples)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract C {
|
||||
function f() {
|
||||
uint a = (1);
|
||||
var (b,) = (1,);
|
||||
var (c,d) = (1, 2 + a);
|
||||
var (e,) = (1, 2, b);
|
||||
}
|
||||
}
|
||||
)";
|
||||
BOOST_CHECK(success(text));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(tuples_empty_components)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract C {
|
||||
function f() {
|
||||
(1,,2);
|
||||
}
|
||||
}
|
||||
)";
|
||||
BOOST_CHECK(expectError(text) == Error::Type::TypeError);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(multi_variable_declaration_wildcards_fail_5)
|
||||
{
|
||||
char const* text = R"(
|
||||
|
@ -983,7 +983,7 @@ BOOST_AUTO_TEST_CASE(local_const_variable)
|
||||
BOOST_AUTO_TEST_CASE(multi_variable_declaration)
|
||||
{
|
||||
char const* text = R"(
|
||||
library Lib {
|
||||
contract C {
|
||||
function f() {
|
||||
var (a,b,c) = g();
|
||||
var (d) = 2;
|
||||
@ -1000,6 +1000,21 @@ BOOST_AUTO_TEST_CASE(multi_variable_declaration)
|
||||
BOOST_CHECK(successParse(text));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(tuples)
|
||||
{
|
||||
char const* text = R"(
|
||||
contract C {
|
||||
function f() {
|
||||
uint a = (1);
|
||||
var (b,) = (1,);
|
||||
var (c,d) = (1, 2 + a);
|
||||
var (e,) = (1, 2, b);
|
||||
}
|
||||
}
|
||||
)";
|
||||
BOOST_CHECK(successParse(text));
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user