From d41eaeba5686828b85279057f3a7da8be0f9a8f9 Mon Sep 17 00:00:00 2001 From: Harikrishnan Mulackal Date: Wed, 3 Jun 2020 16:08:35 +0530 Subject: [PATCH] Added containsNestedMapping() --- .../analysis/DeclarationTypeChecker.cpp | 12 +- libsolidity/analysis/TypeChecker.cpp | 103 +++++++++--------- libsolidity/ast/ASTAnnotations.h | 3 + libsolidity/ast/Types.cpp | 83 ++++++++++---- libsolidity/ast/Types.h | 28 ++--- libsolidity/codegen/ABIFunctions.cpp | 5 +- libsolidity/codegen/CompilerUtils.cpp | 3 +- libsolidity/codegen/LValue.cpp | 4 +- 8 files changed, 136 insertions(+), 105 deletions(-) diff --git a/libsolidity/analysis/DeclarationTypeChecker.cpp b/libsolidity/analysis/DeclarationTypeChecker.cpp index 07e8f6066..f8dbc1d2d 100644 --- a/libsolidity/analysis/DeclarationTypeChecker.cpp +++ b/libsolidity/analysis/DeclarationTypeChecker.cpp @@ -112,18 +112,16 @@ bool DeclarationTypeChecker::visit(StructDefinition const& _struct) for (ASTPointer const& member: _struct.members()) { Type const* memberType = member->annotation().type; - while (auto arrayType = dynamic_cast(memberType)) - { - if (arrayType->isDynamicallySized()) - break; - memberType = arrayType->baseType(); - } + + if (auto arrayType = dynamic_cast(memberType)) + memberType = arrayType->finalBaseType(true); + if (auto structType = dynamic_cast(memberType)) if (_cycleDetector.run(structType->structDefinition())) return; } }; - if (util::CycleDetector(visitor).run(_struct) != nullptr) + if (util::CycleDetector(visitor).run(_struct)) m_errorReporter.fatalTypeError(2046_error, _struct.location(), "Recursive struct definition."); return false; diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index b40853177..afc454cb4 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -339,31 +339,21 @@ bool TypeChecker::visit(FunctionDefinition const& _function) m_errorReporter.typeError(5587_error, _function.location(), "Internal functions cannot be payable."); } auto checkArgumentAndReturnParameter = [&](VariableDeclaration const& var) { - if (type(var)->category() == Type::Category::Mapping) + if (type(var)->containsNestedMapping()) + if (var.referenceLocation() == VariableDeclaration::Location::Storage) + solAssert( + _function.libraryFunction() || !_function.isPublic(), + "Mapping types for parameters or return variables " + "can only be used in internal or library functions." + ); + if (_function.isPublic()) { - if (var.referenceLocation() != VariableDeclaration::Location::Storage) - { - if (!_function.libraryFunction() && _function.isPublic()) - m_errorReporter.typeError(3442_error, var.location(), "Mapping types can only have a data location of \"storage\" and thus only be parameters or return variables for internal or library functions."); - else - m_errorReporter.typeError(5380_error, var.location(), "Mapping types can only have a data location of \"storage\"." ); - } - else - solAssert(_function.libraryFunction() || !_function.isPublic(), "Mapping types for parameters or return variables can only be used in internal or library functions."); - } - else - { - if (!type(var)->canLiveOutsideStorage() && _function.isPublic()) - m_errorReporter.typeError(3312_error, var.location(), "Type is required to live outside storage."); - if (_function.isPublic()) - { - auto iType = type(var)->interfaceType(_function.libraryFunction()); + auto iType = type(var)->interfaceType(_function.libraryFunction()); - if (!iType) - { - solAssert(!iType.message().empty(), "Expected detailed error message!"); - m_errorReporter.fatalTypeError(4103_error, var.location(), iType.message()); - } + if (!iType) + { + solAssert(!iType.message().empty(), "Expected detailed error message!"); + m_errorReporter.typeError(4103_error, var.location(), iType.message()); } } if ( @@ -457,9 +447,13 @@ bool TypeChecker::visit(VariableDeclaration const& _variable) m_errorReporter.typeError(1273_error, _variable.location(), "The type of a variable cannot be a library."); if (_variable.value()) { - if (_variable.isStateVariable() && dynamic_cast(varType)) + if (_variable.isStateVariable() && varType->containsNestedMapping()) { - m_errorReporter.typeError(6280_error, _variable.location(), "Mappings cannot be assigned to."); + m_errorReporter.typeError( + 6280_error, + _variable.location(), + "Types in storage containing (nested) mappings cannot be assigned to." + ); _variable.value()->accept(*this); } else @@ -499,9 +493,16 @@ bool TypeChecker::visit(VariableDeclaration const& _variable) if (!_variable.isStateVariable()) { - if (varType->dataStoredIn(DataLocation::Memory) || varType->dataStoredIn(DataLocation::CallData)) - if (!varType->canLiveOutsideStorage()) - m_errorReporter.typeError(4061_error, _variable.location(), "Type " + varType->toString() + " is only valid in storage."); + if ( + _variable.referenceLocation() == VariableDeclaration::Location::CallData || + _variable.referenceLocation() == VariableDeclaration::Location::Memory + ) + if (varType->containsNestedMapping()) + m_errorReporter.fatalTypeError( + 4061_error, + _variable.location(), + "Type " + varType->toString(true) + " is only valid in storage because it contains a (nested) mapping." + ); } else if (_variable.visibility() >= Visibility::Public) { @@ -612,8 +613,12 @@ bool TypeChecker::visit(EventDefinition const& _eventDef) { if (var->isIndexed()) numIndexed++; - if (!type(*var)->canLiveOutsideStorage()) - m_errorReporter.typeError(3448_error, var->location(), "Type is required to live outside storage."); + if (type(*var)->containsNestedMapping()) + m_errorReporter.typeError( + 3448_error, + var->location(), + "Type containing a (nested) mapping is not allowed as event parameter type." + ); if (!type(*var)->interfaceType(false)) m_errorReporter.typeError(3417_error, var->location(), "Internal or recursive type is not allowed as event parameter type."); if ( @@ -1386,7 +1391,7 @@ void TypeChecker::checkExpressionAssignment(Type const& _type, Expression const& checkExpressionAssignment(*types[i], *tupleExpression->components()[i]); } } - else if (_type.category() == Type::Category::Mapping) + else if (_type.nameable() && _type.containsNestedMapping()) { bool isLocalOrReturn = false; if (auto const* identifier = dynamic_cast(&_expression)) @@ -1394,7 +1399,7 @@ void TypeChecker::checkExpressionAssignment(Type const& _type, Expression const& if (variableDeclaration->isLocalOrReturn()) isLocalOrReturn = true; if (!isLocalOrReturn) - m_errorReporter.typeError(9214_error, _expression.location(), "Mappings cannot be assigned to."); + m_errorReporter.typeError(9214_error, _expression.location(), "Types in storage containing (nested) mappings cannot be assigned to."); } } @@ -1530,8 +1535,12 @@ bool TypeChecker::visit(TupleExpression const& _tuple) _tuple.location(), "Unable to deduce nameable type for array elements. Try adding explicit type conversion for the first element." ); - else if (!inlineArrayType->canLiveOutsideStorage()) - m_errorReporter.fatalTypeError(1545_error, _tuple.location(), "Type " + inlineArrayType->toString() + " is only valid in storage."); + else if (inlineArrayType->containsNestedMapping()) + m_errorReporter.fatalTypeError( + 1545_error, + _tuple.location(), + "Type " + inlineArrayType->toString(true) + " is only valid in storage." + ); _tuple.annotation().type = TypeProvider::array(DataLocation::Memory, inlineArrayType, types.size()); } @@ -2032,24 +2041,8 @@ void TypeChecker::typeCheckFunctionGeneralChecks( toString(parameterTypes.size()) + "."; - // Extend error message in case we try to construct a struct with mapping member. if (isStructConstructorCall) - { - /// For error message: Struct members that were removed during conversion to memory. - TypePointer const expressionType = type(_functionCall.expression()); - auto const& t = dynamic_cast(*expressionType); - auto const& structType = dynamic_cast(*t.actualType()); - set membersRemovedForStructConstructor = structType.membersMissingInMemory(); - - if (!membersRemovedForStructConstructor.empty()) - { - msg += " Members that have to be skipped in memory:"; - for (auto const& member: membersRemovedForStructConstructor) - msg += " " + member; - } - return { isVariadic ? 1123_error : 9755_error, msg }; - } else if ( _functionType->kind() == FunctionType::Kind::BareCall || _functionType->kind() == FunctionType::Kind::BareCallCode || @@ -2269,6 +2262,12 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) if (actualType->category() == Type::Category::Struct) { + if (actualType->containsNestedMapping()) + m_errorReporter.fatalTypeError( + 9515_error, + _functionCall.location(), + "Struct containing a (nested) mapping cannot be constructed." + ); functionType = dynamic_cast(*actualType).constructorType(); funcCallAnno.kind = FunctionCallKind::StructConstructorCall; funcCallAnno.isPure = argumentsArePure; @@ -2518,11 +2517,11 @@ void TypeChecker::endVisit(NewExpression const& _newExpression) } else if (type->category() == Type::Category::Array) { - if (!type->canLiveOutsideStorage()) + if (type->containsNestedMapping()) m_errorReporter.fatalTypeError( 1164_error, _newExpression.typeName().location(), - "Type cannot live outside storage." + "Array containing a (nested) mapping cannot be constructed in memory." ); if (!type->isDynamicallySized()) m_errorReporter.typeError( diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h index 507559ca6..6347092af 100644 --- a/libsolidity/ast/ASTAnnotations.h +++ b/libsolidity/ast/ASTAnnotations.h @@ -136,6 +136,9 @@ struct StructDeclarationAnnotation: TypeDeclarationAnnotation /// recursion immediately raises an error. /// Will be filled in by the DeclarationTypeChecker. std::optional recursive; + /// Whether the struct contains a mapping type, either directly or, indirectly inside another + /// struct or an array. + std::optional containsNestedMapping; }; struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, StructurallyDocumentedAnnotation diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 3592a1490..6f0554539 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -1985,6 +1985,20 @@ TypeResult ArrayType::interfaceType(bool _inLibrary) const return result; } +Type const* ArrayType::finalBaseType(bool _breakIfDynamicArrayType) const +{ + Type const* finalBaseType = this; + + while (auto arrayType = dynamic_cast(finalBaseType)) + { + if (_breakIfDynamicArrayType && arrayType->isDynamicallySized()) + break; + finalBaseType = arrayType->baseType(); + } + + return finalBaseType; +} + u256 ArrayType::memoryDataSize() const { solAssert(!isDynamicallySized(), ""); @@ -2213,7 +2227,7 @@ unsigned StructType::calldataEncodedSize(bool) const unsigned size = 0; for (auto const& member: members(nullptr)) { - solAssert(member.type->canLiveOutsideStorage(), ""); + solAssert(!member.type->containsNestedMapping(), ""); // Struct members are always padded. size += member.type->calldataEncodedSize(); } @@ -2228,7 +2242,7 @@ unsigned StructType::calldataEncodedTailSize() const unsigned size = 0; for (auto const& member: members(nullptr)) { - solAssert(member.type->canLiveOutsideStorage(), ""); + solAssert(!member.type->containsNestedMapping(), ""); // Struct members are always padded. size += member.type->calldataHeadSize(); } @@ -2240,7 +2254,7 @@ unsigned StructType::calldataOffsetOfMember(std::string const& _member) const unsigned offset = 0; for (auto const& member: members(nullptr)) { - solAssert(member.type->canLiveOutsideStorage(), ""); + solAssert(!member.type->containsNestedMapping(), ""); if (member.name == _member) return offset; // Struct members are always padded. @@ -2277,6 +2291,42 @@ u256 StructType::storageSize() const return max(1, members(nullptr).storageSize()); } +bool StructType::containsNestedMapping() const +{ + if (!m_struct.annotation().containsNestedMapping.has_value()) + { + bool hasNestedMapping = false; + + util::BreadthFirstSearch breadthFirstSearch{{&m_struct}}; + + breadthFirstSearch.run( + [&](StructDefinition const* _struct, auto&& _addChild) + { + for (auto const& member: _struct->members()) + { + TypePointer memberType = member->annotation().type; + solAssert(memberType, ""); + + if (auto arrayType = dynamic_cast(memberType)) + memberType = arrayType->finalBaseType(false); + + if (dynamic_cast(memberType)) + { + hasNestedMapping = true; + breadthFirstSearch.abort(); + } + else if (auto structType = dynamic_cast(memberType)) + _addChild(&structType->structDefinition()); + } + + }); + + m_struct.annotation().containsNestedMapping = hasNestedMapping; + } + + return m_struct.annotation().containsNestedMapping.value(); +} + string StructType::toString(bool _short) const { string ret = "struct " + m_struct.annotation().canonicalName; @@ -2292,10 +2342,7 @@ MemberList::MemberMap StructType::nativeMembers(ASTNode const*) const { TypePointer type = variable->annotation().type; solAssert(type, ""); - // If we are not in storage, skip all members that cannot live outside of storage, - // ex. mappings and array of mappings - if (location() != DataLocation::Storage && !type->canLiveOutsideStorage()) - continue; + solAssert(!(location() != DataLocation::Storage && type->containsNestedMapping()), ""); members.emplace_back( variable->name(), copyForLocationIfReference(type), @@ -2457,10 +2504,9 @@ FunctionTypePointer StructType::constructorType() const { TypePointers paramTypes; strings paramNames; + solAssert(!containsNestedMapping(), ""); for (auto const& member: members(nullptr)) { - if (!member.type->canLiveOutsideStorage()) - continue; paramNames.push_back(member.name); paramTypes.push_back(TypeProvider::withLocationIfReference(DataLocation::Memory, member.type)); } @@ -2494,20 +2540,12 @@ u256 StructType::memoryOffsetOfMember(string const& _name) const TypePointers StructType::memoryMemberTypes() const { + solAssert(!containsNestedMapping(), ""); TypePointers types; for (ASTPointer const& variable: m_struct.members()) - if (variable->annotation().type->canLiveOutsideStorage()) - types.push_back(TypeProvider::withLocationIfReference(DataLocation::Memory, variable->annotation().type)); - return types; -} + types.push_back(TypeProvider::withLocationIfReference(DataLocation::Memory, variable->annotation().type)); -set StructType::membersMissingInMemory() const -{ - set missing; - for (ASTPointer const& variable: m_struct.members()) - if (!variable->annotation().type->canLiveOutsideStorage()) - missing.insert(variable->name()); - return missing; + return types; } vector> StructType::makeStackItems() const @@ -3654,7 +3692,10 @@ TypeResult MappingType::interfaceType(bool _inLibrary) const } } else - return TypeResult::err("Only libraries are allowed to use the mapping type in public or external functions."); + return TypeResult::err( + "Types containing (nested) mappings can only be parameters or " + "return variables of internal or library functions." + ); return this; } diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index b902a1c77..fa374d08f 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -259,7 +259,11 @@ public: /// Returns true if the type can be stored in storage. virtual bool canBeStored() const { return true; } /// Returns false if the type cannot live outside the storage, i.e. if it includes some mapping. - virtual bool canLiveOutsideStorage() const { return true; } + virtual bool containsNestedMapping() const + { + solAssert(nameable(), "Called for a non nameable type."); + return false; + } /// Returns true if the type can be stored as a value (as opposed to a reference) on the stack, /// i.e. it behaves differently in lvalue context and in value context. virtual bool isValueType() const { return false; } @@ -549,7 +553,6 @@ public: bool operator==(Type const& _other) const override; bool canBeStored() const override { return false; } - bool canLiveOutsideStorage() const override { return false; } std::string toString(bool _short) const override; u256 literalValue(Literal const* _literal) const override; @@ -610,7 +613,6 @@ public: bool operator==(Type const& _other) const override; bool canBeStored() const override { return false; } - bool canLiveOutsideStorage() const override { return false; } std::string toString(bool) const override; TypePointer mobileType() const override; @@ -781,8 +783,9 @@ public: bool isDynamicallySized() const override { return m_hasDynamicLength; } bool isDynamicallyEncoded() const override; u256 storageSize() const override; - bool canLiveOutsideStorage() const override { return m_baseType->canLiveOutsideStorage(); } + bool containsNestedMapping() const override { return m_baseType->containsNestedMapping(); } bool nameable() const override { return true; } + std::string toString(bool _short) const override; std::string canonicalName() const override; std::string signatureInExternalFunction(bool _structsByName) const override; @@ -798,6 +801,7 @@ public: /// @returns true if this is a string bool isString() const { return m_arrayKind == ArrayKind::String; } Type const* baseType() const { solAssert(!!m_baseType, ""); return m_baseType; } + Type const* finalBaseType(bool breakIfDynamicArrayType) const; u256 const& length() const { return m_length; } u256 memoryDataSize() const override; @@ -842,7 +846,6 @@ public: unsigned calldataEncodedTailSize() const override { return 32; } bool isDynamicallySized() const override { return true; } bool isDynamicallyEncoded() const override { return true; } - bool canLiveOutsideStorage() const override { return m_arrayType.canLiveOutsideStorage(); } std::string toString(bool _short) const override; TypePointer mobileType() const override; @@ -883,7 +886,6 @@ public: } unsigned storageBytes() const override { solAssert(!isSuper(), ""); return 20; } bool leftAligned() const override { solAssert(!isSuper(), ""); return false; } - bool canLiveOutsideStorage() const override { return !isSuper(); } bool isValueType() const override { return !isSuper(); } bool nameable() const override { return !isSuper(); } std::string toString(bool _short) const override; @@ -945,7 +947,7 @@ public: bool isDynamicallyEncoded() const override; u256 memoryDataSize() const override; u256 storageSize() const override; - bool canLiveOutsideStorage() const override { return true; } + bool containsNestedMapping() const override; bool nameable() const override { return true; } std::string toString(bool _short) const override; @@ -975,8 +977,6 @@ public: /// @returns the vector of types of members available in memory. TypePointers memoryMemberTypes() const; - /// @returns the set of all members that are removed in the memory version (typically mappings). - std::set membersMissingInMemory() const; void clearCache() const override; @@ -1007,7 +1007,6 @@ public: } unsigned storageBytes() const override; bool leftAligned() const override { return false; } - bool canLiveOutsideStorage() const override { return true; } std::string toString(bool _short) const override; std::string canonicalName() const override; bool isValueType() const override { return true; } @@ -1047,7 +1046,6 @@ public: std::string toString(bool) const override; bool canBeStored() const override { return false; } u256 storageSize() const override; - bool canLiveOutsideStorage() const override { return false; } bool hasSimpleZeroValueInMemory() const override { return false; } TypePointer mobileType() const override; /// Converts components to their temporary types and performs some wildcard matching. @@ -1218,7 +1216,6 @@ public: unsigned storageBytes() const override; bool isValueType() const override { return true; } bool nameable() const override; - bool canLiveOutsideStorage() const override { return m_kind == Kind::Internal || m_kind == Kind::External; } bool hasSimpleZeroValueInMemory() const override { return false; } MemberList::MemberMap nativeMembers(ASTNode const* _currentScope) const override; TypePointer encodingType() const override; @@ -1351,7 +1348,7 @@ public: bool operator==(Type const& _other) const override; std::string toString(bool _short) const override; std::string canonicalName() const override; - bool canLiveOutsideStorage() const override { return false; } + bool containsNestedMapping() const override { return true; } TypeResult binaryOperatorResult(Token, Type const*) const override { return nullptr; } Type const* encodingType() const override; TypeResult interfaceType(bool _inLibrary) const override; @@ -1386,7 +1383,6 @@ public: bool operator==(Type const& _other) const override; bool canBeStored() const override { return false; } u256 storageSize() const override; - bool canLiveOutsideStorage() const override { return false; } bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); } std::string toString(bool _short) const override { return "type(" + m_actualType->toString(_short) + ")"; } MemberList::MemberMap nativeMembers(ASTNode const* _currentScope) const override; @@ -1412,7 +1408,6 @@ public: TypeResult binaryOperatorResult(Token, Type const*) const override { return nullptr; } bool canBeStored() const override { return false; } u256 storageSize() const override; - bool canLiveOutsideStorage() const override { return false; } bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); } std::string richIdentifier() const override; bool operator==(Type const& _other) const override; @@ -1439,7 +1434,6 @@ public: std::string richIdentifier() const override; bool operator==(Type const& _other) const override; bool canBeStored() const override { return false; } - bool canLiveOutsideStorage() const override { return true; } bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); } MemberList::MemberMap nativeMembers(ASTNode const*) const override; @@ -1479,7 +1473,6 @@ public: std::string richIdentifier() const override; bool operator==(Type const& _other) const override; bool canBeStored() const override { return false; } - bool canLiveOutsideStorage() const override { return true; } bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); } MemberList::MemberMap nativeMembers(ASTNode const*) const override; @@ -1512,7 +1505,6 @@ public: TypeResult binaryOperatorResult(Token, Type const*) const override { return nullptr; } unsigned calldataEncodedSize(bool) const override { return 32; } bool canBeStored() const override { return false; } - bool canLiveOutsideStorage() const override { return false; } bool isValueType() const override { return true; } bool hasSimpleZeroValueInMemory() const override { solAssert(false, ""); } std::string toString(bool) const override { return "inaccessible dynamic type"; } diff --git a/libsolidity/codegen/ABIFunctions.cpp b/libsolidity/codegen/ABIFunctions.cpp index 9f244fdb1..1e0eb1b88 100644 --- a/libsolidity/codegen/ABIFunctions.cpp +++ b/libsolidity/codegen/ABIFunctions.cpp @@ -860,8 +860,7 @@ string ABIFunctions::abiEncodingFunctionStruct( for (auto const& member: _to.members(nullptr)) { solAssert(member.type, ""); - if (!member.type->canLiveOutsideStorage()) - continue; + solAssert(!member.type->containsNestedMapping(), ""); TypePointer memberTypeTo = member.type->fullEncodingType(_options.encodeAsLibraryTypes, true, false); solUnimplementedAssert(memberTypeTo, "Encoding type \"" + member.type->toString() + "\" not yet implemented."); auto memberTypeFrom = _from.memberType(member.name); @@ -1340,7 +1339,7 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr for (auto const& member: _type.members(nullptr)) { solAssert(member.type, ""); - solAssert(member.type->canLiveOutsideStorage(), ""); + solAssert(!member.type->containsNestedMapping(), ""); auto decodingType = member.type->decodingType(); solAssert(decodingType, ""); bool dynamic = decodingType->isDynamicallyEncoded(); diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index c3e81d13a..48712f5e9 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -1069,8 +1069,7 @@ void CompilerUtils::convertType( // stack: for (auto const& member: typeOnStack->members(nullptr)) { - if (!member.type->canLiveOutsideStorage()) - continue; + solAssert(!member.type->containsNestedMapping(), ""); pair const& offsets = typeOnStack->storageOffsetsOfMember(member.name); _context << offsets.first << Instruction::DUP3 << Instruction::ADD; _context << u256(offsets.second); diff --git a/libsolidity/codegen/LValue.cpp b/libsolidity/codegen/LValue.cpp index 3a04d6f92..b0714ac75 100644 --- a/libsolidity/codegen/LValue.cpp +++ b/libsolidity/codegen/LValue.cpp @@ -355,13 +355,13 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc structType.structDefinition() == sourceType.structDefinition(), "Struct assignment with conversion." ); + solAssert(!structType.containsNestedMapping(), ""); solAssert(sourceType.location() != DataLocation::CallData, "Structs in calldata not supported."); for (auto const& member: structType.members(nullptr)) { // assign each member that can live outside of storage TypePointer const& memberType = member.type; - if (!memberType->canLiveOutsideStorage()) - continue; + solAssert(memberType->nameable(), ""); TypePointer sourceMemberType = sourceType.memberType(member.name); if (sourceType.location() == DataLocation::Storage) {