/*
	This file is part of solidity.
	solidity is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.
	solidity is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.
	You should have received a copy of the GNU General Public License
	along with solidity.  If not, see .
*/
/**
 * Component that translates Solidity code into Yul at statement level and below.
 */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
using namespace solidity;
using namespace solidity::util;
using namespace solidity::frontend;
using namespace std::string_literals;
namespace
{
struct CopyTranslate: public yul::ASTCopier
{
	using ExternalRefsMap = std::map;
	CopyTranslate(yul::Dialect const& _dialect, IRGenerationContext& _context, ExternalRefsMap const& _references):
		m_dialect(_dialect), m_context(_context), m_references(_references) {}
	using ASTCopier::operator();
	yul::Expression operator()(yul::Identifier const& _identifier) override
	{
		if (m_references.count(&_identifier))
		{
			auto const& reference = m_references.at(&_identifier);
			auto const varDecl = dynamic_cast(reference.declaration);
			solUnimplementedAssert(varDecl, "");
			if (reference.isOffset || reference.isSlot)
			{
				solAssert(reference.isOffset != reference.isSlot, "");
				pair slot_offset = m_context.storageLocationOfVariable(*varDecl);
				string const value = reference.isSlot ?
					slot_offset.first.str() :
					to_string(slot_offset.second);
				return yul::Literal{
					_identifier.location,
					yul::LiteralKind::Number,
					yul::YulString{value},
					{}
				};
			}
		}
		return ASTCopier::operator()(_identifier);
	}
	yul::YulString translateIdentifier(yul::YulString _name) override
	{
		// Strictly, the dialect used by inline assembly (m_dialect) could be different
		// from the Yul dialect we are compiling to. So we are assuming here that the builtin
		// functions are identical. This should not be a problem for now since everything
		// is EVM anyway.
		if (m_dialect.builtin(_name))
			return _name;
		else
			return yul::YulString{"usr$" + _name.str()};
	}
	yul::Identifier translate(yul::Identifier const& _identifier) override
	{
		if (!m_references.count(&_identifier))
			return ASTCopier::translate(_identifier);
		auto const& reference = m_references.at(&_identifier);
		auto const varDecl = dynamic_cast(reference.declaration);
		solUnimplementedAssert(varDecl, "");
		solAssert(
			reference.isOffset == false && reference.isSlot == false,
			"Should not be called for offset/slot"
		);
		return yul::Identifier{
			_identifier.location,
			yul::YulString{m_context.localVariable(*varDecl).name()}
		};
	}
private:
	yul::Dialect const& m_dialect;
	IRGenerationContext& m_context;
	ExternalRefsMap const& m_references;
};
}
string IRGeneratorForStatements::code() const
{
	solAssert(!m_currentLValue, "LValue not reset!");
	return m_code.str();
}
void IRGeneratorForStatements::initializeStateVar(VariableDeclaration const& _varDecl)
{
	solAssert(m_context.isStateVariable(_varDecl), "Must be a state variable.");
	solAssert(!_varDecl.isConstant(), "");
	solAssert(!_varDecl.immutable(), "");
	if (_varDecl.value())
	{
		_varDecl.value()->accept(*this);
		writeToLValue(IRLValue{
			*_varDecl.annotation().type,
			IRLValue::Storage{
				util::toCompactHexWithPrefix(m_context.storageLocationOfVariable(_varDecl).first),
				m_context.storageLocationOfVariable(_varDecl).second
			}
		}, *_varDecl.value());
	}
}
void IRGeneratorForStatements::initializeLocalVar(VariableDeclaration const& _varDecl)
{
	solAssert(m_context.isLocalVariable(_varDecl), "Must be a local variable.");
	auto const* type = _varDecl.type();
	if (auto const* refType = dynamic_cast(type))
		if (refType->dataStoredIn(DataLocation::Storage) && refType->isPointer())
			return;
	IRVariable zero = zeroValue(*type);
	assign(m_context.localVariable(_varDecl), zero);
}
void IRGeneratorForStatements::endVisit(VariableDeclarationStatement const& _varDeclStatement)
{
	if (Expression const* expression = _varDeclStatement.initialValue())
	{
		if (_varDeclStatement.declarations().size() > 1)
		{
			auto const* tupleType = dynamic_cast(expression->annotation().type);
			solAssert(tupleType, "Expected expression of tuple type.");
			solAssert(_varDeclStatement.declarations().size() == tupleType->components().size(), "Invalid number of tuple components.");
			for (size_t i = 0; i < _varDeclStatement.declarations().size(); ++i)
				if (auto const& decl = _varDeclStatement.declarations()[i])
				{
					solAssert(tupleType->components()[i], "");
					define(m_context.addLocalVariable(*decl), IRVariable(*expression).tupleComponent(i));
				}
		}
		else
		{
			VariableDeclaration const& varDecl = *_varDeclStatement.declarations().front();
			define(m_context.addLocalVariable(varDecl), *expression);
		}
	}
	else
		for (auto const& decl: _varDeclStatement.declarations())
			if (decl)
			{
				declare(m_context.addLocalVariable(*decl));
				initializeLocalVar(*decl);
			}
}
bool IRGeneratorForStatements::visit(Conditional const& _conditional)
{
	_conditional.condition().accept(*this);
	string condition = expressionAsType(_conditional.condition(), *TypeProvider::boolean());
	declare(_conditional);
	m_code << "switch " << condition << "\n" "case 0 {\n";
	_conditional.falseExpression().accept(*this);
	assign(_conditional, _conditional.falseExpression());
	m_code << "}\n" "default {\n";
	_conditional.trueExpression().accept(*this);
	assign(_conditional, _conditional.trueExpression());
	m_code << "}\n";
	return false;
}
bool IRGeneratorForStatements::visit(Assignment const& _assignment)
{
	_assignment.rightHandSide().accept(*this);
	Type const* intermediateType = type(_assignment.rightHandSide()).closestTemporaryType(
		&type(_assignment.leftHandSide())
	);
	IRVariable value = convert(_assignment.rightHandSide(), *intermediateType);
	_assignment.leftHandSide().accept(*this);
	solAssert(!!m_currentLValue, "LValue not retrieved.");
	if (_assignment.assignmentOperator() != Token::Assign)
	{
		solAssert(type(_assignment.leftHandSide()) == *intermediateType, "");
		solAssert(intermediateType->isValueType(), "Compound operators only available for value types.");
		IRVariable leftIntermediate = readFromLValue(*m_currentLValue);
		m_code << value.name() << " := " << binaryOperation(
			TokenTraits::AssignmentToBinaryOp(_assignment.assignmentOperator()),
			*intermediateType,
			leftIntermediate.name(),
			value.name()
		);
	}
	writeToLValue(*m_currentLValue, value);
	m_currentLValue.reset();
	if (*_assignment.annotation().type != *TypeProvider::emptyTuple())
		define(_assignment, value);
	return false;
}
bool IRGeneratorForStatements::visit(TupleExpression const& _tuple)
{
	if (_tuple.isInlineArray())
		solUnimplementedAssert(false, "");
	else
	{
		bool lValueRequested = _tuple.annotation().lValueRequested;
		if (lValueRequested)
			solAssert(!m_currentLValue, "");
		if (_tuple.components().size() == 1)
		{
			solAssert(_tuple.components().front(), "");
			_tuple.components().front()->accept(*this);
			if (lValueRequested)
				solAssert(!!m_currentLValue, "");
			else
				define(_tuple, *_tuple.components().front());
		}
		else
		{
			vector> lvalues;
			for (size_t i = 0; i < _tuple.components().size(); ++i)
				if (auto const& component = _tuple.components()[i])
				{
					component->accept(*this);
					if (lValueRequested)
					{
						solAssert(!!m_currentLValue, "");
						lvalues.emplace_back(std::move(m_currentLValue));
						m_currentLValue.reset();
					}
					else
						define(IRVariable(_tuple).tupleComponent(i), *component);
				}
				else if (lValueRequested)
					lvalues.emplace_back();
			if (_tuple.annotation().lValueRequested)
				m_currentLValue.emplace(IRLValue{
					*_tuple.annotation().type,
					IRLValue::Tuple{std::move(lvalues)}
				});
		}
	}
	return false;
}
bool IRGeneratorForStatements::visit(IfStatement const& _ifStatement)
{
	_ifStatement.condition().accept(*this);
	string condition = expressionAsType(_ifStatement.condition(), *TypeProvider::boolean());
	if (_ifStatement.falseStatement())
	{
		m_code << "switch " << condition << "\n" "case 0 {\n";
		_ifStatement.falseStatement()->accept(*this);
		m_code << "}\n" "default {\n";
	}
	else
		m_code << "if " << condition << " {\n";
	_ifStatement.trueStatement().accept(*this);
	m_code << "}\n";
	return false;
}
bool IRGeneratorForStatements::visit(ForStatement const& _forStatement)
{
	generateLoop(
		_forStatement.body(),
		_forStatement.condition(),
		_forStatement.initializationExpression(),
		_forStatement.loopExpression()
	);
	return false;
}
bool IRGeneratorForStatements::visit(WhileStatement const& _whileStatement)
{
	generateLoop(
		_whileStatement.body(),
		&_whileStatement.condition(),
		nullptr,
		nullptr,
		_whileStatement.isDoWhile()
	);
	return false;
}
bool IRGeneratorForStatements::visit(Continue const&)
{
	m_code << "continue\n";
	return false;
}
bool IRGeneratorForStatements::visit(Break const&)
{
	m_code << "break\n";
	return false;
}
void IRGeneratorForStatements::endVisit(Return const& _return)
{
	if (Expression const* value = _return.expression())
	{
		solAssert(_return.annotation().functionReturnParameters, "Invalid return parameters pointer.");
		vector> const& returnParameters =
			_return.annotation().functionReturnParameters->parameters();
		if (returnParameters.size() > 1)
			for (size_t i = 0; i < returnParameters.size(); ++i)
				assign(m_context.localVariable(*returnParameters[i]), IRVariable(*value).tupleComponent(i));
		else if (returnParameters.size() == 1)
			assign(m_context.localVariable(*returnParameters.front()), *value);
	}
	m_code << "leave\n";
}
void IRGeneratorForStatements::endVisit(UnaryOperation const& _unaryOperation)
{
	Type const& resultType = type(_unaryOperation);
	Token const op = _unaryOperation.getOperator();
	if (op == Token::Delete)
	{
		solAssert(!!m_currentLValue, "LValue not retrieved.");
		std::visit(
			util::GenericVisitor{
				[&](IRLValue::Storage const& _storage) {
					m_code <<
						m_utils.storageSetToZeroFunction(m_currentLValue->type) <<
						"(" <<
						_storage.slot <<
						", " <<
						_storage.offsetString() <<
						")\n";
					m_currentLValue.reset();
				},
				[&](auto const&) {
					IRVariable zeroValue(m_context.newYulVariable(), m_currentLValue->type);
					define(zeroValue) << m_utils.zeroValueFunction(m_currentLValue->type) << "()\n";
					writeToLValue(*m_currentLValue, zeroValue);
					m_currentLValue.reset();
				}
			},
			m_currentLValue->kind
		);
	}
	else if (resultType.category() == Type::Category::RationalNumber)
		define(_unaryOperation) << formatNumber(resultType.literalValue(nullptr)) << "\n";
	else if (resultType.category() == Type::Category::Integer)
	{
		solAssert(resultType == type(_unaryOperation.subExpression()), "Result type doesn't match!");
		if (op == Token::Inc || op == Token::Dec)
		{
			solAssert(!!m_currentLValue, "LValue not retrieved.");
			IRVariable modifiedValue(m_context.newYulVariable(), resultType);
			IRVariable originalValue = readFromLValue(*m_currentLValue);
			define(modifiedValue) <<
				(op == Token::Inc ?
					m_utils.incrementCheckedFunction(resultType) :
					m_utils.decrementCheckedFunction(resultType)
				) <<
				"(" <<
				originalValue.name() <<
				")\n";
			writeToLValue(*m_currentLValue, modifiedValue);
			m_currentLValue.reset();
			define(_unaryOperation, _unaryOperation.isPrefixOperation() ? modifiedValue : originalValue);
		}
		else if (op == Token::BitNot)
			appendSimpleUnaryOperation(_unaryOperation, _unaryOperation.subExpression());
		else if (op == Token::Add)
			// According to SyntaxChecker...
			solAssert(false, "Use of unary + is disallowed.");
		else if (op == Token::Sub)
		{
			IntegerType const& intType = *dynamic_cast(&resultType);
			define(_unaryOperation) <<
				m_utils.negateNumberCheckedFunction(intType) <<
				"(" <<
				IRVariable(_unaryOperation.subExpression()).name() <<
				")\n";
		}
		else
			solUnimplementedAssert(false, "Unary operator not yet implemented");
	}
	else if (resultType.category() == Type::Category::Bool)
	{
		solAssert(
			_unaryOperation.getOperator() != Token::BitNot,
			"Bitwise Negation can't be done on bool!"
		);
		appendSimpleUnaryOperation(_unaryOperation, _unaryOperation.subExpression());
	}
	else
		solUnimplementedAssert(false, "Unary operator not yet implemented");
}
bool IRGeneratorForStatements::visit(BinaryOperation const& _binOp)
{
	solAssert(!!_binOp.annotation().commonType, "");
	TypePointer commonType = _binOp.annotation().commonType;
	langutil::Token op = _binOp.getOperator();
	if (op == Token::And || op == Token::Or)
	{
		// This can short-circuit!
		appendAndOrOperatorCode(_binOp);
		return false;
	}
	_binOp.leftExpression().accept(*this);
	_binOp.rightExpression().accept(*this);
	if (commonType->category() == Type::Category::RationalNumber)
		define(_binOp) << toCompactHexWithPrefix(commonType->literalValue(nullptr)) << "\n";
	else if (TokenTraits::isCompareOp(op))
	{
		if (auto type = dynamic_cast(commonType))
		{
			solAssert(op == Token::Equal || op == Token::NotEqual, "Invalid function pointer comparison!");
			solAssert(type->kind() != FunctionType::Kind::External, "External function comparison not allowed!");
		}
		solAssert(commonType->isValueType(), "");
		bool isSigned = false;
		if (auto type = dynamic_cast(commonType))
			isSigned = type->isSigned();
		string args =
			expressionAsType(_binOp.leftExpression(), *commonType) +
			", " +
			expressionAsType(_binOp.rightExpression(), *commonType);
		string expr;
		if (op == Token::Equal)
			expr = "eq(" + move(args) + ")";
		else if (op == Token::NotEqual)
			expr = "iszero(eq(" + move(args) + "))";
		else if (op == Token::GreaterThanOrEqual)
			expr = "iszero(" + string(isSigned ? "slt(" : "lt(") + move(args) + "))";
		else if (op == Token::LessThanOrEqual)
			expr = "iszero(" + string(isSigned ? "sgt(" : "gt(") + move(args) + "))";
		else if (op == Token::GreaterThan)
			expr = (isSigned ? "sgt(" : "gt(") + move(args) + ")";
		else if (op == Token::LessThan)
			expr = (isSigned ? "slt(" : "lt(") + move(args) + ")";
		else
			solAssert(false, "Unknown comparison operator.");
		define(_binOp) << expr << "\n";
	}
	else
	{
		string left = expressionAsType(_binOp.leftExpression(), *commonType);
		string right = expressionAsType(_binOp.rightExpression(), *commonType);
		define(_binOp) << binaryOperation(_binOp.getOperator(), *commonType, left, right) << "\n";
	}
	return false;
}
void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
{
	solUnimplementedAssert(
		_functionCall.annotation().kind == FunctionCallKind::FunctionCall ||
		_functionCall.annotation().kind == FunctionCallKind::TypeConversion,
		"This type of function call is not yet implemented"
	);
	Type const& funcType = type(_functionCall.expression());
	if (_functionCall.annotation().kind == FunctionCallKind::TypeConversion)
	{
		solAssert(funcType.category() == Type::Category::TypeType, "Expected category to be TypeType");
		solAssert(_functionCall.arguments().size() == 1, "Expected one argument for type conversion");
		define(_functionCall, *_functionCall.arguments().front());
		return;
	}
	FunctionTypePointer functionType = dynamic_cast(&funcType);
	TypePointers parameterTypes = functionType->parameterTypes();
	vector> const& callArguments = _functionCall.arguments();
	vector> const& callArgumentNames = _functionCall.names();
	if (!functionType->takesArbitraryParameters())
		solAssert(callArguments.size() == parameterTypes.size(), "");
	vector> arguments;
	if (callArgumentNames.empty())
		// normal arguments
		arguments = callArguments;
	else
		// named arguments
		for (auto const& parameterName: functionType->parameterNames())
		{
			auto const it = std::find_if(callArgumentNames.cbegin(), callArgumentNames.cend(), [&](ASTPointer const& _argName) {
				return *_argName == parameterName;
			});
			solAssert(it != callArgumentNames.cend(), "");
			arguments.push_back(callArguments[std::distance(callArgumentNames.begin(), it)]);
		}
	solUnimplementedAssert(!functionType->bound(), "");
	switch (functionType->kind())
	{
	case FunctionType::Kind::Internal:
	{
		vector args;
		for (unsigned i = 0; i < arguments.size(); ++i)
			if (functionType->takesArbitraryParameters())
				args.emplace_back(IRVariable(*arguments[i]).commaSeparatedList());
			else
				args.emplace_back(convert(*arguments[i], *parameterTypes[i]).commaSeparatedList());
		if (auto identifier = dynamic_cast(&_functionCall.expression()))
		{
			solAssert(!functionType->bound(), "");
			if (auto functionDef = dynamic_cast(identifier->annotation().referencedDeclaration))
			{
				define(_functionCall) <<
					m_context.virtualFunctionName(*functionDef) <<
					"(" <<
					joinHumanReadable(args) <<
					")\n";
				return;
			}
		}
		define(_functionCall) <<
			m_context.internalDispatch(
				TupleType(functionType->parameterTypes()).sizeOnStack(),
				TupleType(functionType->returnParameterTypes()).sizeOnStack()
			) <<
			"(" <<
			IRVariable(_functionCall.expression()).part("functionIdentifier").name() <<
			joinHumanReadablePrefixed(args) <<
			")\n";
		break;
	}
	case FunctionType::Kind::External:
	case FunctionType::Kind::DelegateCall:
	case FunctionType::Kind::BareCall:
	case FunctionType::Kind::BareDelegateCall:
	case FunctionType::Kind::BareStaticCall:
		appendExternalFunctionCall(_functionCall, arguments);
		break;
	case FunctionType::Kind::BareCallCode:
		solAssert(false, "Callcode has been removed.");
	case FunctionType::Kind::Event:
	{
		auto const& event = dynamic_cast(functionType->declaration());
		TypePointers paramTypes = functionType->parameterTypes();
		ABIFunctions abi(m_context.evmVersion(), m_context.revertStrings(), m_context.functionCollector());
		vector indexedArgs;
		string nonIndexedArgs;
		TypePointers nonIndexedArgTypes;
		TypePointers nonIndexedParamTypes;
		if (!event.isAnonymous())
			define(indexedArgs.emplace_back(m_context.newYulVariable(), *TypeProvider::uint256())) <<
				formatNumber(u256(h256::Arith(keccak256(functionType->externalSignature())))) << "\n";
		for (size_t i = 0; i < event.parameters().size(); ++i)
		{
			Expression const& arg = *arguments[i];
			if (event.parameters()[i]->isIndexed())
			{
				string value;
				if (auto const& referenceType = dynamic_cast(paramTypes[i]))
					define(indexedArgs.emplace_back(m_context.newYulVariable(), *TypeProvider::uint256())) <<
						m_utils.packedHashFunction({arg.annotation().type}, {referenceType}) <<
						"(" <<
						IRVariable(arg).commaSeparatedList() <<
						")";
				else
					indexedArgs.emplace_back(convert(arg, *paramTypes[i]));
			}
			else
			{
				string vars = IRVariable(arg).commaSeparatedList();
				if (!vars.empty())
					// In reverse because abi_encode expects it like that.
					nonIndexedArgs = ", " + move(vars) + nonIndexedArgs;
				nonIndexedArgTypes.push_back(arg.annotation().type);
				nonIndexedParamTypes.push_back(paramTypes[i]);
			}
		}
		solAssert(indexedArgs.size() <= 4, "Too many indexed arguments.");
		Whiskers templ(R"({
			let  := 
			let  := ( )
			(, sub(, ) )
		})");
		templ("pos", m_context.newYulVariable());
		templ("end", m_context.newYulVariable());
		templ("freeMemory", freeMemory());
		templ("encode", abi.tupleEncoder(nonIndexedArgTypes, nonIndexedParamTypes));
		templ("nonIndexedArgs", nonIndexedArgs);
		templ("log", "log" + to_string(indexedArgs.size()));
		templ("indexedArgs", joinHumanReadablePrefixed(indexedArgs | boost::adaptors::transformed([&](auto const& _arg) {
			return _arg.commaSeparatedList();
		})));
		m_code << templ.render();
		break;
	}
	case FunctionType::Kind::Assert:
	case FunctionType::Kind::Require:
	{
		solAssert(arguments.size() > 0, "Expected at least one parameter for require/assert");
		solAssert(arguments.size() <= 2, "Expected no more than two parameters for require/assert");
		Type const* messageArgumentType = arguments.size() > 1 ? arguments[1]->annotation().type : nullptr;
		string requireOrAssertFunction = m_utils.requireOrAssertFunction(
			functionType->kind() == FunctionType::Kind::Assert,
			messageArgumentType
		);
		m_code << move(requireOrAssertFunction) << "(" << IRVariable(*arguments[0]).name();
		if (messageArgumentType && messageArgumentType->sizeOnStack() > 0)
			m_code << ", " << IRVariable(*arguments[1]).commaSeparatedList();
		m_code << ")\n";
		break;
	}
	// Array creation using new
	case FunctionType::Kind::ObjectCreation:
	{
		ArrayType const& arrayType = dynamic_cast(*_functionCall.annotation().type);
		solAssert(arguments.size() == 1, "");
		IRVariable value = convert(*arguments[0], *TypeProvider::uint256());
		define(_functionCall) <<
			m_utils.allocateAndInitializeMemoryArrayFunction(arrayType) <<
			"(" <<
			value.commaSeparatedList() <<
			")\n";
		break;
	}
	case FunctionType::Kind::KECCAK256:
	{
		solAssert(arguments.size() == 1, "");
		ArrayType const* arrayType = TypeProvider::bytesMemory();
		auto array = convert(*arguments[0], *arrayType);
		define(_functionCall) <<
			"keccak256(" <<
			m_utils.arrayDataAreaFunction(*arrayType) <<
			"(" <<
			array.commaSeparatedList() <<
			"), " <<
			m_utils.arrayLengthFunction(*arrayType) <<
			"(" <<
			array.commaSeparatedList() <<
			"))\n";
		break;
	}
	case FunctionType::Kind::ArrayPop:
	{
		auto const& memberAccessExpression = dynamic_cast(_functionCall.expression()).expression();
		ArrayType const& arrayType = dynamic_cast(*memberAccessExpression.annotation().type);
		define(_functionCall) <<
			m_utils.storageArrayPopFunction(arrayType) <<
			"(" <<
			IRVariable(_functionCall.expression()).commaSeparatedList() <<
			")\n";
		break;
	}
	case FunctionType::Kind::ArrayPush:
	{
		auto const& memberAccessExpression = dynamic_cast(_functionCall.expression()).expression();
		ArrayType const& arrayType = dynamic_cast(*memberAccessExpression.annotation().type);
		if (arguments.empty())
		{
			auto slotName = m_context.newYulVariable();
			auto offsetName = m_context.newYulVariable();
			m_code << "let " << slotName << ", " << offsetName << " := " <<
				m_utils.storageArrayPushZeroFunction(arrayType) <<
				"(" << IRVariable(_functionCall.expression()).commaSeparatedList() << ")\n";
			setLValue(_functionCall, IRLValue{
				*arrayType.baseType(),
				IRLValue::Storage{
					slotName,
					offsetName,
				}
			});
		}
		else
		{
			IRVariable argument = convert(*arguments.front(), *arrayType.baseType());
			m_code <<
				m_utils.storageArrayPushFunction(arrayType) <<
				"(" <<
				IRVariable(_functionCall.expression()).commaSeparatedList() <<
				", " <<
				argument.commaSeparatedList() <<
				")\n";
		}
		break;
	}
	default:
		solUnimplemented("FunctionKind " + toString(static_cast(functionType->kind())) + " not yet implemented");
	}
}
void IRGeneratorForStatements::endVisit(FunctionCallOptions const& _options)
{
	FunctionType const& previousType = dynamic_cast(*_options.expression().annotation().type);
	solUnimplementedAssert(!previousType.bound(), "");
	// Copy over existing values.
	for (auto const& item: previousType.stackItems())
		define(IRVariable(_options).part(get<0>(item)), IRVariable(_options.expression()).part(get<0>(item)));
	for (size_t i = 0; i < _options.names().size(); ++i)
	{
		string const& name = *_options.names()[i];
		solAssert(name == "salt" || name == "gas" || name == "value", "");
		define(IRVariable(_options).part(name), *_options.options()[i]);
	}
}
void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
{
	ASTString const& member = _memberAccess.memberName();
	if (auto funType = dynamic_cast(_memberAccess.annotation().type))
		if (funType->bound())
		{
			solUnimplementedAssert(false, "");
		}
	switch (_memberAccess.expression().annotation().type->category())
	{
	case Type::Category::Contract:
	{
		ContractType const& type = dynamic_cast(*_memberAccess.expression().annotation().type);
		if (type.isSuper())
		{
			solUnimplementedAssert(false, "");
		}
		// ordinary contract type
		else if (Declaration const* declaration = _memberAccess.annotation().referencedDeclaration)
		{
			u256 identifier;
			if (auto const* variable = dynamic_cast(declaration))
				identifier = FunctionType(*variable).externalIdentifier();
			else if (auto const* function = dynamic_cast(declaration))
				identifier = FunctionType(*function).externalIdentifier();
			else
				solAssert(false, "Contract member is neither variable nor function.");
			define(IRVariable(_memberAccess).part("address"), _memberAccess.expression());
			define(IRVariable(_memberAccess).part("functionIdentifier")) << formatNumber(identifier) << "\n";
		}
		else
			solAssert(false, "Invalid member access in contract");
		break;
	}
	case Type::Category::Integer:
	{
		solAssert(false, "Invalid member access to integer");
		break;
	}
	case Type::Category::Address:
	{
		if (member == "balance")
			define(_memberAccess) <<
				"balance(" <<
				expressionAsType(_memberAccess.expression(), *TypeProvider::address()) <<
				")\n";
		else if (set{"send", "transfer"}.count(member))
		{
			solAssert(dynamic_cast(*_memberAccess.expression().annotation().type).stateMutability() == StateMutability::Payable, "");
			define(IRVariable{_memberAccess}.part("address"), _memberAccess.expression());
		}
		else if (set{"call", "callcode", "delegatecall", "staticcall"}.count(member))
			define(IRVariable{_memberAccess}.part("address"), _memberAccess.expression());
		else
			solAssert(false, "Invalid member access to address");
		break;
	}
	case Type::Category::Function:
		if (member == "selector")
		{
			solUnimplementedAssert(
				dynamic_cast(*_memberAccess.expression().annotation().type).kind() ==
				FunctionType::Kind::External, ""
			);
			define(IRVariable{_memberAccess}, IRVariable(_memberAccess.expression()).part("functionIdentifier"));
		}
		else if (member == "address")
		{
			solUnimplementedAssert(
				dynamic_cast(*_memberAccess.expression().annotation().type).kind() ==
				FunctionType::Kind::External, ""
			);
			define(IRVariable{_memberAccess}, IRVariable(_memberAccess.expression()).part("address"));
		}
		else
			solAssert(
				!!_memberAccess.expression().annotation().type->memberType(member),
				"Invalid member access to function."
			);
		break;
	case Type::Category::Magic:
		// we can ignore the kind of magic and only look at the name of the member
		if (member == "coinbase")
			define(_memberAccess) << "coinbase()\n";
		else if (member == "timestamp")
			define(_memberAccess) << "timestamp()\n";
		else if (member == "difficulty")
			define(_memberAccess) << "difficulty()\n";
		else if (member == "number")
			define(_memberAccess) << "number()\n";
		else if (member == "gaslimit")
			define(_memberAccess) << "gaslimit()\n";
		else if (member == "sender")
			define(_memberAccess) << "caller()\n";
		else if (member == "value")
			define(_memberAccess) << "callvalue()\n";
		else if (member == "origin")
			define(_memberAccess) << "origin()\n";
		else if (member == "gasprice")
			define(_memberAccess) << "gasprice()\n";
		else if (member == "data")
		{
			IRVariable var(_memberAccess);
			declare(var);
			define(var.part("offset")) << "0\n";
			define(var.part("length")) << "calldatasize()\n";
		}
		else if (member == "sig")
			define(_memberAccess) <<
				"and(calldataload(0), " <<
				formatNumber(u256(0xffffffff) << (256 - 32)) <<
				")\n";
		else if (member == "gas")
			solAssert(false, "Gas has been removed.");
		else if (member == "blockhash")
			solAssert(false, "Blockhash has been removed.");
		else if (member == "creationCode" || member == "runtimeCode")
		{
			solUnimplementedAssert(false, "");
		}
		else if (member == "name")
		{
			solUnimplementedAssert(false, "");
		}
		else if (set{"encode", "encodePacked", "encodeWithSelector", "encodeWithSignature", "decode"}.count(member))
		{
			// no-op
		}
		else
			solAssert(false, "Unknown magic member.");
		break;
	case Type::Category::Struct:
	{
		solUnimplementedAssert(false, "");
	}
	case Type::Category::Enum:
	{
		EnumType const& type = dynamic_cast(*_memberAccess.expression().annotation().type);
		define(_memberAccess) << to_string(type.memberValue(_memberAccess.memberName())) << "\n";
		break;
	}
	case Type::Category::Array:
	{
		auto const& type = dynamic_cast(*_memberAccess.expression().annotation().type);
		if (member == "length")
		{
			if (!type.isDynamicallySized())
				define(_memberAccess) << type.length() << "\n";
			else
				switch (type.location())
				{
					case DataLocation::CallData:
						define(_memberAccess, IRVariable(_memberAccess.expression()).part("length"));
						break;
					case DataLocation::Storage:
					{
						define(_memberAccess) <<
							m_utils.arrayLengthFunction(type) <<
							"(" <<
							IRVariable(_memberAccess.expression()).commaSeparatedList() <<
							")\n";
						break;
					}
					case DataLocation::Memory:
						define(_memberAccess) <<
							"mload(" <<
							IRVariable(_memberAccess.expression()).commaSeparatedList() <<
							")\n";
						break;
				}
		}
		else if (member == "pop" || member == "push")
		{
			solAssert(type.location() == DataLocation::Storage, "");
			define(IRVariable{_memberAccess}.part("slot"), IRVariable{_memberAccess.expression()}.part("slot"));
		}
		else
			solAssert(false, "Invalid array member access.");
		break;
	}
	case Type::Category::FixedBytes:
	{
		auto const& type = dynamic_cast(*_memberAccess.expression().annotation().type);
		if (member == "length")
			define(_memberAccess) << to_string(type.numBytes()) << "\n";
		else
			solAssert(false, "Illegal fixed bytes member.");
		break;
	}
	default:
		solAssert(false, "Member access to unknown type.");
	}
}
bool IRGeneratorForStatements::visit(InlineAssembly const& _inlineAsm)
{
	CopyTranslate bodyCopier{_inlineAsm.dialect(), m_context, _inlineAsm.annotation().externalReferences};
	yul::Statement modified = bodyCopier(_inlineAsm.operations());
	solAssert(holds_alternative(modified), "");
	// Do not provide dialect so that we get the full type information.
	m_code << yul::AsmPrinter()(std::get(std::move(modified))) << "\n";
	return false;
}
void IRGeneratorForStatements::endVisit(IndexAccess const& _indexAccess)
{
	Type const& baseType = *_indexAccess.baseExpression().annotation().type;
	if (baseType.category() == Type::Category::Mapping)
	{
		solAssert(_indexAccess.indexExpression(), "Index expression expected.");
		MappingType const& mappingType = dynamic_cast(baseType);
		Type const& keyType = *_indexAccess.indexExpression()->annotation().type;
		solAssert(keyType.sizeOnStack() <= 1, "");
		string slot = m_context.newYulVariable();
		Whiskers templ("let  := ( )\n");
		templ("slot", slot);
		templ("indexAccess", m_utils.mappingIndexAccessFunction(mappingType, keyType));
		templ("base", IRVariable(_indexAccess.baseExpression()).commaSeparatedList());
		if (keyType.sizeOnStack() == 0)
			templ("key", "");
		else
			templ("key", ", " + IRVariable(*_indexAccess.indexExpression()).commaSeparatedList());
		m_code << templ.render();
		setLValue(_indexAccess, IRLValue{
			*_indexAccess.annotation().type,
			IRLValue::Storage{
				slot,
				0u
			}
		});
	}
	else if (baseType.category() == Type::Category::Array || baseType.category() == Type::Category::ArraySlice)
	{
		ArrayType const& arrayType =
			baseType.category() == Type::Category::Array ?
			dynamic_cast(baseType) :
			dynamic_cast(baseType).arrayType();
		if (baseType.category() == Type::Category::ArraySlice)
			solAssert(arrayType.dataStoredIn(DataLocation::CallData) && arrayType.isDynamicallySized(), "");
		solAssert(_indexAccess.indexExpression(), "Index expression expected.");
		switch (arrayType.location())
		{
			case DataLocation::Storage:
			{
				string slot = m_context.newYulVariable();
				string offset = m_context.newYulVariable();
				m_code << Whiskers(R"(
					let ,  := (, )
				)")
				("slot", slot)
				("offset", offset)
				("indexFunc", m_utils.storageArrayIndexAccessFunction(arrayType))
				("array", IRVariable(_indexAccess.baseExpression()).part("slot").name())
				("index", IRVariable(*_indexAccess.indexExpression()).name())
				.render();
				setLValue(_indexAccess, IRLValue{
					*_indexAccess.annotation().type,
					IRLValue::Storage{slot, offset}
				});
				break;
			}
			case DataLocation::Memory:
			{
				string const memAddress =
					m_utils.memoryArrayIndexAccessFunction(arrayType) +
					"(" +
					IRVariable(_indexAccess.baseExpression()).part("mpos").name() +
					", " +
					expressionAsType(*_indexAccess.indexExpression(), *TypeProvider::uint256()) +
					")";
				setLValue(_indexAccess, IRLValue{
					*arrayType.baseType(),
					IRLValue::Memory{memAddress}
				});
				break;
			}
			case DataLocation::CallData:
			{
				IRVariable var(m_context.newYulVariable(), *arrayType.baseType());
				define(var) <<
					m_utils.calldataArrayIndexAccessFunction(arrayType) <<
					"(" <<
					IRVariable(_indexAccess.baseExpression()).commaSeparatedList() <<
					", " <<
					expressionAsType(*_indexAccess.indexExpression(), *TypeProvider::uint256()) <<
					")\n";
				if (arrayType.isByteArray())
					define(_indexAccess) <<
						m_utils.cleanupFunction(*arrayType.baseType()) <<
						"(calldataload(" <<
						var.name() <<
						"))\n";
				else if (arrayType.baseType()->isValueType())
					define(_indexAccess) <<
						m_utils.readFromCalldata(*arrayType.baseType()) <<
						"(" <<
						var.commaSeparatedList() <<
						")\n";
				else
					define(_indexAccess, var);
				break;
			}
		}
	}
	else if (baseType.category() == Type::Category::FixedBytes)
		solUnimplementedAssert(false, "");
	else if (baseType.category() == Type::Category::TypeType)
	{
		solAssert(baseType.sizeOnStack() == 0, "");
		solAssert(_indexAccess.annotation().type->sizeOnStack() == 0, "");
		// no-op - this seems to be a lone array type (`structType[];`)
	}
	else
		solAssert(false, "Index access only allowed for mappings or arrays.");
}
void IRGeneratorForStatements::endVisit(IndexRangeAccess const& _indexRangeAccess)
{
	Type const& baseType = *_indexRangeAccess.baseExpression().annotation().type;
	solAssert(
		baseType.category() == Type::Category::Array || baseType.category() == Type::Category::ArraySlice,
		"Index range accesses is available only on arrays and array slices."
	);
	ArrayType const& arrayType =
		baseType.category() == Type::Category::Array ?
		dynamic_cast(baseType) :
		dynamic_cast(baseType).arrayType();
	switch (arrayType.location())
	{
		case DataLocation::CallData:
		{
			solAssert(baseType.isDynamicallySized(), "");
			IRVariable sliceStart{m_context.newYulVariable(), *TypeProvider::uint256()};
			if (_indexRangeAccess.startExpression())
				define(sliceStart, IRVariable{*_indexRangeAccess.startExpression()});
			else
				define(sliceStart) << u256(0) << "\n";
			IRVariable sliceEnd{
				m_context.newYulVariable(),
				*TypeProvider::uint256()
			};
			if (_indexRangeAccess.endExpression())
				define(sliceEnd, IRVariable{*_indexRangeAccess.endExpression()});
			else
				define(sliceEnd, IRVariable{_indexRangeAccess.baseExpression()}.part("length"));
			IRVariable range{_indexRangeAccess};
			define(range) <<
				m_utils.calldataArrayIndexRangeAccess(arrayType) << "(" <<
				IRVariable{_indexRangeAccess.baseExpression()}.commaSeparatedList() << ", " <<
				sliceStart.name() << ", " <<
				sliceEnd.name() << ")\n";
			break;
		}
		default:
			solUnimplementedAssert(false, "Index range accesses is implemented only on calldata arrays.");
	}
}
void IRGeneratorForStatements::endVisit(Identifier const& _identifier)
{
	Declaration const* declaration = _identifier.annotation().referencedDeclaration;
	if (MagicVariableDeclaration const* magicVar = dynamic_cast(declaration))
	{
		switch (magicVar->type()->category())
		{
		case Type::Category::Contract:
			if (dynamic_cast(*magicVar->type()).isSuper())
				solAssert(_identifier.name() == "super", "");
			else
			{
				solAssert(_identifier.name() == "this", "");
				define(_identifier) << "address()\n";
			}
			break;
		case Type::Category::Integer:
			solAssert(_identifier.name() == "now", "");
			define(_identifier) << "timestamp()\n";
			break;
		default:
			break;
		}
		return;
	}
	else if (FunctionDefinition const* functionDef = dynamic_cast(declaration))
		define(_identifier) << to_string(functionDef->resolveVirtual(m_context.mostDerivedContract()).id()) << "\n";
	else if (VariableDeclaration const* varDecl = dynamic_cast(declaration))
	{
		// TODO for the constant case, we have to be careful:
		// If the value is visited twice, `defineExpression` is called twice on
		// the same expression.
		solUnimplementedAssert(!varDecl->isConstant(), "");
		solUnimplementedAssert(!varDecl->immutable(), "");
		if (m_context.isLocalVariable(*varDecl))
			setLValue(_identifier, IRLValue{
				*varDecl->annotation().type,
				IRLValue::Stack{m_context.localVariable(*varDecl)}
			});
		else if (m_context.isStateVariable(*varDecl))
			setLValue(_identifier, IRLValue{
				*varDecl->annotation().type,
				IRLValue::Storage{
					toCompactHexWithPrefix(m_context.storageLocationOfVariable(*varDecl).first),
					m_context.storageLocationOfVariable(*varDecl).second
				}
			});
		else
			solAssert(false, "Invalid variable kind.");
	}
	else if (auto contract = dynamic_cast(declaration))
	{
		solUnimplementedAssert(!contract->isLibrary(), "Libraries not yet supported.");
	}
	else if (dynamic_cast(declaration))
	{
		// no-op
	}
	else if (dynamic_cast(declaration))
	{
		// no-op
	}
	else if (dynamic_cast(declaration))
	{
		// no-op
	}
	else
	{
		solAssert(false, "Identifier type not expected in expression context.");
	}
}
bool IRGeneratorForStatements::visit(Literal const& _literal)
{
	Type const& literalType = type(_literal);
	switch (literalType.category())
	{
	case Type::Category::RationalNumber:
	case Type::Category::Bool:
	case Type::Category::Address:
		define(_literal) << toCompactHexWithPrefix(literalType.literalValue(&_literal)) << "\n";
		break;
	case Type::Category::StringLiteral:
		break; // will be done during conversion
	default:
		solUnimplemented("Only integer, boolean and string literals implemented for now.");
	}
	return false;
}
void IRGeneratorForStatements::appendExternalFunctionCall(
	FunctionCall const& _functionCall,
	vector> const& _arguments
)
{
	FunctionType const& funType = dynamic_cast(type(_functionCall.expression()));
	solAssert(
		funType.takesArbitraryParameters() ||
		_arguments.size() == funType.parameterTypes().size(), ""
	);
	solUnimplementedAssert(!funType.bound(), "");
	FunctionType::Kind funKind = funType.kind();
	solAssert(funKind != FunctionType::Kind::BareStaticCall || m_context.evmVersion().hasStaticCall(), "");
	solAssert(funKind != FunctionType::Kind::BareCallCode, "Callcode has been removed.");
	bool returnSuccessConditionAndReturndata = funKind == FunctionType::Kind::BareCall || funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::BareStaticCall;
	bool isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall;
	bool useStaticCall = funKind == FunctionType::Kind::BareStaticCall || (funType.stateMutability() <= StateMutability::View && m_context.evmVersion().hasStaticCall());
	bool haveReturndatacopy = m_context.evmVersion().supportsReturndata();
	unsigned estimatedReturnSize = 0;
	bool dynamicReturnSize = false;
	TypePointers returnTypes;
	if (!returnSuccessConditionAndReturndata)
	{
		if (haveReturndatacopy)
			returnTypes = funType.returnParameterTypes();
		else
			returnTypes = funType.returnParameterTypesWithoutDynamicTypes();
		for (auto const& retType: returnTypes)
			if (retType->isDynamicallyEncoded())
			{
				solAssert(haveReturndatacopy, "");
				dynamicReturnSize = true;
				estimatedReturnSize = 0;
				break;
			}
			else if (retType->decodingType())
				estimatedReturnSize += retType->decodingType()->calldataEncodedSize();
			else
				estimatedReturnSize += retType->calldataEncodedSize();
	}
	TypePointers argumentTypes;
	vector argumentStrings;
	for (auto const& arg: _arguments)
	{
		argumentTypes.emplace_back(&type(*arg));
		argumentStrings.emplace_back(IRVariable(*arg).commaSeparatedList());
	}
	string argumentString = argumentStrings.empty() ? ""s : (", " + joinHumanReadable(argumentStrings));
	solUnimplementedAssert(funKind != FunctionType::Kind::ECRecover, "");
	if (!m_context.evmVersion().canOverchargeGasForCall())
	{
		// Touch the end of the output area so that we do not pay for memory resize during the call
		// (which we would have to subtract from the gas left)
		// We could also just use MLOAD; POP right before the gas calculation, but the optimizer
		// would remove that, so we use MSTORE here.
		if (!funType.gasSet() && estimatedReturnSize > 0)
			m_code << "mstore(add(" << freeMemory() << ", " << to_string(estimatedReturnSize) << "), 0)\n";
	}
	ABIFunctions abi(m_context.evmVersion(), m_context.revertStrings(), m_context.functionCollector());
	solUnimplementedAssert(!funType.isBareCall(), "");
	Whiskers templ(R"(
		
			if iszero(extcodesize()) { revert(0, 0) }
		
		let  := 
		mstore(, ())
		let  := (add(, 4) )
		let  := (, ,  ,  , sub(, ), , )
		if iszero() { () }
		
			returndatacopy(, 0, returndatasize())
		
		mstore(, add(, and(add(, 0x1f), not(0x1f))))
		 let  :=  (, add(, ))
	)");
	templ("pos", m_context.newYulVariable());
	templ("end", m_context.newYulVariable());
	templ("result", m_context.newYulVariable());
	templ("freeMemory", freeMemory());
	templ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer));
	templ("shl28", m_utils.shiftLeftFunction(8 * (32 - 4)));
	templ("funId", IRVariable(_functionCall.expression()).part("functionIdentifier").name());
	templ("address", IRVariable(_functionCall.expression()).part("address").name());
	// If the function takes arbitrary parameters or is a bare call, copy dynamic length data in place.
	// Move arguments to memory, will not update the free memory pointer (but will update the memory
	// pointer on the stack).
	bool encodeInPlace = funType.takesArbitraryParameters() || funType.isBareCall();
	if (funType.kind() == FunctionType::Kind::ECRecover)
		// This would be the only combination of padding and in-place encoding,
		// but all parameters of ecrecover are value types anyway.
		encodeInPlace = false;
	bool encodeForLibraryCall = funKind == FunctionType::Kind::DelegateCall;
	solUnimplementedAssert(!encodeInPlace, "");
	solUnimplementedAssert(funType.padArguments(), "");
	templ("encodeArgs", abi.tupleEncoder(argumentTypes, funType.parameterTypes(), encodeForLibraryCall));
	templ("argumentString", argumentString);
	// Output data will replace input data, unless we have ECRecover (then, output
	// area will be 32 bytes just before input area).
	solUnimplementedAssert(funKind != FunctionType::Kind::ECRecover, "");
	solAssert(!isDelegateCall || !funType.valueSet(), "Value set for delegatecall");
	solAssert(!useStaticCall || !funType.valueSet(), "Value set for staticcall");
	templ("hasValue", !isDelegateCall && !useStaticCall);
	templ("value", funType.valueSet() ? IRVariable(_functionCall.expression()).part("value").name() : "0");
	// Check that the target contract exists (has code) for non-low-level calls.
	bool checkExistence = (funKind == FunctionType::Kind::External || funKind == FunctionType::Kind::DelegateCall);
	templ("checkExistence", checkExistence);
	if (funType.gasSet())
		templ("gas", IRVariable(_functionCall.expression()).part("gas").name());
	else if (m_context.evmVersion().canOverchargeGasForCall())
		// Send all gas (requires tangerine whistle EVM)
		templ("gas", "gas()");
	else
	{
		// send all gas except the amount needed to execute "SUB" and "CALL"
		// @todo this retains too much gas for now, needs to be fine-tuned.
		u256 gasNeededByCaller = evmasm::GasCosts::callGas(m_context.evmVersion()) + 10;
		if (funType.valueSet())
			gasNeededByCaller += evmasm::GasCosts::callValueTransferGas;
		if (!checkExistence)
			gasNeededByCaller += evmasm::GasCosts::callNewAccountGas; // we never know
		templ("gas", "sub(gas(), " + formatNumber(gasNeededByCaller) + ")");
	}
	// Order is important here, STATICCALL might overlap with DELEGATECALL.
	if (isDelegateCall)
		templ("call", "delegatecall");
	else if (useStaticCall)
		templ("call", "staticcall");
	else
		templ("call", "call");
	templ("forwardingRevert", m_utils.forwardingRevertFunction());
	solUnimplementedAssert(!returnSuccessConditionAndReturndata, "");
	solUnimplementedAssert(funKind != FunctionType::Kind::RIPEMD160, "");
	solUnimplementedAssert(funKind != FunctionType::Kind::ECRecover, "");
	templ("dynamicReturnSize", dynamicReturnSize);
	// Always use the actual return length, and not our calculated expected length, if returndatacopy is supported.
	// This ensures it can catch badly formatted input from external calls.
	if (haveReturndatacopy)
		templ("returnSize", "returndatasize()");
	else
		templ("returnSize", to_string(estimatedReturnSize));
	templ("reservedReturnSize", dynamicReturnSize ? "0" : to_string(estimatedReturnSize));
	templ("abiDecode", abi.tupleDecoder(returnTypes, true));
	templ("returns", !returnTypes.empty());
	templ("retVars", IRVariable(_functionCall).commaSeparatedList());
	m_code << templ.render();
}
string IRGeneratorForStatements::freeMemory()
{
	return "mload(" + to_string(CompilerUtils::freeMemoryPointer) + ")";
}
IRVariable IRGeneratorForStatements::convert(IRVariable const& _from, Type const& _to)
{
	if (_from.type() == _to)
		return _from;
	else
	{
		IRVariable converted(m_context.newYulVariable(), _to);
		define(converted, _from);
		return converted;
	}
}
std::string IRGeneratorForStatements::expressionAsType(Expression const& _expression, Type const& _to)
{
	IRVariable from(_expression);
	if (from.type() == _to)
		return from.commaSeparatedList();
	else
		return m_utils.conversionFunction(from.type(), _to) + "(" + from.commaSeparatedList() + ")";
}
std::ostream& IRGeneratorForStatements::define(IRVariable const& _var)
{
	if (_var.type().sizeOnStack() > 0)
		m_code << "let " << _var.commaSeparatedList() << " := ";
	return m_code;
}
void IRGeneratorForStatements::declare(IRVariable const& _var)
{
	if (_var.type().sizeOnStack() > 0)
		m_code << "let " << _var.commaSeparatedList() << "\n";
}
void IRGeneratorForStatements::declareAssign(IRVariable const& _lhs, IRVariable const& _rhs, bool _declare)
{
	string output;
	if (_lhs.type() == _rhs.type())
		for (auto const& [stackItemName, stackItemType]: _lhs.type().stackItems())
			if (stackItemType)
				declareAssign(_lhs.part(stackItemName), _rhs.part(stackItemName), _declare);
			else
				m_code << (_declare ? "let ": "") << _lhs.part(stackItemName).name() << " := " << _rhs.part(stackItemName).name() << "\n";
	else
		m_code <<
			(_declare ? "let ": "") <<
			_lhs.commaSeparatedList() <<
			" := " <<
			m_context.utils().conversionFunction(_rhs.type(), _lhs.type()) <<
			"(" <<
			_rhs.commaSeparatedList() <<
			")\n";
}
IRVariable IRGeneratorForStatements::zeroValue(Type const& _type, bool _splitFunctionTypes)
{
	IRVariable irVar{
		"zero_value_for_type_" + _type.identifier() + m_context.newYulVariable(),
		_type
	};
	define(irVar) << m_utils.zeroValueFunction(_type, _splitFunctionTypes) << "()\n";
	return irVar;
}
void IRGeneratorForStatements::appendSimpleUnaryOperation(UnaryOperation const& _operation, Expression const& _expr)
{
	string func;
	if (_operation.getOperator() == Token::Not)
		func = "iszero";
	else if (_operation.getOperator() == Token::BitNot)
		func = "not";
	else
		solAssert(false, "Invalid Token!");
	define(_operation) <<
		m_utils.cleanupFunction(type(_expr)) <<
		"(" <<
			func <<
			"(" <<
			IRVariable(_expr).commaSeparatedList() <<
			")" <<
		")\n";
}
string IRGeneratorForStatements::binaryOperation(
	langutil::Token _operator,
	Type const& _type,
	string const& _left,
	string const& _right
)
{
	if (IntegerType const* type = dynamic_cast(&_type))
	{
		string fun;
		// TODO: Implement all operations for signed and unsigned types.
		switch (_operator)
		{
			case Token::Add:
				fun = m_utils.overflowCheckedIntAddFunction(*type);
				break;
			case Token::Sub:
				fun = m_utils.overflowCheckedIntSubFunction(*type);
				break;
			case Token::Mul:
				fun = m_utils.overflowCheckedIntMulFunction(*type);
				break;
			case Token::Div:
				fun = m_utils.overflowCheckedIntDivFunction(*type);
				break;
			case Token::Mod:
				fun = m_utils.checkedIntModFunction(*type);
				break;
			default:
				break;
		}
		solUnimplementedAssert(!fun.empty(), "");
		return fun + "(" + _left + ", " + _right + ")\n";
	}
	else
		solUnimplementedAssert(false, "");
	return {};
}
void IRGeneratorForStatements::appendAndOrOperatorCode(BinaryOperation const& _binOp)
{
	langutil::Token const op = _binOp.getOperator();
	solAssert(op == Token::Or || op == Token::And, "");
	_binOp.leftExpression().accept(*this);
	IRVariable value(_binOp);
	define(value, _binOp.leftExpression());
	if (op == Token::Or)
		m_code << "if iszero(" << value.name() << ") {\n";
	else
		m_code << "if " << value.name() << " {\n";
	_binOp.rightExpression().accept(*this);
	assign(value, _binOp.rightExpression());
	m_code << "}\n";
}
void IRGeneratorForStatements::writeToLValue(IRLValue const& _lvalue, IRVariable const& _value)
{
	std::visit(
		util::GenericVisitor{
			[&](IRLValue::Storage const& _storage) {
				std::optional offset;
				if (std::holds_alternative(_storage.offset))
					offset = std::get(_storage.offset);
				m_code <<
					m_utils.updateStorageValueFunction(_lvalue.type, offset) <<
					"(" <<
					_storage.slot <<
					(
						std::holds_alternative(_storage.offset) ?
						(", " + std::get(_storage.offset)) :
						""
					) <<
					_value.commaSeparatedListPrefixed() <<
					")\n";
			},
			[&](IRLValue::Memory const& _memory) {
				if (_lvalue.type.isValueType())
				{
					IRVariable prepared(m_context.newYulVariable(), _lvalue.type);
					define(prepared, _value);
					if (_memory.byteArrayElement)
					{
						solAssert(_lvalue.type == *TypeProvider::byte(), "");
						m_code << "mstore8(" + _memory.address + ", byte(0, " + prepared.commaSeparatedList() + "))\n";
					}
					else
						m_code << m_utils.writeToMemoryFunction(_lvalue.type) <<
							"(" <<
							_memory.address <<
							", " <<
							prepared.commaSeparatedList() <<
							")\n";
				}
				else
				{
					solAssert(_lvalue.type.sizeOnStack() == 1, "");
					solAssert(dynamic_cast(&_lvalue.type), "");
					auto const* valueReferenceType = dynamic_cast(&_value.type());
					solAssert(valueReferenceType && valueReferenceType->dataStoredIn(DataLocation::Memory), "");
					m_code << "mstore(" + _memory.address + ", " + _value.part("mpos").name() + ")\n";
				}
			},
			[&](IRLValue::Stack const& _stack) { assign(_stack.variable, _value); },
			[&](IRLValue::Tuple const& _tuple) {
				auto components = std::move(_tuple.components);
				for (size_t i = 0; i < components.size(); i++)
				{
					size_t idx = components.size() - i - 1;
					if (components[idx])
						writeToLValue(*components[idx], _value.tupleComponent(idx));
				}
			}
		},
		_lvalue.kind
	);
}
IRVariable IRGeneratorForStatements::readFromLValue(IRLValue const& _lvalue)
{
	IRVariable result{m_context.newYulVariable(), _lvalue.type};
	std::visit(GenericVisitor{
		[&](IRLValue::Storage const& _storage) {
			if (!_lvalue.type.isValueType())
				define(result) << _storage.slot << "\n";
			else if (std::holds_alternative(_storage.offset))
				define(result) <<
					m_utils.readFromStorageDynamic(_lvalue.type, false) <<
					"(" <<
					_storage.slot <<
					", " <<
					std::get(_storage.offset) <<
					")\n";
			else
				define(result) <<
					m_utils.readFromStorage(_lvalue.type, std::get(_storage.offset), false) <<
					"(" <<
					_storage.slot <<
					")\n";
		},
		[&](IRLValue::Memory const& _memory) {
			if (_memory.byteArrayElement)
				define(result) <<
					m_utils.cleanupFunction(_lvalue.type) <<
					"(mload(" <<
					_memory.address <<
					"))\n";
			else if (_lvalue.type.isValueType())
				define(result) <<
					m_utils.readFromMemory(_lvalue.type) <<
					"(" <<
					_memory.address <<
					")\n";
			else
				define(result) << "mload(" << _memory.address << ")\n";
		},
		[&](IRLValue::Stack const& _stack) {
			define(result, _stack.variable);
		},
		[&](IRLValue::Tuple const&) {
			solAssert(false, "Attempted to read from tuple lvalue.");
		}
	}, _lvalue.kind);
	return result;
}
void IRGeneratorForStatements::setLValue(Expression const& _expression, IRLValue _lvalue)
{
	solAssert(!m_currentLValue, "");
	if (_expression.annotation().lValueRequested)
	{
		m_currentLValue.emplace(std::move(_lvalue));
		solAssert(!_lvalue.type.dataStoredIn(DataLocation::CallData), "");
	}
	else
		// Only define the expression, if it will not be written to.
		define(_expression, readFromLValue(_lvalue));
}
void IRGeneratorForStatements::generateLoop(
	Statement const& _body,
	Expression const* _conditionExpression,
	Statement const*  _initExpression,
	ExpressionStatement const* _loopExpression,
	bool _isDoWhile
)
{
	string firstRun;
	if (_isDoWhile)
	{
		solAssert(_conditionExpression, "Expected condition for doWhile");
		firstRun = m_context.newYulVariable();
		m_code << "let " << firstRun << " := 1\n";
	}
	m_code << "for {\n";
	if (_initExpression)
		_initExpression->accept(*this);
	m_code << "} 1 {\n";
	if (_loopExpression)
		_loopExpression->accept(*this);
	m_code << "}\n";
	m_code << "{\n";
	if (_conditionExpression)
	{
		if (_isDoWhile)
			m_code << "if iszero(" << firstRun << ") {\n";
		_conditionExpression->accept(*this);
		m_code <<
			"if iszero(" <<
			expressionAsType(*_conditionExpression, *TypeProvider::boolean()) <<
			") { break }\n";
		if (_isDoWhile)
			m_code << "}\n" << firstRun << " := 0\n";
	}
	_body.accept(*this);
	m_code << "}\n";
}
Type const& IRGeneratorForStatements::type(Expression const& _expression)
{
	solAssert(_expression.annotation().type, "Type of expression not set.");
	return *_expression.annotation().type;
}