aboutsummaryrefslogtreecommitdiffstats
path: root/libsolidity
diff options
context:
space:
mode:
authorchriseth <c@ethdev.com>2015-10-16 16:01:34 +0800
committerchriseth <c@ethdev.com>2015-10-16 16:01:34 +0800
commit52eaa477d4bd9ad2f591148727d1ac9fd500d283 (patch)
treeaa45f8f99f9b9d7bc530edeb682727449aacb78e /libsolidity
parent2ea5b2431fc360904f193f61d974acf24fff1326 (diff)
parent2920a32ae81539a10af54e275da72111b792568e (diff)
downloaddexon-solidity-52eaa477d4bd9ad2f591148727d1ac9fd500d283.tar.gz
dexon-solidity-52eaa477d4bd9ad2f591148727d1ac9fd500d283.tar.zst
dexon-solidity-52eaa477d4bd9ad2f591148727d1ac9fd500d283.zip
Merge pull request #132 from chriseth/tupleExpression
Tuple expressions and destructuring assignments
Diffstat (limited to 'libsolidity')
-rw-r--r--libsolidity/AST.h24
-rw-r--r--libsolidity/ASTForward.h1
-rw-r--r--libsolidity/ASTJsonConverter.cpp11
-rw-r--r--libsolidity/ASTJsonConverter.h2
-rw-r--r--libsolidity/ASTPrinter.cpp13
-rw-r--r--libsolidity/ASTPrinter.h2
-rw-r--r--libsolidity/ASTVisitor.h4
-rw-r--r--libsolidity/AST_accept.h18
-rw-r--r--libsolidity/Compiler.cpp16
-rw-r--r--libsolidity/CompilerUtils.cpp69
-rw-r--r--libsolidity/CompilerUtils.h9
-rw-r--r--libsolidity/ExpressionCompiler.cpp42
-rw-r--r--libsolidity/ExpressionCompiler.h1
-rw-r--r--libsolidity/LValue.cpp197
-rw-r--r--libsolidity/LValue.h31
-rw-r--r--libsolidity/Parser.cpp20
-rw-r--r--libsolidity/TypeChecker.cpp83
-rw-r--r--libsolidity/TypeChecker.h1
-rw-r--r--libsolidity/Types.cpp81
-rw-r--r--libsolidity/Types.h12
20 files changed, 532 insertions, 105 deletions
diff --git a/libsolidity/AST.h b/libsolidity/AST.h
index 9dd67cc2..fc1db3f3 100644
--- a/libsolidity/AST.h
+++ b/libsolidity/AST.h
@@ -1068,6 +1068,30 @@ private:
};
/**
+ * 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
*/
diff --git a/libsolidity/ASTForward.h b/libsolidity/ASTForward.h
index 396cf50a..02dd054a 100644
--- a/libsolidity/ASTForward.h
+++ b/libsolidity/ASTForward.h
@@ -69,6 +69,7 @@ class VariableDeclarationStatement;
class ExpressionStatement;
class Expression;
class Assignment;
+class TupleExpression;
class UnaryOperation;
class BinaryOperation;
class FunctionCall;
diff --git a/libsolidity/ASTJsonConverter.cpp b/libsolidity/ASTJsonConverter.cpp
index 4c14f2b2..34012c73 100644
--- a/libsolidity/ASTJsonConverter.cpp
+++ b/libsolidity/ASTJsonConverter.cpp
@@ -226,6 +226,12 @@ bool ASTJsonConverter::visit(Assignment const& _node)
return true;
}
+bool ASTJsonConverter::visit(TupleExpression const&)
+{
+ addJsonNode("TupleExpression",{}, true);
+ return true;
+}
+
bool ASTJsonConverter::visit(UnaryOperation const& _node)
{
addJsonNode("UnaryOperation",
@@ -396,6 +402,11 @@ void ASTJsonConverter::endVisit(Assignment const&)
goUp();
}
+void ASTJsonConverter::endVisit(TupleExpression const&)
+{
+ goUp();
+}
+
void ASTJsonConverter::endVisit(UnaryOperation const&)
{
goUp();
diff --git a/libsolidity/ASTJsonConverter.h b/libsolidity/ASTJsonConverter.h
index 61f87860..a62259e2 100644
--- a/libsolidity/ASTJsonConverter.h
+++ b/libsolidity/ASTJsonConverter.h
@@ -68,6 +68,7 @@ public:
bool visit(VariableDeclarationStatement const& _node) override;
bool visit(ExpressionStatement const& _node) override;
bool visit(Assignment const& _node) override;
+ bool visit(TupleExpression const& _node) override;
bool visit(UnaryOperation const& _node) override;
bool visit(BinaryOperation const& _node) override;
bool visit(FunctionCall const& _node) override;
@@ -99,6 +100,7 @@ public:
void endVisit(VariableDeclarationStatement const&) override;
void endVisit(ExpressionStatement const&) override;
void endVisit(Assignment const&) override;
+ void endVisit(TupleExpression const&) override;
void endVisit(UnaryOperation const&) override;
void endVisit(BinaryOperation const&) override;
void endVisit(FunctionCall const&) override;
diff --git a/libsolidity/ASTPrinter.cpp b/libsolidity/ASTPrinter.cpp
index 534f7c78..cb231842 100644
--- a/libsolidity/ASTPrinter.cpp
+++ b/libsolidity/ASTPrinter.cpp
@@ -256,6 +256,14 @@ bool ASTPrinter::visit(Assignment const& _node)
return goDeeper();
}
+bool ASTPrinter::visit(TupleExpression const& _node)
+{
+ writeLine(string("TupleExpression"));
+ printType(_node);
+ printSourcePart(_node);
+ return goDeeper();
+}
+
bool ASTPrinter::visit(UnaryOperation const& _node)
{
writeLine(string("UnaryOperation (") + (_node.isPrefixOperation() ? "prefix" : "postfix") +
@@ -477,6 +485,11 @@ void ASTPrinter::endVisit(Assignment const&)
m_indentation--;
}
+void ASTPrinter::endVisit(TupleExpression const&)
+{
+ m_indentation--;
+}
+
void ASTPrinter::endVisit(UnaryOperation const&)
{
m_indentation--;
diff --git a/libsolidity/ASTPrinter.h b/libsolidity/ASTPrinter.h
index a12ec0aa..95656436 100644
--- a/libsolidity/ASTPrinter.h
+++ b/libsolidity/ASTPrinter.h
@@ -76,6 +76,7 @@ public:
bool visit(VariableDeclarationStatement const& _node) override;
bool visit(ExpressionStatement const& _node) override;
bool visit(Assignment const& _node) override;
+ bool visit(TupleExpression const& _node) override;
bool visit(UnaryOperation const& _node) override;
bool visit(BinaryOperation const& _node) override;
bool visit(FunctionCall const& _node) override;
@@ -115,6 +116,7 @@ public:
void endVisit(VariableDeclarationStatement const&) override;
void endVisit(ExpressionStatement const&) override;
void endVisit(Assignment const&) override;
+ void endVisit(TupleExpression const&) override;
void endVisit(UnaryOperation const&) override;
void endVisit(BinaryOperation const&) override;
void endVisit(FunctionCall const&) override;
diff --git a/libsolidity/ASTVisitor.h b/libsolidity/ASTVisitor.h
index e665396c..3e50fb28 100644
--- a/libsolidity/ASTVisitor.h
+++ b/libsolidity/ASTVisitor.h
@@ -73,6 +73,7 @@ public:
virtual bool visit(VariableDeclarationStatement& _node) { return visitNode(_node); }
virtual bool visit(ExpressionStatement& _node) { return visitNode(_node); }
virtual bool visit(Assignment& _node) { return visitNode(_node); }
+ virtual bool visit(TupleExpression& _node) { return visitNode(_node); }
virtual bool visit(UnaryOperation& _node) { return visitNode(_node); }
virtual bool visit(BinaryOperation& _node) { return visitNode(_node); }
virtual bool visit(FunctionCall& _node) { return visitNode(_node); }
@@ -113,6 +114,7 @@ public:
virtual void endVisit(VariableDeclarationStatement& _node) { endVisitNode(_node); }
virtual void endVisit(ExpressionStatement& _node) { endVisitNode(_node); }
virtual void endVisit(Assignment& _node) { endVisitNode(_node); }
+ virtual void endVisit(TupleExpression& _node) { endVisitNode(_node); }
virtual void endVisit(UnaryOperation& _node) { endVisitNode(_node); }
virtual void endVisit(BinaryOperation& _node) { endVisitNode(_node); }
virtual void endVisit(FunctionCall& _node) { endVisitNode(_node); }
@@ -165,6 +167,7 @@ public:
virtual bool visit(VariableDeclarationStatement const& _node) { return visitNode(_node); }
virtual bool visit(ExpressionStatement const& _node) { return visitNode(_node); }
virtual bool visit(Assignment const& _node) { return visitNode(_node); }
+ virtual bool visit(TupleExpression const& _node) { return visitNode(_node); }
virtual bool visit(UnaryOperation const& _node) { return visitNode(_node); }
virtual bool visit(BinaryOperation const& _node) { return visitNode(_node); }
virtual bool visit(FunctionCall const& _node) { return visitNode(_node); }
@@ -205,6 +208,7 @@ public:
virtual void endVisit(VariableDeclarationStatement const& _node) { endVisitNode(_node); }
virtual void endVisit(ExpressionStatement const& _node) { endVisitNode(_node); }
virtual void endVisit(Assignment const& _node) { endVisitNode(_node); }
+ virtual void endVisit(TupleExpression const& _node) { endVisitNode(_node); }
virtual void endVisit(UnaryOperation const& _node) { endVisitNode(_node); }
virtual void endVisit(BinaryOperation const& _node) { endVisitNode(_node); }
virtual void endVisit(FunctionCall const& _node) { endVisitNode(_node); }
diff --git a/libsolidity/AST_accept.h b/libsolidity/AST_accept.h
index 994bfc8d..eb1f6098 100644
--- a/libsolidity/AST_accept.h
+++ b/libsolidity/AST_accept.h
@@ -559,6 +559,24 @@ void Assignment::accept(ASTConstVisitor& _visitor) const
_visitor.endVisit(*this);
}
+void TupleExpression::accept(ASTVisitor& _visitor)
+{
+ if (_visitor.visit(*this))
+ for (auto const& component: m_components)
+ if (component)
+ component->accept(_visitor);
+ _visitor.endVisit(*this);
+}
+
+void TupleExpression::accept(ASTConstVisitor& _visitor) const
+{
+ if (_visitor.visit(*this))
+ for (auto const& component: m_components)
+ if (component)
+ component->accept(_visitor);
+ _visitor.endVisit(*this);
+}
+
void UnaryOperation::accept(ASTVisitor& _visitor)
{
if (_visitor.visit(*this))
diff --git a/libsolidity/Compiler.cpp b/libsolidity/Compiler.cpp
index 7c6c7831..679704ba 100644
--- a/libsolidity/Compiler.cpp
+++ b/libsolidity/Compiler.cpp
@@ -597,13 +597,20 @@ bool Compiler::visit(Break const& _breakStatement)
bool Compiler::visit(Return const& _return)
{
CompilerContext::LocationSetter locationSetter(m_context, _return);
- //@todo modifications are needed to make this work with functions returning multiple values
if (Expression const* expression = _return.expression())
{
solAssert(_return.annotation().functionReturnParameters, "Invalid return parameters pointer.");
- VariableDeclaration const& firstVariable = *_return.annotation().functionReturnParameters->parameters().front();
- compileExpression(*expression, firstVariable.annotation().type);
- CompilerUtils(m_context).moveToStackVariable(firstVariable);
+ vector<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]);
diff --git a/libsolidity/CompilerUtils.cpp b/libsolidity/CompilerUtils.cpp
index e1152202..f0dea708 100644
--- a/libsolidity/CompilerUtils.cpp
+++ b/libsolidity/CompilerUtils.cpp
@@ -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)
diff --git a/libsolidity/CompilerUtils.h b/libsolidity/CompilerUtils.h
index f335eed5..01b9f422 100644
--- a/libsolidity/CompilerUtils.h
+++ b/libsolidity/CompilerUtils.h
@@ -124,10 +124,11 @@ public:
/// Copies an item that occupies @a _itemSize stack slots from a stack depth of @a _stackDepth
/// to the top of the stack.
void copyToStackTop(unsigned _stackDepth, unsigned _itemSize);
- /// Moves a single stack element (with _stackDepth items on top of it) to the top of the stack.
- void moveToStackTop(unsigned _stackDepth);
- /// Moves a single stack element past @a _stackDepth other stack elements
- void moveIntoStack(unsigned _stackDepth);
+ /// Moves an item that occupies @a _itemSize stack slots and has items occupying @a _stackDepth
+ /// slots above it to the top of the stack.
+ void moveToStackTop(unsigned _stackDepth, unsigned _itemSize = 1);
+ /// Moves @a _itemSize elements past @a _stackDepth other stack elements
+ void moveIntoStack(unsigned _stackDepth, unsigned _itemSize = 1);
/// Removes the current value from the top of the stack.
void popStackElement(Type const& _type);
/// Removes element from the top of the stack _amount times.
diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp
index 85302afc..fde88a00 100644
--- a/libsolidity/ExpressionCompiler.cpp
+++ b/libsolidity/ExpressionCompiler.cpp
@@ -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
diff --git a/libsolidity/ExpressionCompiler.h b/libsolidity/ExpressionCompiler.h
index d8fef5af..44d27ea2 100644
--- a/libsolidity/ExpressionCompiler.h
+++ b/libsolidity/ExpressionCompiler.h
@@ -72,6 +72,7 @@ public:
private:
virtual bool visit(Assignment const& _assignment) override;
+ virtual bool visit(TupleExpression const& _tuple) override;
virtual bool visit(UnaryOperation const& _unaryOperation) override;
virtual bool visit(BinaryOperation const& _binaryOperation) override;
virtual bool visit(FunctionCall const& _functionCall) override;
diff --git a/libsolidity/LValue.cpp b/libsolidity/LValue.cpp
index 9f33e846..ac04ebef 100644
--- a/libsolidity/LValue.cpp
+++ b/libsolidity/LValue.cpp
@@ -32,9 +32,9 @@ using namespace solidity;
StackVariable::StackVariable(CompilerContext& _compilerContext, VariableDeclaration const& _declaration):
- LValue(_compilerContext, *_declaration.annotation().type),
+ LValue(_compilerContext, _declaration.annotation().type.get()),
m_baseStackOffset(m_context.baseStackOffsetOfVariable(_declaration)),
- m_size(m_dataType.sizeOnStack())
+ m_size(m_dataType->sizeOnStack())
{
}
@@ -70,23 +70,23 @@ void StackVariable::storeValue(Type const&, SourceLocation const& _location, boo
void StackVariable::setToZero(SourceLocation const& _location, bool) const
{
- CompilerUtils(m_context).pushZeroValue(m_dataType);
- storeValue(m_dataType, _location, true);
+ CompilerUtils(m_context).pushZeroValue(*m_dataType);
+ storeValue(*m_dataType, _location, true);
}
MemoryItem::MemoryItem(CompilerContext& _compilerContext, Type const& _type, bool _padded):
- LValue(_compilerContext, _type),
+ LValue(_compilerContext, &_type),
m_padded(_padded)
{
}
void MemoryItem::retrieveValue(SourceLocation const&, bool _remove) const
{
- if (m_dataType.isValueType())
+ if (m_dataType->isValueType())
{
if (!_remove)
m_context << eth::Instruction::DUP1;
- CompilerUtils(m_context).loadFromMemoryDynamic(m_dataType, false, m_padded, false);
+ CompilerUtils(m_context).loadFromMemoryDynamic(*m_dataType, false, m_padded, false);
}
else
m_context << eth::Instruction::MLOAD;
@@ -95,24 +95,24 @@ void MemoryItem::retrieveValue(SourceLocation const&, bool _remove) const
void MemoryItem::storeValue(Type const& _sourceType, SourceLocation const&, bool _move) const
{
CompilerUtils utils(m_context);
- if (m_dataType.isValueType())
+ if (m_dataType->isValueType())
{
solAssert(_sourceType.isValueType(), "");
utils.moveIntoStack(_sourceType.sizeOnStack());
- utils.convertType(_sourceType, m_dataType, true);
+ utils.convertType(_sourceType, *m_dataType, true);
if (!_move)
{
- utils.moveToStackTop(m_dataType.sizeOnStack());
- utils.copyToStackTop(2, m_dataType.sizeOnStack());
+ utils.moveToStackTop(m_dataType->sizeOnStack());
+ utils.copyToStackTop(2, m_dataType->sizeOnStack());
}
- utils.storeInMemoryDynamic(m_dataType, m_padded);
+ utils.storeInMemoryDynamic(*m_dataType, m_padded);
m_context << eth::Instruction::POP;
}
else
{
- solAssert(_sourceType == m_dataType, "Conversion not implemented for assignment to memory.");
+ solAssert(_sourceType == *m_dataType, "Conversion not implemented for assignment to memory.");
- solAssert(m_dataType.sizeOnStack() == 1, "");
+ solAssert(m_dataType->sizeOnStack() == 1, "");
if (!_move)
m_context << eth::Instruction::DUP2 << eth::Instruction::SWAP1;
// stack: [value] value lvalue
@@ -126,8 +126,8 @@ void MemoryItem::setToZero(SourceLocation const&, bool _removeReference) const
CompilerUtils utils(m_context);
if (!_removeReference)
m_context << eth::Instruction::DUP1;
- utils.pushZeroValue(m_dataType);
- utils.storeInMemoryDynamic(m_dataType, m_padded);
+ utils.pushZeroValue(*m_dataType);
+ utils.storeInMemoryDynamic(*m_dataType, m_padded);
m_context << eth::Instruction::POP;
}
@@ -139,21 +139,21 @@ StorageItem::StorageItem(CompilerContext& _compilerContext, VariableDeclaration
}
StorageItem::StorageItem(CompilerContext& _compilerContext, Type const& _type):
- LValue(_compilerContext, _type)
+ LValue(_compilerContext, &_type)
{
- if (m_dataType.isValueType())
+ if (m_dataType->isValueType())
{
- solAssert(m_dataType.storageSize() == m_dataType.sizeOnStack(), "");
- solAssert(m_dataType.storageSize() == 1, "Invalid storage size.");
+ solAssert(m_dataType->storageSize() == m_dataType->sizeOnStack(), "");
+ solAssert(m_dataType->storageSize() == 1, "Invalid storage size.");
}
}
void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const
{
// stack: storage_key storage_offset
- if (!m_dataType.isValueType())
+ if (!m_dataType->isValueType())
{
- solAssert(m_dataType.sizeOnStack() == 1, "Invalid storage ref size.");
+ solAssert(m_dataType->sizeOnStack() == 1, "Invalid storage ref size.");
if (_remove)
m_context << eth::Instruction::POP; // remove byte offset
else
@@ -162,22 +162,22 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const
}
if (!_remove)
CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack());
- if (m_dataType.storageBytes() == 32)
+ if (m_dataType->storageBytes() == 32)
m_context << eth::Instruction::POP << eth::Instruction::SLOAD;
else
{
m_context
<< eth::Instruction::SWAP1 << eth::Instruction::SLOAD << eth::Instruction::SWAP1
<< u256(0x100) << eth::Instruction::EXP << eth::Instruction::SWAP1 << eth::Instruction::DIV;
- if (m_dataType.category() == Type::Category::FixedBytes)
- m_context << (u256(0x1) << (256 - 8 * m_dataType.storageBytes())) << eth::Instruction::MUL;
+ if (m_dataType->category() == Type::Category::FixedBytes)
+ m_context << (u256(0x1) << (256 - 8 * m_dataType->storageBytes())) << eth::Instruction::MUL;
else if (
- m_dataType.category() == Type::Category::Integer &&
- dynamic_cast<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();
+ }
+ }
+}
diff --git a/libsolidity/LValue.h b/libsolidity/LValue.h
index cbbfb102..94c8d3b8 100644
--- a/libsolidity/LValue.h
+++ b/libsolidity/LValue.h
@@ -23,6 +23,7 @@
#pragma once
#include <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;
+};
+
}
}
diff --git a/libsolidity/Parser.cpp b/libsolidity/Parser.cpp
index ab2d0094..1bb16b84 100644
--- a/libsolidity/Parser.cpp
+++ b/libsolidity/Parser.cpp
@@ -806,6 +806,7 @@ ASTPointer<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))
diff --git a/libsolidity/TypeChecker.cpp b/libsolidity/TypeChecker.cpp
index ca5b1eb7..dcaecdfb 100644
--- a/libsolidity/TypeChecker.cpp
+++ b/libsolidity/TypeChecker.cpp
@@ -560,13 +560,31 @@ void TypeChecker::endVisit(Return const& _return)
return;
ParameterList const* params = _return.annotation().functionReturnParameters;
if (!params)
+ {
typeError(_return, "Return arguments not allowed.");
+ return;
+ }
+ TypePointers returnTypes;
+ for (auto const& var: params->parameters())
+ returnTypes.push_back(type(*var));
+ if (auto tupleType = dynamic_cast<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)
diff --git a/libsolidity/TypeChecker.h b/libsolidity/TypeChecker.h
index d9cb39ae..7af5473b 100644
--- a/libsolidity/TypeChecker.h
+++ b/libsolidity/TypeChecker.h
@@ -90,6 +90,7 @@ private:
virtual bool visit(VariableDeclarationStatement const& _variable) override;
virtual void endVisit(ExpressionStatement const& _statement) override;
virtual bool visit(Assignment const& _assignment) override;
+ virtual bool visit(TupleExpression const& _tuple) override;
virtual void endVisit(BinaryOperation const& _operation) override;
virtual bool visit(UnaryOperation const& _operation) override;
virtual bool visit(FunctionCall const& _functionCall) override;
diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp
index 51df5fbf..02b86a7f 100644
--- a/libsolidity/Types.cpp
+++ b/libsolidity/Types.cpp
@@ -1242,6 +1242,38 @@ unsigned int EnumType::memberValue(ASTString const& _member) const
BOOST_THROW_EXCEPTION(m_enum.createTypeError("Requested unknown enum value ." + _member));
}
+bool TupleType::isImplicitlyConvertibleTo(Type const& _other) const
+{
+ if (auto tupleType = dynamic_cast<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,
diff --git a/libsolidity/Types.h b/libsolidity/Types.h
index 5c4aacdc..626ebbe4 100644
--- a/libsolidity/Types.h
+++ b/libsolidity/Types.h
@@ -210,6 +210,13 @@ public:
/// @returns true if this is a non-value type and the data of this type is stored at the
/// given location.
virtual bool dataStoredIn(DataLocation) const { return false; }
+ /// @returns the type of a temporary during assignment to a variable of the given type.
+ /// Specifically, returns the requested itself if it can be dynamically allocated (or is a value type)
+ /// and the mobile type otherwise.
+ virtual TypePointer closestTemporaryType(TypePointer const& _targetType) const
+ {
+ return _targetType->dataStoredIn(DataLocation::Storage) ? mobileType() : _targetType;
+ }
/// Returns the list of all members of this type. Default implementation: no members.
virtual MemberList const& members() const { return EmptyMemberList; }
@@ -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; }