Ability to specify the storage location of a reference type.

This commit is contained in:
chriseth 2015-06-05 11:07:50 +02:00
parent 4987eec3d1
commit f4d1acc563
11 changed files with 229 additions and 96 deletions

19
AST.cpp
View File

@ -528,6 +528,17 @@ void VariableDeclaration::checkTypeRequirements()
BOOST_THROW_EXCEPTION(createTypeError("Internal type is not allowed for public state variables.")); BOOST_THROW_EXCEPTION(createTypeError("Internal type is not allowed for public state variables."));
} }
bool VariableDeclaration::isFunctionParameter() const
{
auto const* function = dynamic_cast<FunctionDefinition const*>(getScope());
if (!function)
return false;
for (auto const& variable: function->getParameters() + function->getReturnParameters())
if (variable.get() == this)
return true;
return false;
}
bool VariableDeclaration::isExternalFunctionParameter() const bool VariableDeclaration::isExternalFunctionParameter() const
{ {
auto const* function = dynamic_cast<FunctionDefinition const*>(getScope()); auto const* function = dynamic_cast<FunctionDefinition const*>(getScope());
@ -879,7 +890,7 @@ void MemberAccess::checkTypeRequirements(TypePointers const* _argumentTypes)
{ {
auto const& arrayType(dynamic_cast<ArrayType const&>(type)); auto const& arrayType(dynamic_cast<ArrayType const&>(type));
m_isLValue = (*m_memberName == "length" && m_isLValue = (*m_memberName == "length" &&
arrayType.getLocation() != ArrayType::Location::CallData && arrayType.isDynamicallySized()); arrayType.location() != ReferenceType::Location::CallData && arrayType.isDynamicallySized());
} }
else else
m_isLValue = false; m_isLValue = false;
@ -902,7 +913,7 @@ void IndexAccess::checkTypeRequirements(TypePointers const*)
m_type = make_shared<FixedBytesType>(1); m_type = make_shared<FixedBytesType>(1);
else else
m_type = type.getBaseType(); m_type = type.getBaseType();
m_isLValue = type.getLocation() != ArrayType::Location::CallData; m_isLValue = type.location() != ReferenceType::Location::CallData;
break; break;
} }
case Type::Category::Mapping: case Type::Category::Mapping:
@ -919,7 +930,7 @@ void IndexAccess::checkTypeRequirements(TypePointers const*)
{ {
TypeType const& type = dynamic_cast<TypeType const&>(*m_base->getType()); TypeType const& type = dynamic_cast<TypeType const&>(*m_base->getType());
if (!m_index) if (!m_index)
m_type = make_shared<TypeType>(make_shared<ArrayType>(ArrayType::Location::Memory, type.getActualType())); m_type = make_shared<TypeType>(make_shared<ArrayType>(ReferenceType::Location::Memory, type.getActualType()));
else else
{ {
m_index->checkTypeRequirements(nullptr); m_index->checkTypeRequirements(nullptr);
@ -927,7 +938,7 @@ void IndexAccess::checkTypeRequirements(TypePointers const*)
if (!length) if (!length)
BOOST_THROW_EXCEPTION(m_index->createTypeError("Integer constant expected.")); BOOST_THROW_EXCEPTION(m_index->createTypeError("Integer constant expected."));
m_type = make_shared<TypeType>(make_shared<ArrayType>( m_type = make_shared<TypeType>(make_shared<ArrayType>(
ArrayType::Location::Memory, type.getActualType(), length->literalValue(nullptr))); ReferenceType::Location::Memory, type.getActualType(), length->literalValue(nullptr)));
} }
break; break;
} }

17
AST.h
View File

@ -474,22 +474,26 @@ private:
class VariableDeclaration: public Declaration class VariableDeclaration: public Declaration
{ {
public: public:
enum Location { Default, Storage, Memory };
VariableDeclaration( VariableDeclaration(
SourceLocation const& _location, SourceLocation const& _sourceLocation,
ASTPointer<TypeName> const& _type, ASTPointer<TypeName> const& _type,
ASTPointer<ASTString> const& _name, ASTPointer<ASTString> const& _name,
ASTPointer<Expression> _value, ASTPointer<Expression> _value,
Visibility _visibility, Visibility _visibility,
bool _isStateVar = false, bool _isStateVar = false,
bool _isIndexed = false, bool _isIndexed = false,
bool _isConstant = false bool _isConstant = false,
Location _referenceLocation = Location::Default
): ):
Declaration(_location, _name, _visibility), Declaration(_sourceLocation, _name, _visibility),
m_typeName(_type), m_typeName(_type),
m_value(_value), m_value(_value),
m_isStateVariable(_isStateVar), m_isStateVariable(_isStateVar),
m_isIndexed(_isIndexed), m_isIndexed(_isIndexed),
m_isConstant(_isConstant){} m_isConstant(_isConstant),
m_location(_referenceLocation) {}
virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTVisitor& _visitor) override;
virtual void accept(ASTConstVisitor& _visitor) const override; virtual void accept(ASTConstVisitor& _visitor) const override;
@ -507,10 +511,14 @@ public:
void checkTypeRequirements(); void checkTypeRequirements();
bool isLocalVariable() const { return !!dynamic_cast<FunctionDefinition const*>(getScope()); } bool isLocalVariable() const { return !!dynamic_cast<FunctionDefinition const*>(getScope()); }
/// @returns true if this variable is a parameter or return parameter of a function.
bool isFunctionParameter() const;
/// @returns true if this variable is a parameter (not return parameter) of an external function.
bool isExternalFunctionParameter() const; bool isExternalFunctionParameter() const;
bool isStateVariable() const { return m_isStateVariable; } bool isStateVariable() const { return m_isStateVariable; }
bool isIndexed() const { return m_isIndexed; } bool isIndexed() const { return m_isIndexed; }
bool isConstant() const { return m_isConstant; } bool isConstant() const { return m_isConstant; }
Location referenceLocation() const { return m_location; }
protected: protected:
Visibility getDefaultVisibility() const override { return Visibility::Internal; } Visibility getDefaultVisibility() const override { return Visibility::Internal; }
@ -521,6 +529,7 @@ private:
bool m_isStateVariable; ///< Whether or not this is a contract state variable bool m_isStateVariable; ///< Whether or not this is a contract state variable
bool m_isIndexed; ///< Whether this is an indexed variable (used by events). bool m_isIndexed; ///< Whether this is an indexed variable (used by events).
bool m_isConstant; ///< Whether the variable is a compile-time constant. bool m_isConstant; ///< Whether the variable is a compile-time constant.
Location m_location; ///< Location of the variable if it is of reference type.
std::shared_ptr<Type const> m_type; ///< derived type, initially empty std::shared_ptr<Type const> m_type; ///< derived type, initially empty
}; };

View File

@ -38,10 +38,10 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
// need to leave "target_ref target_byte_off" on the stack at the end // need to leave "target_ref target_byte_off" on the stack at the end
// stack layout: [source_ref] [source_byte_off] [source length] target_ref target_byte_off (top) // stack layout: [source_ref] [source_byte_off] [source length] target_ref target_byte_off (top)
solAssert(_targetType.getLocation() == ArrayType::Location::Storage, ""); solAssert(_targetType.location() == ReferenceType::Location::Storage, "");
solAssert( solAssert(
_sourceType.getLocation() == ArrayType::Location::CallData || _sourceType.location() == ReferenceType::Location::CallData ||
_sourceType.getLocation() == ArrayType::Location::Storage, _sourceType.location() == ReferenceType::Location::Storage,
"Given array location not implemented." "Given array location not implemented."
); );
@ -51,7 +51,7 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
// TODO unroll loop for small sizes // TODO unroll loop for small sizes
bool sourceIsStorage = _sourceType.getLocation() == ArrayType::Location::Storage; bool sourceIsStorage = _sourceType.location() == ReferenceType::Location::Storage;
bool directCopy = sourceIsStorage && sourceBaseType->isValueType() && *sourceBaseType == *targetBaseType; bool directCopy = sourceIsStorage && sourceBaseType->isValueType() && *sourceBaseType == *targetBaseType;
bool haveByteOffsetSource = !directCopy && sourceIsStorage && sourceBaseType->getStorageBytes() <= 16; bool haveByteOffsetSource = !directCopy && sourceIsStorage && sourceBaseType->getStorageBytes() <= 16;
bool haveByteOffsetTarget = !directCopy && targetBaseType->getStorageBytes() <= 16; bool haveByteOffsetTarget = !directCopy && targetBaseType->getStorageBytes() <= 16;
@ -69,7 +69,7 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
m_context << eth::Instruction::POP; m_context << eth::Instruction::POP;
// stack: target_ref source_ref [source_length] // stack: target_ref source_ref [source_length]
// retrieve source length // retrieve source length
if (_sourceType.getLocation() != ArrayType::Location::CallData || !_sourceType.isDynamicallySized()) if (_sourceType.location() != ReferenceType::Location::CallData || !_sourceType.isDynamicallySized())
retrieveLength(_sourceType); // otherwise, length is already there retrieveLength(_sourceType); // otherwise, length is already there
// stack: target_ref source_ref source_length // stack: target_ref source_ref source_length
m_context << eth::Instruction::DUP3; m_context << eth::Instruction::DUP3;
@ -82,7 +82,7 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
if (sourceBaseType->getCategory() == Type::Category::Mapping) if (sourceBaseType->getCategory() == Type::Category::Mapping)
{ {
solAssert(targetBaseType->getCategory() == Type::Category::Mapping, ""); solAssert(targetBaseType->getCategory() == Type::Category::Mapping, "");
solAssert(_sourceType.getLocation() == ArrayType::Location::Storage, ""); solAssert(_sourceType.location() == ReferenceType::Location::Storage, "");
// nothing to copy // nothing to copy
m_context m_context
<< eth::Instruction::POP << eth::Instruction::POP << eth::Instruction::POP << eth::Instruction::POP
@ -106,7 +106,7 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
eth::AssemblyItem copyLoopEndWithoutByteOffset = m_context.newTag(); eth::AssemblyItem copyLoopEndWithoutByteOffset = m_context.newTag();
m_context.appendConditionalJumpTo(copyLoopEndWithoutByteOffset); m_context.appendConditionalJumpTo(copyLoopEndWithoutByteOffset);
if (_sourceType.getLocation() == ArrayType::Location::Storage && _sourceType.isDynamicallySized()) if (_sourceType.location() == ReferenceType::Location::Storage && _sourceType.isDynamicallySized())
CompilerUtils(m_context).computeHashStatic(); CompilerUtils(m_context).computeHashStatic();
// stack: target_ref target_data_end source_length target_data_pos source_data_pos // stack: target_ref target_data_end source_length target_data_pos source_data_pos
m_context << eth::Instruction::SWAP2; m_context << eth::Instruction::SWAP2;
@ -155,7 +155,7 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
// checking is easier. // checking is easier.
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
m_context << eth::dupInstruction(3 + byteOffsetSize); m_context << eth::dupInstruction(3 + byteOffsetSize);
if (_sourceType.getLocation() == ArrayType::Location::Storage) if (_sourceType.location() == ReferenceType::Location::Storage)
{ {
if (haveByteOffsetSource) if (haveByteOffsetSource)
m_context << eth::Instruction::DUP2; m_context << eth::Instruction::DUP2;
@ -228,7 +228,7 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
void ArrayUtils::clearArray(ArrayType const& _type) const void ArrayUtils::clearArray(ArrayType const& _type) const
{ {
unsigned stackHeightStart = m_context.getStackHeight(); unsigned stackHeightStart = m_context.getStackHeight();
solAssert(_type.getLocation() == ArrayType::Location::Storage, ""); solAssert(_type.location() == ReferenceType::Location::Storage, "");
if (_type.getBaseType()->getStorageBytes() < 32) if (_type.getBaseType()->getStorageBytes() < 32)
{ {
solAssert(_type.getBaseType()->isValueType(), "Invalid storage size for non-value type."); solAssert(_type.getBaseType()->isValueType(), "Invalid storage size for non-value type.");
@ -283,7 +283,7 @@ void ArrayUtils::clearArray(ArrayType const& _type) const
void ArrayUtils::clearDynamicArray(ArrayType const& _type) const void ArrayUtils::clearDynamicArray(ArrayType const& _type) const
{ {
solAssert(_type.getLocation() == ArrayType::Location::Storage, ""); solAssert(_type.location() == ReferenceType::Location::Storage, "");
solAssert(_type.isDynamicallySized(), ""); solAssert(_type.isDynamicallySized(), "");
unsigned stackHeightStart = m_context.getStackHeight(); unsigned stackHeightStart = m_context.getStackHeight();
@ -311,7 +311,7 @@ void ArrayUtils::clearDynamicArray(ArrayType const& _type) const
void ArrayUtils::resizeDynamicArray(const ArrayType& _type) const void ArrayUtils::resizeDynamicArray(const ArrayType& _type) const
{ {
solAssert(_type.getLocation() == ArrayType::Location::Storage, ""); solAssert(_type.location() == ReferenceType::Location::Storage, "");
solAssert(_type.isDynamicallySized(), ""); solAssert(_type.isDynamicallySized(), "");
if (!_type.isByteArray() && _type.getBaseType()->getStorageBytes() < 32) if (!_type.isByteArray() && _type.getBaseType()->getStorageBytes() < 32)
solAssert(_type.getBaseType()->isValueType(), "Invalid storage size for non-value type."); solAssert(_type.getBaseType()->isValueType(), "Invalid storage size for non-value type.");
@ -396,7 +396,7 @@ void ArrayUtils::clearStorageLoop(Type const& _type) const
void ArrayUtils::convertLengthToSize(ArrayType const& _arrayType, bool _pad) const void ArrayUtils::convertLengthToSize(ArrayType const& _arrayType, bool _pad) const
{ {
if (_arrayType.getLocation() == ArrayType::Location::Storage) if (_arrayType.location() == ReferenceType::Location::Storage)
{ {
if (_arrayType.getBaseType()->getStorageSize() <= 1) if (_arrayType.getBaseType()->getStorageSize() <= 1)
{ {
@ -432,15 +432,15 @@ void ArrayUtils::retrieveLength(ArrayType const& _arrayType) const
else else
{ {
m_context << eth::Instruction::DUP1; m_context << eth::Instruction::DUP1;
switch (_arrayType.getLocation()) switch (_arrayType.location())
{ {
case ArrayType::Location::CallData: case ReferenceType::Location::CallData:
// length is stored on the stack // length is stored on the stack
break; break;
case ArrayType::Location::Memory: case ReferenceType::Location::Memory:
m_context << eth::Instruction::MLOAD; m_context << eth::Instruction::MLOAD;
break; break;
case ArrayType::Location::Storage: case ReferenceType::Location::Storage:
m_context << eth::Instruction::SLOAD; m_context << eth::Instruction::SLOAD;
break; break;
} }
@ -449,16 +449,16 @@ void ArrayUtils::retrieveLength(ArrayType const& _arrayType) const
void ArrayUtils::accessIndex(ArrayType const& _arrayType) const void ArrayUtils::accessIndex(ArrayType const& _arrayType) const
{ {
ArrayType::Location location = _arrayType.getLocation(); ReferenceType::Location location = _arrayType.location();
eth::Instruction load = eth::Instruction load =
location == ArrayType::Location::Storage ? eth::Instruction::SLOAD : location == ReferenceType::Location::Storage ? eth::Instruction::SLOAD :
location == ArrayType::Location::Memory ? eth::Instruction::MLOAD : location == ReferenceType::Location::Memory ? eth::Instruction::MLOAD :
eth::Instruction::CALLDATALOAD; eth::Instruction::CALLDATALOAD;
// retrieve length // retrieve length
if (!_arrayType.isDynamicallySized()) if (!_arrayType.isDynamicallySized())
m_context << _arrayType.getLength(); m_context << _arrayType.getLength();
else if (location == ArrayType::Location::CallData) else if (location == ReferenceType::Location::CallData)
// length is stored on the stack // length is stored on the stack
m_context << eth::Instruction::SWAP1; m_context << eth::Instruction::SWAP1;
else else
@ -473,15 +473,15 @@ void ArrayUtils::accessIndex(ArrayType const& _arrayType) const
m_context << eth::Instruction::SWAP1; m_context << eth::Instruction::SWAP1;
if (_arrayType.isDynamicallySized()) if (_arrayType.isDynamicallySized())
{ {
if (location == ArrayType::Location::Storage) if (location == ReferenceType::Location::Storage)
CompilerUtils(m_context).computeHashStatic(); CompilerUtils(m_context).computeHashStatic();
else if (location == ArrayType::Location::Memory) else if (location == ReferenceType::Location::Memory)
m_context << u256(32) << eth::Instruction::ADD; m_context << u256(32) << eth::Instruction::ADD;
} }
// stack: <index> <data_ref> // stack: <index> <data_ref>
switch (location) switch (location)
{ {
case ArrayType::Location::CallData: case ReferenceType::Location::CallData:
if (!_arrayType.isByteArray()) if (!_arrayType.isByteArray())
m_context m_context
<< eth::Instruction::SWAP1 << eth::Instruction::SWAP1
@ -496,7 +496,7 @@ void ArrayUtils::accessIndex(ArrayType const& _arrayType) const
false false
); );
break; break;
case ArrayType::Location::Storage: case ReferenceType::Location::Storage:
m_context << eth::Instruction::SWAP1; m_context << eth::Instruction::SWAP1;
if (_arrayType.getBaseType()->getStorageBytes() <= 16) if (_arrayType.getBaseType()->getStorageBytes() <= 16)
{ {
@ -524,7 +524,7 @@ void ArrayUtils::accessIndex(ArrayType const& _arrayType) const
m_context << eth::Instruction::ADD << u256(0); m_context << eth::Instruction::ADD << u256(0);
} }
break; break;
case ArrayType::Location::Memory: case ReferenceType::Location::Memory:
solAssert(false, "Memory lvalues not yet implemented."); solAssert(false, "Memory lvalues not yet implemented.");
} }
} }

View File

@ -81,7 +81,7 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound
auto const& type = dynamic_cast<ArrayType const&>(_type); auto const& type = dynamic_cast<ArrayType const&>(_type);
solAssert(type.isByteArray(), "Non byte arrays not yet implemented here."); solAssert(type.isByteArray(), "Non byte arrays not yet implemented here.");
if (type.getLocation() == ArrayType::Location::CallData) if (type.location() == ReferenceType::Location::CallData)
{ {
// stack: target source_offset source_len // stack: target source_offset source_len
m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3 << eth::Instruction::DUP5 m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3 << eth::Instruction::DUP5
@ -92,7 +92,7 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound
} }
else else
{ {
solAssert(type.getLocation() == ArrayType::Location::Storage, "Memory arrays not yet implemented."); solAssert(type.location() == ReferenceType::Location::Storage, "Memory arrays not yet implemented.");
m_context << eth::Instruction::POP; // remove offset, arrays always start new slot m_context << eth::Instruction::POP; // remove offset, arrays always start new slot
m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD; m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD;
// stack here: memory_offset storage_offset length_bytes // stack here: memory_offset storage_offset length_bytes

View File

@ -770,12 +770,12 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess)
m_context << type.getLength(); m_context << type.getLength();
} }
else else
switch (type.getLocation()) switch (type.location())
{ {
case ArrayType::Location::CallData: case ReferenceType::Location::CallData:
m_context << eth::Instruction::SWAP1 << eth::Instruction::POP; m_context << eth::Instruction::SWAP1 << eth::Instruction::POP;
break; break;
case ArrayType::Location::Storage: case ReferenceType::Location::Storage:
setLValue<StorageArrayLength>(_memberAccess, type); setLValue<StorageArrayLength>(_memberAccess, type);
break; break;
default: default:
@ -816,13 +816,13 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
solAssert(_indexAccess.getIndexExpression(), "Index expression expected."); solAssert(_indexAccess.getIndexExpression(), "Index expression expected.");
// remove storage byte offset // remove storage byte offset
if (arrayType.getLocation() == ArrayType::Location::Storage) if (arrayType.location() == ReferenceType::Location::Storage)
m_context << eth::Instruction::POP; m_context << eth::Instruction::POP;
_indexAccess.getIndexExpression()->accept(*this); _indexAccess.getIndexExpression()->accept(*this);
// stack layout: <base_ref> [<length>] <index> // stack layout: <base_ref> [<length>] <index>
ArrayUtils(m_context).accessIndex(arrayType); ArrayUtils(m_context).accessIndex(arrayType);
if (arrayType.getLocation() == ArrayType::Location::Storage) if (arrayType.location() == ReferenceType::Location::Storage)
{ {
if (arrayType.isByteArray()) if (arrayType.isByteArray())
{ {
@ -1169,13 +1169,13 @@ void ExpressionCompiler::appendArgumentsCopyToMemory(
auto const& arrayType = dynamic_cast<ArrayType const&>(*_arguments[i]->getType()); auto const& arrayType = dynamic_cast<ArrayType const&>(*_arguments[i]->getType());
// move memory reference to top of stack // move memory reference to top of stack
CompilerUtils(m_context).moveToStackTop(arrayType.getSizeOnStack()); CompilerUtils(m_context).moveToStackTop(arrayType.getSizeOnStack());
if (arrayType.getLocation() == ArrayType::Location::CallData) if (arrayType.location() == ReferenceType::Location::CallData)
m_context << eth::Instruction::DUP2; // length is on stack m_context << eth::Instruction::DUP2; // length is on stack
else if (arrayType.getLocation() == ArrayType::Location::Storage) else if (arrayType.location() == ReferenceType::Location::Storage)
m_context << eth::Instruction::DUP3 << eth::Instruction::SLOAD; m_context << eth::Instruction::DUP3 << eth::Instruction::SLOAD;
else else
{ {
solAssert(arrayType.getLocation() == ArrayType::Location::Memory, ""); solAssert(arrayType.location() == ReferenceType::Location::Memory, "");
m_context << eth::Instruction::DUP2 << eth::Instruction::MLOAD; m_context << eth::Instruction::DUP2 << eth::Instruction::MLOAD;
} }
appendTypeMoveToMemory(IntegerType(256), true); appendTypeMoveToMemory(IntegerType(256), true);

View File

@ -424,10 +424,49 @@ void ReferencesResolver::endVisit(VariableDeclaration& _variable)
if (_variable.getTypeName()) if (_variable.getTypeName())
{ {
TypePointer type = _variable.getTypeName()->toType(); TypePointer type = _variable.getTypeName()->toType();
// All array parameter types should point to call data using Location = VariableDeclaration::Location;
if (_variable.isExternalFunctionParameter()) Location loc = _variable.referenceLocation();
if (auto const* arrayType = dynamic_cast<ArrayType const*>(type.get())) // References are forced to calldata for external function parameters (not return)
type = arrayType->copyForLocation(ArrayType::Location::CallData); // and memory for parameters (also return) of publicly visible functions.
// They default to memory for function parameters and storage for local variables.
if (auto ref = dynamic_cast<ReferenceType const*>(type.get()))
{
if (_variable.isExternalFunctionParameter())
{
// force location of external function parameters (not return) to calldata
if (loc != Location::Default)
BOOST_THROW_EXCEPTION(_variable.createTypeError(
"Location has to be calldata for external functions "
"(remove the \"memory\" or \"storage\" keyword)."
));
type = ref->copyForLocation(ReferenceType::Location::CallData);
}
else if (_variable.isFunctionParameter() && _variable.getScope()->isPublic())
{
// force locations of public or external function (return) parameters to memory
if (loc == VariableDeclaration::Location::Storage)
BOOST_THROW_EXCEPTION(_variable.createTypeError(
"Location has to be memory for publicly visible functions "
"(remove the \"storage\" keyword)."
));
type = ref->copyForLocation(ReferenceType::Location::Memory);
}
else
{
if (loc == Location::Default)
loc = _variable.isFunctionParameter() ? Location::Memory : Location::Storage;
type = ref->copyForLocation(
loc == Location::Memory ?
ReferenceType::Location::Memory :
ReferenceType::Location::Storage
);
}
}
else if (loc != Location::Default && !ref)
BOOST_THROW_EXCEPTION(_variable.createTypeError(
"Storage location can only be given for array or struct types."
));
_variable.setType(type); _variable.setType(type);
if (!_variable.getType()) if (!_variable.getType())

View File

@ -224,7 +224,9 @@ ASTPointer<FunctionDefinition> Parser::parseFunctionDefinition(ASTString const*
name = make_shared<ASTString>(); // anonymous function name = make_shared<ASTString>(); // anonymous function
else else
name = expectIdentifierToken(); name = expectIdentifierToken();
ASTPointer<ParameterList> parameters(parseParameterList()); VarDeclParserOptions options;
options.allowLocationSpecifier = true;
ASTPointer<ParameterList> parameters(parseParameterList(options));
bool isDeclaredConst = false; bool isDeclaredConst = false;
Declaration::Visibility visibility(Declaration::Visibility::Default); Declaration::Visibility visibility(Declaration::Visibility::Default);
vector<ASTPointer<ModifierInvocation>> modifiers; vector<ASTPointer<ModifierInvocation>> modifiers;
@ -252,7 +254,7 @@ ASTPointer<FunctionDefinition> Parser::parseFunctionDefinition(ASTString const*
{ {
bool const permitEmptyParameterList = false; bool const permitEmptyParameterList = false;
m_scanner->next(); m_scanner->next();
returnParameters = parseParameterList(permitEmptyParameterList); returnParameters = parseParameterList(options, permitEmptyParameterList);
} }
else else
returnParameters = createEmptyParameterList(); returnParameters = createEmptyParameterList();
@ -319,7 +321,9 @@ ASTPointer<EnumDefinition> Parser::parseEnumDefinition()
} }
ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration( ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
VarDeclParserOptions const& _options, ASTPointer<TypeName> const& _lookAheadArrayType) VarDeclParserOptions const& _options,
ASTPointer<TypeName> const& _lookAheadArrayType
)
{ {
ASTNodeFactory nodeFactory = _lookAheadArrayType ? ASTNodeFactory nodeFactory = _lookAheadArrayType ?
ASTNodeFactory(*this, _lookAheadArrayType) : ASTNodeFactory(*this); ASTNodeFactory(*this, _lookAheadArrayType) : ASTNodeFactory(*this);
@ -334,20 +338,41 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
} }
bool isIndexed = false; bool isIndexed = false;
bool isDeclaredConst = false; bool isDeclaredConst = false;
ASTPointer<ASTString> identifier;
Token::Value token = m_scanner->getCurrentToken();
Declaration::Visibility visibility(Declaration::Visibility::Default); Declaration::Visibility visibility(Declaration::Visibility::Default);
if (_options.isStateVariable && Token::isVariableVisibilitySpecifier(token)) VariableDeclaration::Location location = VariableDeclaration::Location::Default;
visibility = parseVisibilitySpecifier(token); ASTPointer<ASTString> identifier;
if (_options.allowIndexed && token == Token::Indexed)
while (true)
{ {
isIndexed = true; Token::Value token = m_scanner->getCurrentToken();
m_scanner->next(); if (_options.isStateVariable && Token::isVariableVisibilitySpecifier(token))
} {
if (token == Token::Const) if (visibility != Declaration::Visibility::Default)
{ BOOST_THROW_EXCEPTION(createParserError("Visibility already specified."));
isDeclaredConst = true; visibility = parseVisibilitySpecifier(token);
m_scanner->next(); }
else
{
if (_options.allowIndexed && token == Token::Indexed)
isIndexed = true;
else if (token == Token::Const)
isDeclaredConst = true;
else if (_options.allowLocationSpecifier && Token::isLocationSpecifier(token))
{
if (location != VariableDeclaration::Location::Default)
BOOST_THROW_EXCEPTION(createParserError("Location already specified."));
if (!type)
BOOST_THROW_EXCEPTION(createParserError("Location specifier needs explicit type name."));
location = (
token == Token::Memory ?
VariableDeclaration::Location::Memory :
VariableDeclaration::Location::Storage
);
}
else
break;
m_scanner->next();
}
} }
nodeFactory.markEndPosition(); nodeFactory.markEndPosition();
@ -371,7 +396,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
} }
return nodeFactory.createNode<VariableDeclaration>(type, identifier, value, return nodeFactory.createNode<VariableDeclaration>(type, identifier, value,
visibility, _options.isStateVariable, visibility, _options.isStateVariable,
isIndexed, isDeclaredConst); isIndexed, isDeclaredConst, location);
} }
ASTPointer<ModifierDefinition> Parser::parseModifierDefinition() ASTPointer<ModifierDefinition> Parser::parseModifierDefinition()
@ -388,7 +413,12 @@ ASTPointer<ModifierDefinition> Parser::parseModifierDefinition()
ASTPointer<ASTString> name(expectIdentifierToken()); ASTPointer<ASTString> name(expectIdentifierToken());
ASTPointer<ParameterList> parameters; ASTPointer<ParameterList> parameters;
if (m_scanner->getCurrentToken() == Token::LParen) if (m_scanner->getCurrentToken() == Token::LParen)
parameters = parseParameterList(); {
VarDeclParserOptions options;
options.allowIndexed = true;
options.allowLocationSpecifier = true;
parameters = parseParameterList(options);
}
else else
parameters = createEmptyParameterList(); parameters = createEmptyParameterList();
ASTPointer<Block> block = parseBlock(); ASTPointer<Block> block = parseBlock();
@ -407,7 +437,11 @@ ASTPointer<EventDefinition> Parser::parseEventDefinition()
ASTPointer<ASTString> name(expectIdentifierToken()); ASTPointer<ASTString> name(expectIdentifierToken());
ASTPointer<ParameterList> parameters; ASTPointer<ParameterList> parameters;
if (m_scanner->getCurrentToken() == Token::LParen) if (m_scanner->getCurrentToken() == Token::LParen)
parameters = parseParameterList(true, true); {
VarDeclParserOptions options;
options.allowIndexed = true;
parameters = parseParameterList(options);
}
else else
parameters = createEmptyParameterList(); parameters = createEmptyParameterList();
bool anonymous = false; bool anonymous = false;
@ -505,12 +539,14 @@ ASTPointer<Mapping> Parser::parseMapping()
return nodeFactory.createNode<Mapping>(keyType, valueType); return nodeFactory.createNode<Mapping>(keyType, valueType);
} }
ASTPointer<ParameterList> Parser::parseParameterList(bool _allowEmpty, bool _allowIndexed) ASTPointer<ParameterList> Parser::parseParameterList(
VarDeclParserOptions const& _options,
bool _allowEmpty
)
{ {
ASTNodeFactory nodeFactory(*this); ASTNodeFactory nodeFactory(*this);
vector<ASTPointer<VariableDeclaration>> parameters; vector<ASTPointer<VariableDeclaration>> parameters;
VarDeclParserOptions options; VarDeclParserOptions options(_options);
options.allowIndexed = _allowIndexed;
options.allowEmptyName = true; options.allowEmptyName = true;
expectToken(Token::LParen); expectToken(Token::LParen);
if (!_allowEmpty || m_scanner->getCurrentToken() != Token::RParen) if (!_allowEmpty || m_scanner->getCurrentToken() != Token::RParen)
@ -691,7 +727,7 @@ ASTPointer<Statement> Parser::parseSimpleStatement()
} }
while (m_scanner->getCurrentToken() == Token::LBrack); while (m_scanner->getCurrentToken() == Token::LBrack);
if (m_scanner->getCurrentToken() == Token::Identifier) if (m_scanner->getCurrentToken() == Token::Identifier || Token::isLocationSpecifier(m_scanner->getCurrentToken()))
return parseVariableDeclarationStatement(typeNameIndexAccessStructure(primary, indices)); return parseVariableDeclarationStatement(typeNameIndexAccessStructure(primary, indices));
else else
return parseExpressionStatement(expressionFromIndexAccessStructure(primary, indices)); return parseExpressionStatement(expressionFromIndexAccessStructure(primary, indices));
@ -703,6 +739,7 @@ ASTPointer<VariableDeclarationStatement> Parser::parseVariableDeclarationStateme
VarDeclParserOptions options; VarDeclParserOptions options;
options.allowVar = true; options.allowVar = true;
options.allowInitialValue = true; options.allowInitialValue = true;
options.allowLocationSpecifier = true;
ASTPointer<VariableDeclaration> variable = parseVariableDeclaration(options, _lookAheadArrayType); ASTPointer<VariableDeclaration> variable = parseVariableDeclaration(options, _lookAheadArrayType);
ASTNodeFactory nodeFactory(*this, variable); ASTNodeFactory nodeFactory(*this, variable);
return nodeFactory.createNode<VariableDeclarationStatement>(variable); return nodeFactory.createNode<VariableDeclarationStatement>(variable);
@ -944,11 +981,16 @@ Parser::LookAheadInfo Parser::peekStatementType() const
Token::Value token(m_scanner->getCurrentToken()); Token::Value token(m_scanner->getCurrentToken());
bool mightBeTypeName = (Token::isElementaryTypeName(token) || token == Token::Identifier); bool mightBeTypeName = (Token::isElementaryTypeName(token) || token == Token::Identifier);
if (token == Token::Mapping || token == Token::Var || if (token == Token::Mapping || token == Token::Var)
(mightBeTypeName && m_scanner->peekNextToken() == Token::Identifier))
return LookAheadInfo::VariableDeclarationStatement; return LookAheadInfo::VariableDeclarationStatement;
if (mightBeTypeName && m_scanner->peekNextToken() == Token::LBrack) if (mightBeTypeName)
return LookAheadInfo::IndexAccessStructure; {
Token::Value next = m_scanner->peekNextToken();
if (next == Token::Identifier || Token::isLocationSpecifier(next))
return LookAheadInfo::VariableDeclarationStatement;
if (m_scanner->peekNextToken() == Token::LBrack)
return LookAheadInfo::IndexAccessStructure;
}
return LookAheadInfo::ExpressionStatement; return LookAheadInfo::ExpressionStatement;
} }

View File

@ -47,13 +47,15 @@ private:
/// End position of the current token /// End position of the current token
int getEndPosition() const; int getEndPosition() const;
struct VarDeclParserOptions { struct VarDeclParserOptions
{
VarDeclParserOptions() {} VarDeclParserOptions() {}
bool allowVar = false; bool allowVar = false;
bool isStateVariable = false; bool isStateVariable = false;
bool allowIndexed = false; bool allowIndexed = false;
bool allowEmptyName = false; bool allowEmptyName = false;
bool allowInitialValue = false; bool allowInitialValue = false;
bool allowLocationSpecifier = false;
}; };
///@{ ///@{
@ -74,7 +76,10 @@ private:
ASTPointer<Identifier> parseIdentifier(); ASTPointer<Identifier> parseIdentifier();
ASTPointer<TypeName> parseTypeName(bool _allowVar); ASTPointer<TypeName> parseTypeName(bool _allowVar);
ASTPointer<Mapping> parseMapping(); ASTPointer<Mapping> parseMapping();
ASTPointer<ParameterList> parseParameterList(bool _allowEmpty = true, bool _allowIndexed = false); ASTPointer<ParameterList> parseParameterList(
VarDeclParserOptions const& _options,
bool _allowEmpty = true
);
ASTPointer<Block> parseBlock(); ASTPointer<Block> parseBlock();
ASTPointer<Statement> parseStatement(); ASTPointer<Statement> parseStatement();
ASTPointer<IfStatement> parseIfStatement(); ASTPointer<IfStatement> parseIfStatement();

View File

@ -161,12 +161,14 @@ namespace solidity
K(Import, "import", 0) \ K(Import, "import", 0) \
K(Is, "is", 0) \ K(Is, "is", 0) \
K(Mapping, "mapping", 0) \ K(Mapping, "mapping", 0) \
K(Memory, "memory", 0) \
K(Modifier, "modifier", 0) \ K(Modifier, "modifier", 0) \
K(New, "new", 0) \ K(New, "new", 0) \
K(Public, "public", 0) \ K(Public, "public", 0) \
K(Private, "private", 0) \ K(Private, "private", 0) \
K(Return, "return", 0) \ K(Return, "return", 0) \
K(Returns, "returns", 0) \ K(Returns, "returns", 0) \
K(Storage, "storage", 0) \
K(Struct, "struct", 0) \ K(Struct, "struct", 0) \
K(Var, "var", 0) \ K(Var, "var", 0) \
K(While, "while", 0) \ K(While, "while", 0) \
@ -370,6 +372,7 @@ public:
static bool isShiftOp(Value op) { return (SHL <= op) && (op <= SHR); } static bool isShiftOp(Value op) { return (SHL <= op) && (op <= SHR); }
static bool isVisibilitySpecifier(Value op) { return isVariableVisibilitySpecifier(op) || op == External; } static bool isVisibilitySpecifier(Value op) { return isVariableVisibilitySpecifier(op) || op == External; }
static bool isVariableVisibilitySpecifier(Value op) { return op == Public || op == Private || op == Internal; } static bool isVariableVisibilitySpecifier(Value op) { return op == Public || op == Private || op == Internal; }
static bool isLocationSpecifier(Value op) { return op == Memory || op == Storage; }
static bool isEtherSubdenomination(Value op) { return op == SubWei || op == SubSzabo || op == SubFinney || op == SubEther; } static bool isEtherSubdenomination(Value op) { return op == SubWei || op == SubSzabo || op == SubFinney || op == SubEther; }
static bool isTimeSubdenomination(Value op) { return op == SubSecond || op == SubMinute || op == SubHour || op == SubDay || op == SubWeek || op == SubYear; } static bool isTimeSubdenomination(Value op) { return op == SubSecond || op == SubMinute || op == SubHour || op == SubDay || op == SubWeek || op == SubYear; }

View File

@ -144,9 +144,9 @@ TypePointer Type::fromElementaryTypeName(Token::Value _typeToken)
else if (_typeToken == Token::Bool) else if (_typeToken == Token::Bool)
return make_shared<BoolType>(); return make_shared<BoolType>();
else if (_typeToken == Token::Bytes) else if (_typeToken == Token::Bytes)
return make_shared<ArrayType>(ArrayType::Location::Storage); return make_shared<ArrayType>(ReferenceType::Location::Storage);
else if (_typeToken == Token::String) else if (_typeToken == Token::String)
return make_shared<ArrayType>(ArrayType::Location::Storage, true); return make_shared<ArrayType>(ReferenceType::Location::Storage, true);
else else
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unable to convert elementary typename " + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unable to convert elementary typename " +
std::string(Token::toString(_typeToken)) + " to type.")); std::string(Token::toString(_typeToken)) + " to type."));
@ -196,10 +196,10 @@ TypePointer Type::fromArrayTypeName(TypeName& _baseTypeName, Expression* _length
auto const* length = dynamic_cast<IntegerConstantType const*>(_length->getType().get()); auto const* length = dynamic_cast<IntegerConstantType const*>(_length->getType().get());
if (!length) if (!length)
BOOST_THROW_EXCEPTION(_length->createTypeError("Invalid array length.")); BOOST_THROW_EXCEPTION(_length->createTypeError("Invalid array length."));
return make_shared<ArrayType>(ArrayType::Location::Storage, baseType, length->literalValue(nullptr)); return make_shared<ArrayType>(ReferenceType::Location::Storage, baseType, length->literalValue(nullptr));
} }
else else
return make_shared<ArrayType>(ArrayType::Location::Storage, baseType); return make_shared<ArrayType>(ReferenceType::Location::Storage, baseType);
} }
TypePointer Type::forLiteral(Literal const& _literal) TypePointer Type::forLiteral(Literal const& _literal)
@ -674,7 +674,7 @@ bool ArrayType::isImplicitlyConvertibleTo(const Type& _convertTo) const
return false; return false;
auto& convertTo = dynamic_cast<ArrayType const&>(_convertTo); auto& convertTo = dynamic_cast<ArrayType const&>(_convertTo);
// let us not allow assignment to memory arrays for now // let us not allow assignment to memory arrays for now
if (convertTo.getLocation() != Location::Storage) if (convertTo.location() != Location::Storage)
return false; return false;
if (convertTo.isByteArray() != isByteArray() || convertTo.isString() != isString()) if (convertTo.isByteArray() != isByteArray() || convertTo.isString() != isString())
return false; return false;
@ -778,12 +778,12 @@ TypePointer ArrayType::externalType() const
return std::make_shared<ArrayType>(Location::CallData, m_baseType->externalType(), m_length); return std::make_shared<ArrayType>(Location::CallData, m_baseType->externalType(), m_length);
} }
shared_ptr<ArrayType> ArrayType::copyForLocation(ArrayType::Location _location) const TypePointer ArrayType::copyForLocation(ReferenceType::Location _location) const
{ {
auto copy = make_shared<ArrayType>(_location); auto copy = make_shared<ArrayType>(_location);
copy->m_arrayKind = m_arrayKind; copy->m_arrayKind = m_arrayKind;
if (m_baseType->getCategory() == Type::Category::Array) if (auto ref = dynamic_cast<ReferenceType const*>(m_baseType.get()))
copy->m_baseType = dynamic_cast<ArrayType const&>(*m_baseType).copyForLocation(_location); copy->m_baseType = ref->copyForLocation(_location);
else else
copy->m_baseType = m_baseType; copy->m_baseType = m_baseType;
copy->m_hasDynamicLength = m_hasDynamicLength; copy->m_hasDynamicLength = m_hasDynamicLength;
@ -934,6 +934,13 @@ MemberList const& StructType::getMembers() const
return *m_members; return *m_members;
} }
TypePointer StructType::copyForLocation(ReferenceType::Location _location) const
{
auto copy = make_shared<StructType>(m_struct);
copy->m_location = _location;
return copy;
}
pair<u256, unsigned> const& StructType::getStorageOffsetsOfMember(string const& _name) const pair<u256, unsigned> const& StructType::getStorageOffsetsOfMember(string const& _name) const
{ {
auto const* offsets = getMembers().getMemberStorageOffset(_name); auto const* offsets = getMembers().getMemberStorageOffset(_name);
@ -1466,7 +1473,7 @@ MagicType::MagicType(MagicType::Kind _kind):
{"sender", make_shared<IntegerType>(0, IntegerType::Modifier::Address)}, {"sender", make_shared<IntegerType>(0, IntegerType::Modifier::Address)},
{"gas", make_shared<IntegerType>(256)}, {"gas", make_shared<IntegerType>(256)},
{"value", make_shared<IntegerType>(256)}, {"value", make_shared<IntegerType>(256)},
{"data", make_shared<ArrayType>(ArrayType::Location::CallData)}, {"data", make_shared<ArrayType>(ReferenceType::Location::CallData)},
{"sig", make_shared<FixedBytesType>(4)} {"sig", make_shared<FixedBytesType>(4)}
})); }));
break; break;

41
Types.h
View File

@ -353,6 +353,24 @@ public:
virtual TypePointer externalType() const override { return shared_from_this(); } virtual TypePointer externalType() const override { return shared_from_this(); }
}; };
/**
* Trait used by types which are not value types and can be stored either in storage, memory
* or calldata. This is currently used by arrays and structs.
*/
class ReferenceType
{
public:
enum class Location { Storage, CallData, Memory };
explicit ReferenceType(Location _location): m_location(_location) {}
Location location() const { return m_location; }
/// @returns a copy of this type with location (recursively) changed to @a _location.
virtual TypePointer copyForLocation(Location _location) const = 0;
protected:
Location m_location = Location::Storage;
};
/** /**
* The type of an array. The flavours are byte array (bytes), statically- (<type>[<length>]) * The type of an array. The flavours are byte array (bytes), statically- (<type>[<length>])
* and dynamically-sized array (<type>[]). * and dynamically-sized array (<type>[]).
@ -360,27 +378,26 @@ public:
* one slot). Dynamically sized arrays (including byte arrays) start with their size as a uint and * one slot). Dynamically sized arrays (including byte arrays) start with their size as a uint and
* thus start on their own slot. * thus start on their own slot.
*/ */
class ArrayType: public Type class ArrayType: public Type, public ReferenceType
{ {
public: public:
enum class Location { Storage, CallData, Memory };
virtual Category getCategory() const override { return Category::Array; } virtual Category getCategory() const override { return Category::Array; }
/// Constructor for a byte array ("bytes") and string. /// Constructor for a byte array ("bytes") and string.
explicit ArrayType(Location _location, bool _isString = false): explicit ArrayType(Location _location, bool _isString = false):
m_location(_location), ReferenceType(_location),
m_arrayKind(_isString ? ArrayKind::String : ArrayKind::Bytes), m_arrayKind(_isString ? ArrayKind::String : ArrayKind::Bytes),
m_baseType(std::make_shared<FixedBytesType>(1)) m_baseType(std::make_shared<FixedBytesType>(1))
{} {}
/// Constructor for a dynamically sized array type ("type[]") /// Constructor for a dynamically sized array type ("type[]")
ArrayType(Location _location, const TypePointer &_baseType): ArrayType(Location _location, const TypePointer &_baseType):
m_location(_location), ReferenceType(_location),
m_baseType(_baseType) m_baseType(_baseType)
{} {}
/// Constructor for a fixed-size array type ("type[20]") /// Constructor for a fixed-size array type ("type[20]")
ArrayType(Location _location, const TypePointer &_baseType, u256 const& _length): ArrayType(Location _location, const TypePointer &_baseType, u256 const& _length):
m_location(_location), ReferenceType(_location),
m_baseType(_baseType), m_baseType(_baseType),
m_hasDynamicLength(false), m_hasDynamicLength(false),
m_length(_length) m_length(_length)
@ -400,7 +417,6 @@ public:
} }
virtual TypePointer externalType() const override; virtual TypePointer externalType() const override;
Location getLocation() const { return m_location; }
/// @returns true if this is a byte array or a string /// @returns true if this is a byte array or a string
bool isByteArray() const { return m_arrayKind != ArrayKind::Ordinary; } bool isByteArray() const { return m_arrayKind != ArrayKind::Ordinary; }
/// @returns true if this is a string /// @returns true if this is a string
@ -408,15 +424,12 @@ public:
TypePointer const& getBaseType() const { solAssert(!!m_baseType, ""); return m_baseType;} TypePointer const& getBaseType() const { solAssert(!!m_baseType, ""); return m_baseType;}
u256 const& getLength() const { return m_length; } u256 const& getLength() const { return m_length; }
/// @returns a copy of this type with location changed to @a _location TypePointer copyForLocation(Location _location) const override;
/// @todo this might move as far up as Type later
std::shared_ptr<ArrayType> copyForLocation(Location _location) const;
private: private:
/// String is interpreted as a subtype of Bytes. /// String is interpreted as a subtype of Bytes.
enum class ArrayKind { Ordinary, Bytes, String }; enum class ArrayKind { Ordinary, Bytes, String };
Location m_location;
///< Byte arrays ("bytes") and strings have different semantics from ordinary arrays. ///< Byte arrays ("bytes") and strings have different semantics from ordinary arrays.
ArrayKind m_arrayKind = ArrayKind::Ordinary; ArrayKind m_arrayKind = ArrayKind::Ordinary;
TypePointer m_baseType; TypePointer m_baseType;
@ -484,11 +497,13 @@ private:
/** /**
* The type of a struct instance, there is one distinct type per struct definition. * The type of a struct instance, there is one distinct type per struct definition.
*/ */
class StructType: public Type class StructType: public Type, public ReferenceType
{ {
public: public:
virtual Category getCategory() const override { return Category::Struct; } virtual Category getCategory() const override { return Category::Struct; }
explicit StructType(StructDefinition const& _struct): m_struct(_struct) {} explicit StructType(StructDefinition const& _struct):
//@todo only storage until we have non-storage structs
ReferenceType(Location::Storage), m_struct(_struct) {}
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
virtual bool operator==(Type const& _other) const override; virtual bool operator==(Type const& _other) const override;
virtual u256 getStorageSize() const override; virtual u256 getStorageSize() const override;
@ -498,6 +513,8 @@ public:
virtual MemberList const& getMembers() const override; virtual MemberList const& getMembers() const override;
TypePointer copyForLocation(Location _location) const override;
std::pair<u256, unsigned> const& getStorageOffsetsOfMember(std::string const& _name) const; std::pair<u256, unsigned> const& getStorageOffsetsOfMember(std::string const& _name) const;
private: private: