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; | 	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. |  * Operation involving a unary operator, pre- or postfix. | ||||||
|  * Examples: ++i, delete x or !true |  * Examples: ++i, delete x or !true | ||||||
|  | |||||||
| @ -69,6 +69,7 @@ class VariableDeclarationStatement; | |||||||
| class ExpressionStatement; | class ExpressionStatement; | ||||||
| class Expression; | class Expression; | ||||||
| class Assignment; | class Assignment; | ||||||
|  | class TupleExpression; | ||||||
| class UnaryOperation; | class UnaryOperation; | ||||||
| class BinaryOperation; | class BinaryOperation; | ||||||
| class FunctionCall; | class FunctionCall; | ||||||
|  | |||||||
| @ -226,6 +226,12 @@ bool ASTJsonConverter::visit(Assignment const& _node) | |||||||
| 	return true; | 	return true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | bool ASTJsonConverter::visit(TupleExpression const&) | ||||||
|  | { | ||||||
|  | 	addJsonNode("TupleExpression",{}, true); | ||||||
|  | 	return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| bool ASTJsonConverter::visit(UnaryOperation const& _node) | bool ASTJsonConverter::visit(UnaryOperation const& _node) | ||||||
| { | { | ||||||
| 	addJsonNode("UnaryOperation", | 	addJsonNode("UnaryOperation", | ||||||
| @ -396,6 +402,11 @@ void ASTJsonConverter::endVisit(Assignment const&) | |||||||
| 	goUp(); | 	goUp(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void ASTJsonConverter::endVisit(TupleExpression const&) | ||||||
|  | { | ||||||
|  | 	goUp(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void ASTJsonConverter::endVisit(UnaryOperation const&) | void ASTJsonConverter::endVisit(UnaryOperation const&) | ||||||
| { | { | ||||||
| 	goUp(); | 	goUp(); | ||||||
|  | |||||||
| @ -68,6 +68,7 @@ public: | |||||||
| 	bool visit(VariableDeclarationStatement const& _node) override; | 	bool visit(VariableDeclarationStatement const& _node) override; | ||||||
| 	bool visit(ExpressionStatement const& _node) override; | 	bool visit(ExpressionStatement const& _node) override; | ||||||
| 	bool visit(Assignment const& _node) override; | 	bool visit(Assignment const& _node) override; | ||||||
|  | 	bool visit(TupleExpression const& _node) override; | ||||||
| 	bool visit(UnaryOperation const& _node) override; | 	bool visit(UnaryOperation const& _node) override; | ||||||
| 	bool visit(BinaryOperation const& _node) override; | 	bool visit(BinaryOperation const& _node) override; | ||||||
| 	bool visit(FunctionCall const& _node) override; | 	bool visit(FunctionCall const& _node) override; | ||||||
| @ -99,6 +100,7 @@ public: | |||||||
| 	void endVisit(VariableDeclarationStatement const&) override; | 	void endVisit(VariableDeclarationStatement const&) override; | ||||||
| 	void endVisit(ExpressionStatement const&) override; | 	void endVisit(ExpressionStatement const&) override; | ||||||
| 	void endVisit(Assignment const&) override; | 	void endVisit(Assignment const&) override; | ||||||
|  | 	void endVisit(TupleExpression const&) override; | ||||||
| 	void endVisit(UnaryOperation const&) override; | 	void endVisit(UnaryOperation const&) override; | ||||||
| 	void endVisit(BinaryOperation const&) override; | 	void endVisit(BinaryOperation const&) override; | ||||||
| 	void endVisit(FunctionCall const&) override; | 	void endVisit(FunctionCall const&) override; | ||||||
|  | |||||||
| @ -256,6 +256,14 @@ bool ASTPrinter::visit(Assignment const& _node) | |||||||
| 	return goDeeper(); | 	return goDeeper(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | bool ASTPrinter::visit(TupleExpression const& _node) | ||||||
|  | { | ||||||
|  | 	writeLine(string("TupleExpression")); | ||||||
|  | 	printType(_node); | ||||||
|  | 	printSourcePart(_node); | ||||||
|  | 	return goDeeper(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| bool ASTPrinter::visit(UnaryOperation const& _node) | bool ASTPrinter::visit(UnaryOperation const& _node) | ||||||
| { | { | ||||||
| 	writeLine(string("UnaryOperation (") + (_node.isPrefixOperation() ? "prefix" : "postfix") + | 	writeLine(string("UnaryOperation (") + (_node.isPrefixOperation() ? "prefix" : "postfix") + | ||||||
| @ -477,6 +485,11 @@ void ASTPrinter::endVisit(Assignment const&) | |||||||
| 	m_indentation--; | 	m_indentation--; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void ASTPrinter::endVisit(TupleExpression const&) | ||||||
|  | { | ||||||
|  | 	m_indentation--; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void ASTPrinter::endVisit(UnaryOperation const&) | void ASTPrinter::endVisit(UnaryOperation const&) | ||||||
| { | { | ||||||
| 	m_indentation--; | 	m_indentation--; | ||||||
|  | |||||||
| @ -76,6 +76,7 @@ public: | |||||||
| 	bool visit(VariableDeclarationStatement const& _node) override; | 	bool visit(VariableDeclarationStatement const& _node) override; | ||||||
| 	bool visit(ExpressionStatement const& _node) override; | 	bool visit(ExpressionStatement const& _node) override; | ||||||
| 	bool visit(Assignment const& _node) override; | 	bool visit(Assignment const& _node) override; | ||||||
|  | 	bool visit(TupleExpression const& _node) override; | ||||||
| 	bool visit(UnaryOperation const& _node) override; | 	bool visit(UnaryOperation const& _node) override; | ||||||
| 	bool visit(BinaryOperation const& _node) override; | 	bool visit(BinaryOperation const& _node) override; | ||||||
| 	bool visit(FunctionCall const& _node) override; | 	bool visit(FunctionCall const& _node) override; | ||||||
| @ -115,6 +116,7 @@ public: | |||||||
| 	void endVisit(VariableDeclarationStatement const&) override; | 	void endVisit(VariableDeclarationStatement const&) override; | ||||||
| 	void endVisit(ExpressionStatement const&) override; | 	void endVisit(ExpressionStatement const&) override; | ||||||
| 	void endVisit(Assignment const&) override; | 	void endVisit(Assignment const&) override; | ||||||
|  | 	void endVisit(TupleExpression const&) override; | ||||||
| 	void endVisit(UnaryOperation const&) override; | 	void endVisit(UnaryOperation const&) override; | ||||||
| 	void endVisit(BinaryOperation const&) override; | 	void endVisit(BinaryOperation const&) override; | ||||||
| 	void endVisit(FunctionCall const&) override; | 	void endVisit(FunctionCall const&) override; | ||||||
|  | |||||||
| @ -73,6 +73,7 @@ public: | |||||||
| 	virtual bool visit(VariableDeclarationStatement& _node) { return visitNode(_node); } | 	virtual bool visit(VariableDeclarationStatement& _node) { return visitNode(_node); } | ||||||
| 	virtual bool visit(ExpressionStatement& _node) { return visitNode(_node); } | 	virtual bool visit(ExpressionStatement& _node) { return visitNode(_node); } | ||||||
| 	virtual bool visit(Assignment& _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(UnaryOperation& _node) { return visitNode(_node); } | ||||||
| 	virtual bool visit(BinaryOperation& _node) { return visitNode(_node); } | 	virtual bool visit(BinaryOperation& _node) { return visitNode(_node); } | ||||||
| 	virtual bool visit(FunctionCall& _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(VariableDeclarationStatement& _node) { endVisitNode(_node); } | ||||||
| 	virtual void endVisit(ExpressionStatement& _node) { endVisitNode(_node); } | 	virtual void endVisit(ExpressionStatement& _node) { endVisitNode(_node); } | ||||||
| 	virtual void endVisit(Assignment& _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(UnaryOperation& _node) { endVisitNode(_node); } | ||||||
| 	virtual void endVisit(BinaryOperation& _node) { endVisitNode(_node); } | 	virtual void endVisit(BinaryOperation& _node) { endVisitNode(_node); } | ||||||
| 	virtual void endVisit(FunctionCall& _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(VariableDeclarationStatement const& _node) { return visitNode(_node); } | ||||||
| 	virtual bool visit(ExpressionStatement 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(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(UnaryOperation const& _node) { return visitNode(_node); } | ||||||
| 	virtual bool visit(BinaryOperation const& _node) { return visitNode(_node); } | 	virtual bool visit(BinaryOperation const& _node) { return visitNode(_node); } | ||||||
| 	virtual bool visit(FunctionCall 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(VariableDeclarationStatement const& _node) { endVisitNode(_node); } | ||||||
| 	virtual void endVisit(ExpressionStatement const& _node) { endVisitNode(_node); } | 	virtual void endVisit(ExpressionStatement const& _node) { endVisitNode(_node); } | ||||||
| 	virtual void endVisit(Assignment 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(UnaryOperation const& _node) { endVisitNode(_node); } | ||||||
| 	virtual void endVisit(BinaryOperation const& _node) { endVisitNode(_node); } | 	virtual void endVisit(BinaryOperation const& _node) { endVisitNode(_node); } | ||||||
| 	virtual void endVisit(FunctionCall 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); | 	_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) | void UnaryOperation::accept(ASTVisitor& _visitor) | ||||||
| { | { | ||||||
| 	if (_visitor.visit(*this)) | 	if (_visitor.visit(*this)) | ||||||
|  | |||||||
| @ -597,13 +597,20 @@ bool Compiler::visit(Break const& _breakStatement) | |||||||
| bool Compiler::visit(Return const& _return) | bool Compiler::visit(Return const& _return) | ||||||
| { | { | ||||||
| 	CompilerContext::LocationSetter locationSetter(m_context, _return); | 	CompilerContext::LocationSetter locationSetter(m_context, _return); | ||||||
| 	//@todo modifications are needed to make this work with functions returning multiple values
 |  | ||||||
| 	if (Expression const* expression = _return.expression()) | 	if (Expression const* expression = _return.expression()) | ||||||
| 	{ | 	{ | ||||||
| 		solAssert(_return.annotation().functionReturnParameters, "Invalid return parameters pointer."); | 		solAssert(_return.annotation().functionReturnParameters, "Invalid return parameters pointer."); | ||||||
| 		VariableDeclaration const& firstVariable = *_return.annotation().functionReturnParameters->parameters().front(); | 		vector<ASTPointer<VariableDeclaration>> const& returnParameters = | ||||||
| 		compileExpression(*expression, firstVariable.annotation().type); | 			_return.annotation().functionReturnParameters->parameters(); | ||||||
| 		CompilerUtils(m_context).moveToStackVariable(firstVariable); | 		TypePointers types; | ||||||
|  | 		for (auto const& retVariable: returnParameters) | ||||||
|  | 			types.push_back(retVariable->annotation().type); | ||||||
|  | 
 | ||||||
|  | 		TypePointer expectedType = types.size() == 1 ? types.front() : make_shared<TupleType>(types); | ||||||
|  | 		compileExpression(*expression, expectedType); | ||||||
|  | 
 | ||||||
|  | 		for (auto const& retVariable: boost::adaptors::reverse(returnParameters)) | ||||||
|  | 			CompilerUtils(m_context).moveToStackVariable(*retVariable); | ||||||
| 	} | 	} | ||||||
| 	for (unsigned i = 0; i < m_stackCleanupForReturn; ++i) | 	for (unsigned i = 0; i < m_stackCleanupForReturn; ++i) | ||||||
| 		m_context << eth::Instruction::POP; | 		m_context << eth::Instruction::POP; | ||||||
| @ -637,6 +644,7 @@ bool Compiler::visit(VariableDeclarationStatement const& _variableDeclarationSta | |||||||
| 		for (size_t i = 0; i < assignments.size(); ++i) | 		for (size_t i = 0; i < assignments.size(); ++i) | ||||||
| 		{ | 		{ | ||||||
| 			size_t j = assignments.size() - i - 1; | 			size_t j = assignments.size() - i - 1; | ||||||
|  | 			solAssert(!!valueTypes[j], ""); | ||||||
| 			VariableDeclaration const* varDecl = assignments[j]; | 			VariableDeclaration const* varDecl = assignments[j]; | ||||||
| 			if (!varDecl) | 			if (!varDecl) | ||||||
| 				utils.popStackElement(*valueTypes[j]); | 				utils.popStackElement(*valueTypes[j]); | ||||||
|  | |||||||
| @ -550,6 +550,61 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp | |||||||
| 		} | 		} | ||||||
| 		break; | 		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: | 	default: | ||||||
| 		// All other types should not be convertible to non-equal types.
 | 		// All other types should not be convertible to non-equal types.
 | ||||||
| 		solAssert(_typeOnStack == _targetType, "Invalid type conversion requested."); | 		solAssert(_typeOnStack == _targetType, "Invalid type conversion requested."); | ||||||
| @ -631,18 +686,20 @@ void CompilerUtils::copyToStackTop(unsigned _stackDepth, unsigned _itemSize) | |||||||
| 		m_context << eth::dupInstruction(_stackDepth); | 		m_context << eth::dupInstruction(_stackDepth); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void CompilerUtils::moveToStackTop(unsigned _stackDepth) | void CompilerUtils::moveToStackTop(unsigned _stackDepth, unsigned _itemSize) | ||||||
| { | { | ||||||
| 	solAssert(_stackDepth <= 15, "Stack too deep, try removing local variables."); | 	solAssert(_stackDepth <= 15, "Stack too deep, try removing local variables."); | ||||||
| 	for (unsigned i = 0; i < _stackDepth; ++i) | 	for (unsigned j = 0; j < _itemSize; ++j) | ||||||
| 		m_context << eth::swapInstruction(1 + i); | 		for (unsigned i = 0; i < _stackDepth + _itemSize - 1; ++i) | ||||||
|  | 			m_context << eth::swapInstruction(1 + i); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void CompilerUtils::moveIntoStack(unsigned _stackDepth) | void CompilerUtils::moveIntoStack(unsigned _stackDepth, unsigned _itemSize) | ||||||
| { | { | ||||||
| 	solAssert(_stackDepth <= 16, "Stack too deep, try removing local variables."); | 	solAssert(_stackDepth <= 16, "Stack too deep, try removing local variables."); | ||||||
| 	for (unsigned i = _stackDepth; i > 0; --i) | 	for (unsigned j = 0; j < _itemSize; ++j) | ||||||
| 		m_context << eth::swapInstruction(i); | 		for (unsigned i = _stackDepth; i > 0; --i) | ||||||
|  | 			m_context << eth::swapInstruction(i + _itemSize - 1); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void CompilerUtils::popStackElement(Type const& _type) | void CompilerUtils::popStackElement(Type const& _type) | ||||||
|  | |||||||
| @ -124,10 +124,11 @@ public: | |||||||
| 	/// Copies an item that occupies @a _itemSize stack slots from a stack depth of @a _stackDepth
 | 	/// Copies an item that occupies @a _itemSize stack slots from a stack depth of @a _stackDepth
 | ||||||
| 	/// to the top of the stack.
 | 	/// to the top of the stack.
 | ||||||
| 	void copyToStackTop(unsigned _stackDepth, unsigned _itemSize); | 	void copyToStackTop(unsigned _stackDepth, unsigned _itemSize); | ||||||
| 	/// Moves a single stack element (with _stackDepth items on top of it) to the top of the stack.
 | 	/// Moves an item that occupies @a _itemSize stack slots and has items occupying @a _stackDepth
 | ||||||
| 	void moveToStackTop(unsigned _stackDepth); | 	/// slots above it to the top of the stack.
 | ||||||
| 	/// Moves a single stack element past @a _stackDepth other stack elements
 | 	void moveToStackTop(unsigned _stackDepth, unsigned _itemSize = 1); | ||||||
| 	void moveIntoStack(unsigned _stackDepth); | 	/// Moves @a _itemSize elements past @a _stackDepth other stack elements
 | ||||||
|  | 	void moveIntoStack(unsigned _stackDepth, unsigned _itemSize = 1); | ||||||
| 	/// Removes the current value from the top of the stack.
 | 	/// Removes the current value from the top of the stack.
 | ||||||
| 	void popStackElement(Type const& _type); | 	void popStackElement(Type const& _type); | ||||||
| 	/// Removes element from the top of the stack _amount times.
 | 	/// Removes element from the top of the stack _amount times.
 | ||||||
|  | |||||||
| @ -179,17 +179,12 @@ bool ExpressionCompiler::visit(Assignment const& _assignment) | |||||||
| { | { | ||||||
| 	CompilerContext::LocationSetter locationSetter(m_context, _assignment); | 	CompilerContext::LocationSetter locationSetter(m_context, _assignment); | ||||||
| 	_assignment.rightHandSide().accept(*this); | 	_assignment.rightHandSide().accept(*this); | ||||||
| 	TypePointer type = _assignment.rightHandSide().annotation().type; | 	// Perform some conversion already. This will convert storage types to memory and literals
 | ||||||
| 	if (!_assignment.annotation().type->dataStoredIn(DataLocation::Storage)) | 	// to their actual type, but will not convert e.g. memory to storage.
 | ||||||
| 	{ | 	TypePointer type = _assignment.rightHandSide().annotation().type->closestTemporaryType( | ||||||
| 		utils().convertType(*type, *_assignment.annotation().type); | 		_assignment.leftHandSide().annotation().type | ||||||
| 		type = _assignment.annotation().type; | 	); | ||||||
| 	} | 	utils().convertType(*_assignment.rightHandSide().annotation().type, *type); | ||||||
| 	else |  | ||||||
| 	{ |  | ||||||
| 		utils().convertType(*type, *type->mobileType()); |  | ||||||
| 		type = type->mobileType(); |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	_assignment.leftHandSide().accept(*this); | 	_assignment.leftHandSide().accept(*this); | ||||||
| 	solAssert(!!m_currentLValue, "LValue not retrieved."); | 	solAssert(!!m_currentLValue, "LValue not retrieved."); | ||||||
| @ -221,6 +216,26 @@ bool ExpressionCompiler::visit(Assignment const& _assignment) | |||||||
| 	return false; | 	return false; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | bool ExpressionCompiler::visit(TupleExpression const& _tuple) | ||||||
|  | { | ||||||
|  | 	vector<unique_ptr<LValue>> lvalues; | ||||||
|  | 	for (auto const& component: _tuple.components()) | ||||||
|  | 		if (component) | ||||||
|  | 		{ | ||||||
|  | 			component->accept(*this); | ||||||
|  | 			if (_tuple.annotation().lValueRequested) | ||||||
|  | 			{ | ||||||
|  | 				solAssert(!!m_currentLValue, ""); | ||||||
|  | 				lvalues.push_back(move(m_currentLValue)); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		else if (_tuple.annotation().lValueRequested) | ||||||
|  | 			lvalues.push_back(unique_ptr<LValue>()); | ||||||
|  | 	if (_tuple.annotation().lValueRequested) | ||||||
|  | 		m_currentLValue.reset(new TupleObject(m_context, move(lvalues))); | ||||||
|  | 	return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation) | bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation) | ||||||
| { | { | ||||||
| 	CompilerContext::LocationSetter locationSetter(m_context, _unaryOperation); | 	CompilerContext::LocationSetter locationSetter(m_context, _unaryOperation); | ||||||
| @ -649,9 +664,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) | |||||||
| 			// stack: newLength storageSlot slotOffset
 | 			// stack: newLength storageSlot slotOffset
 | ||||||
| 			arguments[0]->accept(*this); | 			arguments[0]->accept(*this); | ||||||
| 			// stack: newLength storageSlot slotOffset argValue
 | 			// stack: newLength storageSlot slotOffset argValue
 | ||||||
| 			TypePointer type = arguments[0]->annotation().type; | 			TypePointer type = arguments[0]->annotation().type->closestTemporaryType(arrayType->baseType()); | ||||||
| 			utils().convertType(*type, *arrayType->baseType()); | 			utils().convertType(*arguments[0]->annotation().type, *type); | ||||||
| 			type = arrayType->baseType(); |  | ||||||
| 			utils().moveToStackTop(1 + type->sizeOnStack()); | 			utils().moveToStackTop(1 + type->sizeOnStack()); | ||||||
| 			utils().moveToStackTop(1 + type->sizeOnStack()); | 			utils().moveToStackTop(1 + type->sizeOnStack()); | ||||||
| 			// stack: newLength argValue storageSlot slotOffset
 | 			// stack: newLength argValue storageSlot slotOffset
 | ||||||
|  | |||||||
| @ -72,6 +72,7 @@ public: | |||||||
| 
 | 
 | ||||||
| private: | private: | ||||||
| 	virtual bool visit(Assignment const& _assignment) override; | 	virtual bool visit(Assignment const& _assignment) override; | ||||||
|  | 	virtual bool visit(TupleExpression const& _tuple) override; | ||||||
| 	virtual bool visit(UnaryOperation const& _unaryOperation) override; | 	virtual bool visit(UnaryOperation const& _unaryOperation) override; | ||||||
| 	virtual bool visit(BinaryOperation const& _binaryOperation) override; | 	virtual bool visit(BinaryOperation const& _binaryOperation) override; | ||||||
| 	virtual bool visit(FunctionCall const& _functionCall) override; | 	virtual bool visit(FunctionCall const& _functionCall) override; | ||||||
|  | |||||||
| @ -32,9 +32,9 @@ using namespace solidity; | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| StackVariable::StackVariable(CompilerContext& _compilerContext, VariableDeclaration const& _declaration): | StackVariable::StackVariable(CompilerContext& _compilerContext, VariableDeclaration const& _declaration): | ||||||
| 	LValue(_compilerContext, *_declaration.annotation().type), | 	LValue(_compilerContext, _declaration.annotation().type.get()), | ||||||
| 	m_baseStackOffset(m_context.baseStackOffsetOfVariable(_declaration)), | 	m_baseStackOffset(m_context.baseStackOffsetOfVariable(_declaration)), | ||||||
| 	m_size(m_dataType.sizeOnStack()) | 	m_size(m_dataType->sizeOnStack()) | ||||||
| { | { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -70,23 +70,23 @@ void StackVariable::storeValue(Type const&, SourceLocation const& _location, boo | |||||||
| 
 | 
 | ||||||
| void StackVariable::setToZero(SourceLocation const& _location, bool) const | void StackVariable::setToZero(SourceLocation const& _location, bool) const | ||||||
| { | { | ||||||
| 	CompilerUtils(m_context).pushZeroValue(m_dataType); | 	CompilerUtils(m_context).pushZeroValue(*m_dataType); | ||||||
| 	storeValue(m_dataType, _location, true); | 	storeValue(*m_dataType, _location, true); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| MemoryItem::MemoryItem(CompilerContext& _compilerContext, Type const& _type, bool _padded): | MemoryItem::MemoryItem(CompilerContext& _compilerContext, Type const& _type, bool _padded): | ||||||
| 	LValue(_compilerContext, _type), | 	LValue(_compilerContext, &_type), | ||||||
| 	m_padded(_padded) | 	m_padded(_padded) | ||||||
| { | { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void MemoryItem::retrieveValue(SourceLocation const&, bool _remove) const | void MemoryItem::retrieveValue(SourceLocation const&, bool _remove) const | ||||||
| { | { | ||||||
| 	if (m_dataType.isValueType()) | 	if (m_dataType->isValueType()) | ||||||
| 	{ | 	{ | ||||||
| 		if (!_remove) | 		if (!_remove) | ||||||
| 			m_context << eth::Instruction::DUP1; | 			m_context << eth::Instruction::DUP1; | ||||||
| 		CompilerUtils(m_context).loadFromMemoryDynamic(m_dataType, false, m_padded, false); | 		CompilerUtils(m_context).loadFromMemoryDynamic(*m_dataType, false, m_padded, false); | ||||||
| 	} | 	} | ||||||
| 	else | 	else | ||||||
| 		m_context << eth::Instruction::MLOAD; | 		m_context << eth::Instruction::MLOAD; | ||||||
| @ -95,24 +95,24 @@ void MemoryItem::retrieveValue(SourceLocation const&, bool _remove) const | |||||||
| void MemoryItem::storeValue(Type const& _sourceType, SourceLocation const&, bool _move) const | void MemoryItem::storeValue(Type const& _sourceType, SourceLocation const&, bool _move) const | ||||||
| { | { | ||||||
| 	CompilerUtils utils(m_context); | 	CompilerUtils utils(m_context); | ||||||
| 	if (m_dataType.isValueType()) | 	if (m_dataType->isValueType()) | ||||||
| 	{ | 	{ | ||||||
| 		solAssert(_sourceType.isValueType(), ""); | 		solAssert(_sourceType.isValueType(), ""); | ||||||
| 		utils.moveIntoStack(_sourceType.sizeOnStack()); | 		utils.moveIntoStack(_sourceType.sizeOnStack()); | ||||||
| 		utils.convertType(_sourceType, m_dataType, true); | 		utils.convertType(_sourceType, *m_dataType, true); | ||||||
| 		if (!_move) | 		if (!_move) | ||||||
| 		{ | 		{ | ||||||
| 			utils.moveToStackTop(m_dataType.sizeOnStack()); | 			utils.moveToStackTop(m_dataType->sizeOnStack()); | ||||||
| 			utils.copyToStackTop(2, m_dataType.sizeOnStack()); | 			utils.copyToStackTop(2, m_dataType->sizeOnStack()); | ||||||
| 		} | 		} | ||||||
| 		utils.storeInMemoryDynamic(m_dataType, m_padded); | 		utils.storeInMemoryDynamic(*m_dataType, m_padded); | ||||||
| 		m_context << eth::Instruction::POP; | 		m_context << eth::Instruction::POP; | ||||||
| 	} | 	} | ||||||
| 	else | 	else | ||||||
| 	{ | 	{ | ||||||
| 		solAssert(_sourceType == m_dataType, "Conversion not implemented for assignment to memory."); | 		solAssert(_sourceType == *m_dataType, "Conversion not implemented for assignment to memory."); | ||||||
| 
 | 
 | ||||||
| 		solAssert(m_dataType.sizeOnStack() == 1, ""); | 		solAssert(m_dataType->sizeOnStack() == 1, ""); | ||||||
| 		if (!_move) | 		if (!_move) | ||||||
| 			m_context << eth::Instruction::DUP2 << eth::Instruction::SWAP1; | 			m_context << eth::Instruction::DUP2 << eth::Instruction::SWAP1; | ||||||
| 		// stack: [value] value lvalue
 | 		// stack: [value] value lvalue
 | ||||||
| @ -126,8 +126,8 @@ void MemoryItem::setToZero(SourceLocation const&, bool _removeReference) const | |||||||
| 	CompilerUtils utils(m_context); | 	CompilerUtils utils(m_context); | ||||||
| 	if (!_removeReference) | 	if (!_removeReference) | ||||||
| 		m_context << eth::Instruction::DUP1; | 		m_context << eth::Instruction::DUP1; | ||||||
| 	utils.pushZeroValue(m_dataType); | 	utils.pushZeroValue(*m_dataType); | ||||||
| 	utils.storeInMemoryDynamic(m_dataType, m_padded); | 	utils.storeInMemoryDynamic(*m_dataType, m_padded); | ||||||
| 	m_context << eth::Instruction::POP; | 	m_context << eth::Instruction::POP; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -139,21 +139,21 @@ StorageItem::StorageItem(CompilerContext& _compilerContext, VariableDeclaration | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| StorageItem::StorageItem(CompilerContext& _compilerContext, Type const& _type): | StorageItem::StorageItem(CompilerContext& _compilerContext, Type const& _type): | ||||||
| 	LValue(_compilerContext, _type) | 	LValue(_compilerContext, &_type) | ||||||
| { | { | ||||||
| 	if (m_dataType.isValueType()) | 	if (m_dataType->isValueType()) | ||||||
| 	{ | 	{ | ||||||
| 		solAssert(m_dataType.storageSize() == m_dataType.sizeOnStack(), ""); | 		solAssert(m_dataType->storageSize() == m_dataType->sizeOnStack(), ""); | ||||||
| 		solAssert(m_dataType.storageSize() == 1, "Invalid storage size."); | 		solAssert(m_dataType->storageSize() == 1, "Invalid storage size."); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const | void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const | ||||||
| { | { | ||||||
| 	// stack: storage_key storage_offset
 | 	// stack: storage_key storage_offset
 | ||||||
| 	if (!m_dataType.isValueType()) | 	if (!m_dataType->isValueType()) | ||||||
| 	{ | 	{ | ||||||
| 		solAssert(m_dataType.sizeOnStack() == 1, "Invalid storage ref size."); | 		solAssert(m_dataType->sizeOnStack() == 1, "Invalid storage ref size."); | ||||||
| 		if (_remove) | 		if (_remove) | ||||||
| 			m_context << eth::Instruction::POP; // remove byte offset
 | 			m_context << eth::Instruction::POP; // remove byte offset
 | ||||||
| 		else | 		else | ||||||
| @ -162,22 +162,22 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const | |||||||
| 	} | 	} | ||||||
| 	if (!_remove) | 	if (!_remove) | ||||||
| 		CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack()); | 		CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack()); | ||||||
| 	if (m_dataType.storageBytes() == 32) | 	if (m_dataType->storageBytes() == 32) | ||||||
| 		m_context << eth::Instruction::POP << eth::Instruction::SLOAD; | 		m_context << eth::Instruction::POP << eth::Instruction::SLOAD; | ||||||
| 	else | 	else | ||||||
| 	{ | 	{ | ||||||
| 		m_context | 		m_context | ||||||
| 			<< eth::Instruction::SWAP1 << eth::Instruction::SLOAD << eth::Instruction::SWAP1 | 			<< eth::Instruction::SWAP1 << eth::Instruction::SLOAD << eth::Instruction::SWAP1 | ||||||
| 			<< u256(0x100) << eth::Instruction::EXP << eth::Instruction::SWAP1 << eth::Instruction::DIV; | 			<< u256(0x100) << eth::Instruction::EXP << eth::Instruction::SWAP1 << eth::Instruction::DIV; | ||||||
| 		if (m_dataType.category() == Type::Category::FixedBytes) | 		if (m_dataType->category() == Type::Category::FixedBytes) | ||||||
| 			m_context << (u256(0x1) << (256 - 8 * m_dataType.storageBytes())) << eth::Instruction::MUL; | 			m_context << (u256(0x1) << (256 - 8 * m_dataType->storageBytes())) << eth::Instruction::MUL; | ||||||
| 		else if ( | 		else if ( | ||||||
| 			m_dataType.category() == Type::Category::Integer && | 			m_dataType->category() == Type::Category::Integer && | ||||||
| 			dynamic_cast<IntegerType const&>(m_dataType).isSigned() | 			dynamic_cast<IntegerType const&>(*m_dataType).isSigned() | ||||||
| 		) | 		) | ||||||
| 			m_context << u256(m_dataType.storageBytes() - 1) << eth::Instruction::SIGNEXTEND; | 			m_context << u256(m_dataType->storageBytes() - 1) << eth::Instruction::SIGNEXTEND; | ||||||
| 		else | 		else | ||||||
| 			m_context << ((u256(0x1) << (8 * m_dataType.storageBytes())) - 1) << eth::Instruction::AND; | 			m_context << ((u256(0x1) << (8 * m_dataType->storageBytes())) - 1) << eth::Instruction::AND; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -185,11 +185,11 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc | |||||||
| { | { | ||||||
| 	CompilerUtils utils(m_context); | 	CompilerUtils utils(m_context); | ||||||
| 	// stack: value storage_key storage_offset
 | 	// stack: value storage_key storage_offset
 | ||||||
| 	if (m_dataType.isValueType()) | 	if (m_dataType->isValueType()) | ||||||
| 	{ | 	{ | ||||||
| 		solAssert(m_dataType.storageBytes() <= 32, "Invalid storage bytes size."); | 		solAssert(m_dataType->storageBytes() <= 32, "Invalid storage bytes size."); | ||||||
| 		solAssert(m_dataType.storageBytes() > 0, "Invalid storage bytes size."); | 		solAssert(m_dataType->storageBytes() > 0, "Invalid storage bytes size."); | ||||||
| 		if (m_dataType.storageBytes() == 32) | 		if (m_dataType->storageBytes() == 32) | ||||||
| 		{ | 		{ | ||||||
| 			// offset should be zero
 | 			// offset should be zero
 | ||||||
| 			m_context << eth::Instruction::POP; | 			m_context << eth::Instruction::POP; | ||||||
| @ -207,24 +207,24 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc | |||||||
| 			// stack: value storege_ref multiplier old_full_value
 | 			// stack: value storege_ref multiplier old_full_value
 | ||||||
| 			// clear bytes in old value
 | 			// clear bytes in old value
 | ||||||
| 			m_context | 			m_context | ||||||
| 				<< eth::Instruction::DUP2 << ((u256(1) << (8 * m_dataType.storageBytes())) - 1) | 				<< eth::Instruction::DUP2 << ((u256(1) << (8 * m_dataType->storageBytes())) - 1) | ||||||
| 				<< eth::Instruction::MUL; | 				<< eth::Instruction::MUL; | ||||||
| 			m_context << eth::Instruction::NOT << eth::Instruction::AND; | 			m_context << eth::Instruction::NOT << eth::Instruction::AND; | ||||||
| 			// stack: value storage_ref multiplier cleared_value
 | 			// stack: value storage_ref multiplier cleared_value
 | ||||||
| 			m_context | 			m_context | ||||||
| 				<< eth::Instruction::SWAP1 << eth::Instruction::DUP4; | 				<< eth::Instruction::SWAP1 << eth::Instruction::DUP4; | ||||||
| 			// stack: value storage_ref cleared_value multiplier value
 | 			// stack: value storage_ref cleared_value multiplier value
 | ||||||
| 			if (m_dataType.category() == Type::Category::FixedBytes) | 			if (m_dataType->category() == Type::Category::FixedBytes) | ||||||
| 				m_context | 				m_context | ||||||
| 					<< (u256(0x1) << (256 - 8 * dynamic_cast<FixedBytesType const&>(m_dataType).numBytes())) | 					<< (u256(0x1) << (256 - 8 * dynamic_cast<FixedBytesType const&>(*m_dataType).numBytes())) | ||||||
| 					<< eth::Instruction::SWAP1 << eth::Instruction::DIV; | 					<< eth::Instruction::SWAP1 << eth::Instruction::DIV; | ||||||
| 			else if ( | 			else if ( | ||||||
| 				m_dataType.category() == Type::Category::Integer && | 				m_dataType->category() == Type::Category::Integer && | ||||||
| 				dynamic_cast<IntegerType const&>(m_dataType).isSigned() | 				dynamic_cast<IntegerType const&>(*m_dataType).isSigned() | ||||||
| 			) | 			) | ||||||
| 				// remove the higher order bits
 | 				// remove the higher order bits
 | ||||||
| 				m_context | 				m_context | ||||||
| 					<< (u256(1) << (8 * (32 - m_dataType.storageBytes()))) | 					<< (u256(1) << (8 * (32 - m_dataType->storageBytes()))) | ||||||
| 					<< eth::Instruction::SWAP1 | 					<< eth::Instruction::SWAP1 | ||||||
| 					<< eth::Instruction::DUP2 | 					<< eth::Instruction::DUP2 | ||||||
| 					<< eth::Instruction::MUL | 					<< eth::Instruction::MUL | ||||||
| @ -239,23 +239,24 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc | |||||||
| 	else | 	else | ||||||
| 	{ | 	{ | ||||||
| 		solAssert( | 		solAssert( | ||||||
| 			_sourceType.category() == m_dataType.category(), | 			_sourceType.category() == m_dataType->category(), | ||||||
| 			"Wrong type conversation for assignment."); | 			"Wrong type conversation for assignment."); | ||||||
| 		if (m_dataType.category() == Type::Category::Array) | 		if (m_dataType->category() == Type::Category::Array) | ||||||
| 		{ | 		{ | ||||||
| 			m_context << eth::Instruction::POP; // remove byte offset
 | 			m_context << eth::Instruction::POP; // remove byte offset
 | ||||||
| 			ArrayUtils(m_context).copyArrayToStorage( | 			ArrayUtils(m_context).copyArrayToStorage( | ||||||
| 						dynamic_cast<ArrayType const&>(m_dataType), | 				dynamic_cast<ArrayType const&>(*m_dataType), | ||||||
| 						dynamic_cast<ArrayType const&>(_sourceType)); | 				dynamic_cast<ArrayType const&>(_sourceType) | ||||||
|  | 			); | ||||||
| 			if (_move) | 			if (_move) | ||||||
| 				m_context << eth::Instruction::POP; | 				m_context << eth::Instruction::POP; | ||||||
| 		} | 		} | ||||||
| 		else if (m_dataType.category() == Type::Category::Struct) | 		else if (m_dataType->category() == Type::Category::Struct) | ||||||
| 		{ | 		{ | ||||||
| 			// stack layout: source_ref target_ref target_offset
 | 			// stack layout: source_ref target_ref target_offset
 | ||||||
| 			// note that we have structs, so offset should be zero and are ignored
 | 			// note that we have structs, so offset should be zero and are ignored
 | ||||||
| 			m_context << eth::Instruction::POP; | 			m_context << eth::Instruction::POP; | ||||||
| 			auto const& structType = dynamic_cast<StructType const&>(m_dataType); | 			auto const& structType = dynamic_cast<StructType const&>(*m_dataType); | ||||||
| 			auto const& sourceType = dynamic_cast<StructType const&>(_sourceType); | 			auto const& sourceType = dynamic_cast<StructType const&>(_sourceType); | ||||||
| 			solAssert( | 			solAssert( | ||||||
| 				structType.structDefinition() == sourceType.structDefinition(), | 				structType.structDefinition() == sourceType.structDefinition(), | ||||||
| @ -313,18 +314,18 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc | |||||||
| 
 | 
 | ||||||
| void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const | void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const | ||||||
| { | { | ||||||
| 	if (m_dataType.category() == Type::Category::Array) | 	if (m_dataType->category() == Type::Category::Array) | ||||||
| 	{ | 	{ | ||||||
| 		if (!_removeReference) | 		if (!_removeReference) | ||||||
| 			CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack()); | 			CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack()); | ||||||
| 		ArrayUtils(m_context).clearArray(dynamic_cast<ArrayType const&>(m_dataType)); | 		ArrayUtils(m_context).clearArray(dynamic_cast<ArrayType const&>(*m_dataType)); | ||||||
| 	} | 	} | ||||||
| 	else if (m_dataType.category() == Type::Category::Struct) | 	else if (m_dataType->category() == Type::Category::Struct) | ||||||
| 	{ | 	{ | ||||||
| 		// stack layout: storage_key storage_offset
 | 		// stack layout: storage_key storage_offset
 | ||||||
| 		// @todo this can be improved: use StorageItem for non-value types, and just store 0 in
 | 		// @todo this can be improved: use StorageItem for non-value types, and just store 0 in
 | ||||||
| 		// all slots that contain value types later.
 | 		// all slots that contain value types later.
 | ||||||
| 		auto const& structType = dynamic_cast<StructType const&>(m_dataType); | 		auto const& structType = dynamic_cast<StructType const&>(*m_dataType); | ||||||
| 		for (auto const& member: structType.members()) | 		for (auto const& member: structType.members()) | ||||||
| 		{ | 		{ | ||||||
| 			// zero each member that is not a mapping
 | 			// zero each member that is not a mapping
 | ||||||
| @ -342,10 +343,10 @@ void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const | |||||||
| 	} | 	} | ||||||
| 	else | 	else | ||||||
| 	{ | 	{ | ||||||
| 		solAssert(m_dataType.isValueType(), "Clearing of unsupported type requested: " + m_dataType.toString()); | 		solAssert(m_dataType->isValueType(), "Clearing of unsupported type requested: " + m_dataType->toString()); | ||||||
| 		if (!_removeReference) | 		if (!_removeReference) | ||||||
| 			CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack()); | 			CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack()); | ||||||
| 		if (m_dataType.storageBytes() == 32) | 		if (m_dataType->storageBytes() == 32) | ||||||
| 		{ | 		{ | ||||||
| 			// offset should be zero
 | 			// offset should be zero
 | ||||||
| 			m_context | 			m_context | ||||||
| @ -361,7 +362,7 @@ void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const | |||||||
| 			// stack: storege_ref multiplier old_full_value
 | 			// stack: storege_ref multiplier old_full_value
 | ||||||
| 			// clear bytes in old value
 | 			// clear bytes in old value
 | ||||||
| 			m_context | 			m_context | ||||||
| 				<< eth::Instruction::SWAP1 << ((u256(1) << (8 * m_dataType.storageBytes())) - 1) | 				<< eth::Instruction::SWAP1 << ((u256(1) << (8 * m_dataType->storageBytes())) - 1) | ||||||
| 				<< eth::Instruction::MUL; | 				<< eth::Instruction::MUL; | ||||||
| 			m_context << eth::Instruction::NOT << eth::Instruction::AND; | 			m_context << eth::Instruction::NOT << eth::Instruction::AND; | ||||||
| 			// stack: storage_ref cleared_value
 | 			// stack: storage_ref cleared_value
 | ||||||
| @ -374,7 +375,7 @@ void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const | |||||||
| static FixedBytesType byteType(1); | static FixedBytesType byteType(1); | ||||||
| 
 | 
 | ||||||
| StorageByteArrayElement::StorageByteArrayElement(CompilerContext& _compilerContext): | StorageByteArrayElement::StorageByteArrayElement(CompilerContext& _compilerContext): | ||||||
| 	LValue(_compilerContext, byteType) | 	LValue(_compilerContext, &byteType) | ||||||
| { | { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -427,7 +428,7 @@ void StorageByteArrayElement::setToZero(SourceLocation const&, bool _removeRefer | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| StorageArrayLength::StorageArrayLength(CompilerContext& _compilerContext, const ArrayType& _arrayType): | StorageArrayLength::StorageArrayLength(CompilerContext& _compilerContext, const ArrayType& _arrayType): | ||||||
| 	LValue(_compilerContext, *_arrayType.memberType("length")), | 	LValue(_compilerContext, _arrayType.memberType("length").get()), | ||||||
| 	m_arrayType(_arrayType) | 	m_arrayType(_arrayType) | ||||||
| { | { | ||||||
| 	solAssert(m_arrayType.isDynamicallySized(), ""); | 	solAssert(m_arrayType.isDynamicallySized(), ""); | ||||||
| @ -455,3 +456,91 @@ void StorageArrayLength::setToZero(SourceLocation const&, bool _removeReference) | |||||||
| 		m_context << eth::Instruction::DUP1; | 		m_context << eth::Instruction::DUP1; | ||||||
| 	ArrayUtils(m_context).clearDynamicArray(m_arrayType); | 	ArrayUtils(m_context).clearDynamicArray(m_arrayType); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | TupleObject::TupleObject( | ||||||
|  | 	CompilerContext& _compilerContext, | ||||||
|  | 	std::vector<std::unique_ptr<LValue>>&& _lvalues | ||||||
|  | ): | ||||||
|  | 	LValue(_compilerContext), m_lvalues(move(_lvalues)) | ||||||
|  | { | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | unsigned TupleObject::sizeOnStack() const | ||||||
|  | { | ||||||
|  | 	unsigned size = 0; | ||||||
|  | 	for (auto const& lv: m_lvalues) | ||||||
|  | 		if (lv) | ||||||
|  | 			size += lv->sizeOnStack(); | ||||||
|  | 	return size; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void TupleObject::retrieveValue(SourceLocation const& _location, bool _remove) const | ||||||
|  | { | ||||||
|  | 	unsigned initialDepth = sizeOnStack(); | ||||||
|  | 	unsigned initialStack = m_context.stackHeight(); | ||||||
|  | 	for (auto const& lv: m_lvalues) | ||||||
|  | 		if (lv) | ||||||
|  | 		{ | ||||||
|  | 			solAssert(initialDepth + m_context.stackHeight() >= initialStack, ""); | ||||||
|  | 			unsigned depth = initialDepth + m_context.stackHeight() - initialStack; | ||||||
|  | 			if (lv->sizeOnStack() > 0) | ||||||
|  | 			{ | ||||||
|  | 				if (_remove && depth > lv->sizeOnStack()) | ||||||
|  | 					CompilerUtils(m_context).moveToStackTop(depth, depth - lv->sizeOnStack()); | ||||||
|  | 				else if (!_remove && depth > 0) | ||||||
|  | 					CompilerUtils(m_context).copyToStackTop(depth, lv->sizeOnStack()); | ||||||
|  | 			} | ||||||
|  | 			lv->retrieveValue(_location, true); | ||||||
|  | 		} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void TupleObject::storeValue(Type const& _sourceType, SourceLocation const& _location, bool) 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 | #pragma once | ||||||
| 
 | 
 | ||||||
| #include <memory> | #include <memory> | ||||||
|  | #include <vector> | ||||||
| #include <libevmasm/SourceLocation.h> | #include <libevmasm/SourceLocation.h> | ||||||
| #include <libsolidity/ArrayUtils.h> | #include <libsolidity/ArrayUtils.h> | ||||||
| 
 | 
 | ||||||
| @ -33,6 +34,7 @@ namespace solidity | |||||||
| 
 | 
 | ||||||
| class Declaration; | class Declaration; | ||||||
| class Type; | class Type; | ||||||
|  | class TupleType; | ||||||
| class ArrayType; | class ArrayType; | ||||||
| class CompilerContext; | class CompilerContext; | ||||||
| class VariableDeclaration; | class VariableDeclaration; | ||||||
| @ -43,7 +45,7 @@ class VariableDeclaration; | |||||||
| class LValue | class LValue | ||||||
| { | { | ||||||
| protected: | protected: | ||||||
| 	LValue(CompilerContext& _compilerContext, Type const& _dataType): | 	explicit LValue(CompilerContext& _compilerContext, Type const* _dataType = nullptr): | ||||||
| 		m_context(_compilerContext), m_dataType(_dataType) {} | 		m_context(_compilerContext), m_dataType(_dataType) {} | ||||||
| 
 | 
 | ||||||
| public: | public: | ||||||
| @ -68,7 +70,7 @@ public: | |||||||
| 
 | 
 | ||||||
| protected: | protected: | ||||||
| 	CompilerContext& m_context; | 	CompilerContext& m_context; | ||||||
| 	Type const& m_dataType; | 	Type const* m_dataType; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
| @ -193,5 +195,30 @@ private: | |||||||
| 	ArrayType const& m_arrayType; | 	ArrayType const& m_arrayType; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | /**
 | ||||||
|  |  * Tuple object that can itself hold several LValues. | ||||||
|  |  */ | ||||||
|  | class TupleObject: public LValue | ||||||
|  | { | ||||||
|  | public: | ||||||
|  | 	/// Constructs the LValue assuming that the other LValues are present on the stack.
 | ||||||
|  | 	/// Empty unique_ptrs are possible if e.g. some values should be ignored during assignment.
 | ||||||
|  | 	TupleObject(CompilerContext& _compilerContext, std::vector<std::unique_ptr<LValue>>&& _lvalues); | ||||||
|  | 	virtual unsigned sizeOnStack() const; | ||||||
|  | 	virtual void retrieveValue(SourceLocation const& _location, bool _remove = false) const override; | ||||||
|  | 	virtual void storeValue( | ||||||
|  | 		Type const& _sourceType, | ||||||
|  | 		SourceLocation const& _location = SourceLocation(), | ||||||
|  | 		bool _move = false | ||||||
|  | 	) const override; | ||||||
|  | 	virtual void setToZero( | ||||||
|  | 		SourceLocation const& _location = SourceLocation(), | ||||||
|  | 		bool _removeReference = true | ||||||
|  | 	) const override; | ||||||
|  | 
 | ||||||
|  | private: | ||||||
|  | 	std::vector<std::unique_ptr<LValue>> m_lvalues; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| } | } | ||||||
| } | } | ||||||
|  | |||||||
| @ -806,6 +806,7 @@ ASTPointer<VariableDeclarationStatement> Parser::parseVariableDeclarationStateme | |||||||
| 				) | 				) | ||||||
| 				{ | 				{ | ||||||
| 					ASTNodeFactory varDeclNodeFactory(*this); | 					ASTNodeFactory varDeclNodeFactory(*this); | ||||||
|  | 					varDeclNodeFactory.markEndPosition(); | ||||||
| 					ASTPointer<ASTString> name = expectIdentifierToken(); | 					ASTPointer<ASTString> name = expectIdentifierToken(); | ||||||
| 					var = varDeclNodeFactory.createNode<VariableDeclaration>( | 					var = varDeclNodeFactory.createNode<VariableDeclaration>( | ||||||
| 						ASTPointer<TypeName>(), | 						ASTPointer<TypeName>(), | ||||||
| @ -1009,10 +1010,25 @@ ASTPointer<Expression> Parser::parsePrimaryExpression() | |||||||
| 		break; | 		break; | ||||||
| 	case Token::LParen: | 	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(); | 		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); | 		expectToken(Token::RParen); | ||||||
| 		return expression; | 		return nodeFactory.createNode<TupleExpression>(components); | ||||||
| 	} | 	} | ||||||
| 	default: | 	default: | ||||||
| 		if (Token::isElementaryTypeName(token)) | 		if (Token::isElementaryTypeName(token)) | ||||||
|  | |||||||
| @ -560,13 +560,31 @@ void TypeChecker::endVisit(Return const& _return) | |||||||
| 		return; | 		return; | ||||||
| 	ParameterList const* params = _return.annotation().functionReturnParameters; | 	ParameterList const* params = _return.annotation().functionReturnParameters; | ||||||
| 	if (!params) | 	if (!params) | ||||||
|  | 	{ | ||||||
| 		typeError(_return, "Return arguments not allowed."); | 		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) | 	else if (params->parameters().size() != 1) | ||||||
| 		typeError(_return, "Different number of arguments in return statement than in returns declaration."); | 		typeError(_return, "Different number of arguments in return statement than in returns declaration."); | ||||||
| 	else | 	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()); | 		TypePointer const& expected = type(*params->parameters().front()); | ||||||
| 		if (!type(*_return.expression())->isImplicitlyConvertibleTo(*expected)) | 		if (!type(*_return.expression())->isImplicitlyConvertibleTo(*expected)) | ||||||
| 			typeError( | 			typeError( | ||||||
| @ -590,7 +608,7 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) | |||||||
| 		VariableDeclaration const& varDecl = *_statement.declarations().front(); | 		VariableDeclaration const& varDecl = *_statement.declarations().front(); | ||||||
| 		if (!varDecl.annotation().type) | 		if (!varDecl.annotation().type) | ||||||
| 			fatalTypeError(_statement, "Assignment necessary for type detection."); | 			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)) | 			if (ref->dataStoredIn(DataLocation::Storage)) | ||||||
| 			{ | 			{ | ||||||
| @ -610,10 +628,10 @@ bool TypeChecker::visit(VariableDeclarationStatement const& _statement) | |||||||
| 
 | 
 | ||||||
| 	_statement.initialValue()->accept(*this); | 	_statement.initialValue()->accept(*this); | ||||||
| 	TypePointers valueTypes; | 	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(); | 		valueTypes = tupleType->components(); | ||||||
| 	else | 	else | ||||||
| 		valueTypes = TypePointers{_statement.initialValue()->annotation().type}; | 		valueTypes = TypePointers{type(*_statement.initialValue())}; | ||||||
| 
 | 
 | ||||||
| 	// Determine which component is assigned to which variable.
 | 	// Determine which component is assigned to which variable.
 | ||||||
| 	// If numbers do not match, fill up if variables begin or end empty (not both).
 | 	// 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()); | 	requireLValue(_assignment.leftHandSide()); | ||||||
| 	TypePointer t = type(_assignment.leftHandSide()); | 	TypePointer t = type(_assignment.leftHandSide()); | ||||||
| 	_assignment.annotation().type = t; | 	_assignment.annotation().type = t; | ||||||
| 	if (t->category() == Type::Category::Mapping) | 	if (TupleType const* tupleType = dynamic_cast<TupleType const*>(t.get())) | ||||||
|  | 	{ | ||||||
|  | 		// Sequenced assignments of tuples is not valid, 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."); | 		typeError(_assignment, "Mappings cannot be assigned to."); | ||||||
| 		_assignment.rightHandSide().accept(*this); | 		_assignment.rightHandSide().accept(*this); | ||||||
| @ -741,6 +765,51 @@ bool TypeChecker::visit(Assignment const& _assignment) | |||||||
| 	return false; | 	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) | bool TypeChecker::visit(UnaryOperation const& _operation) | ||||||
| { | { | ||||||
| 	// Inc, Dec, Add, Sub, Not, BitNot, Delete
 | 	// 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) | void TypeChecker::requireLValue(Expression const& _expression) | ||||||
| { | { | ||||||
|  | 	_expression.annotation().lValueRequested = true; | ||||||
| 	_expression.accept(*this); | 	_expression.accept(*this); | ||||||
| 	if (!_expression.annotation().isLValue) | 	if (!_expression.annotation().isLValue) | ||||||
| 		typeError(_expression, "Expression has to be an lvalue."); | 		typeError(_expression, "Expression has to be an lvalue."); | ||||||
| 	_expression.annotation().lValueRequested = true; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void TypeChecker::typeError(ASTNode const& _node, string const& _description) | void TypeChecker::typeError(ASTNode const& _node, string const& _description) | ||||||
|  | |||||||
| @ -90,6 +90,7 @@ private: | |||||||
| 	virtual bool visit(VariableDeclarationStatement const& _variable) override; | 	virtual bool visit(VariableDeclarationStatement const& _variable) override; | ||||||
| 	virtual void endVisit(ExpressionStatement const& _statement) override; | 	virtual void endVisit(ExpressionStatement const& _statement) override; | ||||||
| 	virtual bool visit(Assignment const& _assignment) override; | 	virtual bool visit(Assignment const& _assignment) override; | ||||||
|  | 	virtual bool visit(TupleExpression const& _tuple) override; | ||||||
| 	virtual void endVisit(BinaryOperation const& _operation) override; | 	virtual void endVisit(BinaryOperation const& _operation) override; | ||||||
| 	virtual bool visit(UnaryOperation const& _operation) override; | 	virtual bool visit(UnaryOperation const& _operation) override; | ||||||
| 	virtual bool visit(FunctionCall const& _functionCall) 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)); | 	BOOST_THROW_EXCEPTION(m_enum.createTypeError("Requested unknown enum value ." + _member)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | bool TupleType::isImplicitlyConvertibleTo(Type const& _other) const | ||||||
|  | { | ||||||
|  | 	if (auto tupleType = dynamic_cast<TupleType const*>(&_other)) | ||||||
|  | 	{ | ||||||
|  | 		TypePointers const& targets = tupleType->components(); | ||||||
|  | 		if (targets.empty()) | ||||||
|  | 			return components().empty(); | ||||||
|  | 		if (components().size() != targets.size() && !targets.front() && !targets.back()) | ||||||
|  | 			return false; // (,a,) = (1,2,3,4) - unable to position `a` in the tuple.
 | ||||||
|  | 		size_t minNumValues = targets.size(); | ||||||
|  | 		if (!targets.back() || !targets.front()) | ||||||
|  | 			--minNumValues; // wildcards can also match 0 components
 | ||||||
|  | 		if (components().size() < minNumValues) | ||||||
|  | 			return false; | ||||||
|  | 		if (components().size() > targets.size() && targets.front() && targets.back()) | ||||||
|  | 			return false; // larger source and no wildcard
 | ||||||
|  | 		bool fillRight = !targets.back() || targets.front(); | ||||||
|  | 		for (size_t i = 0; i < min(targets.size(), components().size()); ++i) | ||||||
|  | 		{ | ||||||
|  | 			auto const& s = components()[fillRight ? i : components().size() - i - 1]; | ||||||
|  | 			auto const& t = targets[fillRight ? i : targets.size() - i - 1]; | ||||||
|  | 			if (!s && t) | ||||||
|  | 				return false; | ||||||
|  | 			else if (s && t && !s->isImplicitlyConvertibleTo(*t)) | ||||||
|  | 				return false; | ||||||
|  | 		} | ||||||
|  | 		return true; | ||||||
|  | 	} | ||||||
|  | 	else | ||||||
|  | 		return false; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| bool TupleType::operator==(Type const& _other) const | bool TupleType::operator==(Type const& _other) const | ||||||
| { | { | ||||||
| 	if (auto tupleType = dynamic_cast<TupleType const*>(&_other)) | 	if (auto tupleType = dynamic_cast<TupleType const*>(&_other)) | ||||||
| @ -1252,12 +1284,12 @@ bool TupleType::operator==(Type const& _other) const | |||||||
| 
 | 
 | ||||||
| string TupleType::toString(bool _short) const | string TupleType::toString(bool _short) const | ||||||
| { | { | ||||||
| 	if (m_components.empty()) | 	if (components().empty()) | ||||||
| 		return "tuple()"; | 		return "tuple()"; | ||||||
| 	string str = "tuple("; | 	string str = "tuple("; | ||||||
| 	for (auto const& t: m_components) | 	for (auto const& t: components()) | ||||||
| 		str += t->toString(_short) + ", "; | 		str += (t ? t->toString(_short) : "") + ","; | ||||||
| 	str.resize(str.size() - 2); | 	str.pop_back(); | ||||||
| 	return str + ")"; | 	return str + ")"; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -1272,11 +1304,35 @@ u256 TupleType::storageSize() const | |||||||
| unsigned TupleType::sizeOnStack() const | unsigned TupleType::sizeOnStack() const | ||||||
| { | { | ||||||
| 	unsigned size = 0; | 	unsigned size = 0; | ||||||
| 	for (auto const& t: m_components) | 	for (auto const& t: components()) | ||||||
| 		size += t->sizeOnStack(); | 		size += t ? t->sizeOnStack() : 0; | ||||||
| 	return size; | 	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): | FunctionType::FunctionType(FunctionDefinition const& _function, bool _isInternal): | ||||||
| 	m_location(_isInternal ? Location::Internal : Location::External), | 	m_location(_isInternal ? Location::Internal : Location::External), | ||||||
| 	m_isConstant(_function.isDeclaredConst()), | 	m_isConstant(_function.isDeclaredConst()), | ||||||
| @ -1638,14 +1694,15 @@ FunctionTypePointer FunctionType::asMemberFunction(bool _inLibrary) const | |||||||
| 			parameterTypes.push_back(t); | 			parameterTypes.push_back(t); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	//@todo make this more intelligent once we support destructuring assignments
 | 	// Removes dynamic types.
 | ||||||
| 	TypePointers returnParameterTypes; | 	TypePointers returnParameterTypes; | ||||||
| 	vector<string> returnParameterNames; | 	vector<string> returnParameterNames; | ||||||
| 	if (!m_returnParameterTypes.empty() && m_returnParameterTypes.front()->calldataEncodedSize() > 0) | 	for (size_t i = 0; i < m_returnParameterTypes.size(); ++i) | ||||||
| 	{ | 		if (m_returnParameterTypes[i]->calldataEncodedSize() > 0) | ||||||
| 		returnParameterTypes.push_back(m_returnParameterTypes.front()); | 		{ | ||||||
| 		returnParameterNames.push_back(m_returnParameterNames.front()); | 			returnParameterTypes.push_back(m_returnParameterTypes[i]); | ||||||
| 	} | 			returnParameterNames.push_back(m_returnParameterNames[i]); | ||||||
|  | 		} | ||||||
| 	return make_shared<FunctionType>( | 	return make_shared<FunctionType>( | ||||||
| 		parameterTypes, | 		parameterTypes, | ||||||
| 		returnParameterTypes, | 		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
 | 	/// @returns true if this is a non-value type and the data of this type is stored at the
 | ||||||
| 	/// given location.
 | 	/// given location.
 | ||||||
| 	virtual bool dataStoredIn(DataLocation) const { return false; } | 	virtual bool dataStoredIn(DataLocation) const { return false; } | ||||||
|  | 	/// @returns the type of a temporary during assignment to a variable of the given type.
 | ||||||
|  | 	/// Specifically, returns the requested itself if it can be dynamically allocated (or is a value type)
 | ||||||
|  | 	/// and the mobile type otherwise.
 | ||||||
|  | 	virtual TypePointer closestTemporaryType(TypePointer const& _targetType) const | ||||||
|  | 	{ | ||||||
|  | 		return _targetType->dataStoredIn(DataLocation::Storage) ? mobileType() : _targetType; | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	/// Returns the list of all members of this type. Default implementation: no members.
 | 	/// Returns the list of all members of this type. Default implementation: no members.
 | ||||||
| 	virtual MemberList const& members() const { return EmptyMemberList; } | 	virtual MemberList const& members() const { return EmptyMemberList; } | ||||||
| @ -682,12 +689,14 @@ private: | |||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
|  * Type that can hold a finite sequence of values of different types. |  * 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 | class TupleType: public Type | ||||||
| { | { | ||||||
| public: | public: | ||||||
| 	virtual Category category() const override { return Category::Tuple; } | 	virtual Category category() const override { return Category::Tuple; } | ||||||
| 	explicit TupleType(std::vector<TypePointer> const& _types = std::vector<TypePointer>()): m_components(_types) {} | 	explicit TupleType(std::vector<TypePointer> const& _types = std::vector<TypePointer>()): m_components(_types) {} | ||||||
|  | 	virtual bool isImplicitlyConvertibleTo(Type const& _other) const override; | ||||||
| 	virtual bool operator==(Type const& _other) const override; | 	virtual bool operator==(Type const& _other) const override; | ||||||
| 	virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } | 	virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } | ||||||
| 	virtual std::string toString(bool) const override; | 	virtual std::string toString(bool) const override; | ||||||
| @ -695,6 +704,9 @@ public: | |||||||
| 	virtual u256 storageSize() const override; | 	virtual u256 storageSize() const override; | ||||||
| 	virtual bool canLiveOutsideStorage() const override { return false; } | 	virtual bool canLiveOutsideStorage() const override { return false; } | ||||||
| 	virtual unsigned sizeOnStack() const override; | 	virtual unsigned sizeOnStack() const override; | ||||||
|  | 	virtual TypePointer mobileType() const override; | ||||||
|  | 	/// Converts components to their temporary types and performs some wildcard matching.
 | ||||||
|  | 	virtual TypePointer closestTemporaryType(TypePointer const& _targetType) const override; | ||||||
| 
 | 
 | ||||||
| 	std::vector<TypePointer> const& components() const { return m_components; } | 	std::vector<TypePointer> const& components() const { return m_components; } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -5677,6 +5677,101 @@ BOOST_AUTO_TEST_CASE(multi_variable_declaration) | |||||||
| 	BOOST_CHECK(callContractFunction("f()", encodeArgs()) == encodeArgs(true)); | 	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() | BOOST_AUTO_TEST_SUITE_END() | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | |||||||
| @ -109,13 +109,9 @@ ASTPointer<SourceUnit> parseAndAnalyse(string const& _source) | |||||||
| 	return sourceAndError.first; | 	return sourceAndError.first; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool success(std::string const& _source) | bool success(string const& _source) | ||||||
| { | { | ||||||
| 	auto sourceAndError = parseAnalyseAndReturnError(_source); | 	return !parseAnalyseAndReturnError(_source).second; | ||||||
| 
 |  | ||||||
| 	if (sourceAndError.second && *sourceAndError.second == Error::Type::TypeError) |  | ||||||
| 		return false; |  | ||||||
| 	return true; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| Error::Type expectError(std::string const& _source, bool _warning = false) | 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_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) | BOOST_AUTO_TEST_CASE(multi_variable_declaration_wildcards_fail_5) | ||||||
| { | { | ||||||
| 	char const* text = R"( | 	char const* text = R"( | ||||||
|  | |||||||
| @ -983,7 +983,7 @@ BOOST_AUTO_TEST_CASE(local_const_variable) | |||||||
| BOOST_AUTO_TEST_CASE(multi_variable_declaration) | BOOST_AUTO_TEST_CASE(multi_variable_declaration) | ||||||
| { | { | ||||||
| 	char const* text = R"( | 	char const* text = R"( | ||||||
| 		library Lib { | 		contract C { | ||||||
| 			function f() { | 			function f() { | ||||||
| 				var (a,b,c) = g(); | 				var (a,b,c) = g(); | ||||||
| 				var (d) = 2; | 				var (d) = 2; | ||||||
| @ -1000,6 +1000,21 @@ BOOST_AUTO_TEST_CASE(multi_variable_declaration) | |||||||
| 	BOOST_CHECK(successParse(text)); | 	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() | BOOST_AUTO_TEST_SUITE_END() | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user