Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow named parameters in mapping types #13384

Merged
merged 1 commit into from
Jan 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
### 0.8.18 (unreleased)

Language Features:
* Allow named parameters in mapping types.


Compiler Features:
Expand Down
2 changes: 1 addition & 1 deletion docs/grammar/SolidityParser.g4
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,7 @@ variableDeclarationTuple:
variableDeclarationStatement: ((variableDeclaration (Assign expression)?) | (variableDeclarationTuple Assign expression)) Semicolon;
expressionStatement: expression Semicolon;

mappingType: Mapping LParen key=mappingKeyType DoubleArrow value=typeName RParen;
mappingType: Mapping LParen key=mappingKeyType name=identifier? DoubleArrow value=typeName name=identifier? RParen;
/**
* Only elementary types or user defined types are viable as mapping keys.
*/
Expand Down
38 changes: 30 additions & 8 deletions docs/types/mapping-types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
Mapping Types
=============

Mapping types use the syntax ``mapping(KeyType => ValueType)`` and variables
of mapping type are declared using the syntax ``mapping(KeyType => ValueType) VariableName``.
The ``KeyType`` can be any
built-in value type, ``bytes``, ``string``, or any contract or enum type. Other user-defined
or complex types, such as mappings, structs or array types are not allowed.
``ValueType`` can be any type, including mappings, arrays and structs.
Mapping types use the syntax ``mapping(KeyType KeyName? => ValueType ValueName?)`` and variables of
mapping type are declared using the syntax ``mapping(KeyType KeyName? => ValueType ValueName?)
VariableName``. The ``KeyType`` can be any built-in value type, ``bytes``, ``string``, or any
contract or enum type. Other user-defined or complex types, such as mappings, structs or array types
are not allowed. ``ValueType`` can be any type, including mappings, arrays and structs. ``KeyName``
and ``ValueName`` are optional (so ``mapping(KeyType => ValueType)`` works as well) and can be any
valid identifier that is not a type.

You can think of mappings as `hash tables <https://en.wikipedia.org/wiki/Hash_table>`_, which are virtually initialised
such that every possible key exists and is mapped to a value whose
Expand All @@ -29,8 +30,10 @@ of contract functions that are publicly visible.
These restrictions are also true for arrays and structs that contain mappings.

You can mark state variables of mapping type as ``public`` and Solidity creates a
:ref:`getter <visibility-and-getters>` for you. The ``KeyType`` becomes a parameter for the getter.
If ``ValueType`` is a value type or a struct, the getter returns ``ValueType``.
:ref:`getter <visibility-and-getters>` for you. The ``KeyType`` becomes a parameter
with name ``KeyName`` (if specified) for the getter.
If ``ValueType`` is a value type or a struct, the getter returns ``ValueType`` with
name ``ValueName`` (if specified).
If ``ValueType`` is an array or a mapping, the getter has one parameter for
each ``KeyType``, recursively.

Expand Down Expand Up @@ -64,6 +67,25 @@ contract that returns the value at the specified address.
The example below is a simplified version of an
`ERC20 token <https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol>`_.
``_allowances`` is an example of a mapping type inside another mapping type.

In the example below, the optional ``KeyName`` and ``ValueName`` are provided for the mapping.
It does not affect any contract functionality or bytecode, it only sets the ``name`` field
for the inputs and outputs in the ABI for the mapping's getter.

.. code-block:: solidity

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.18;

contract MappingExampleWithNames {
mapping(address user => uint balance) public balances;

function update(uint newBalance) public {
balances[msg.sender] = newBalance;
}
}


The example below uses ``_allowances`` to record the amount someone else is allowed to withdraw from your account.

.. code-block:: solidity
Expand Down
47 changes: 46 additions & 1 deletion libsolidity/analysis/DeclarationTypeChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -267,14 +267,59 @@ void DeclarationTypeChecker::endVisit(Mapping const& _mapping)
solAssert(dynamic_cast<ElementaryTypeName const*>(&_mapping.keyType()), "");

Type const* keyType = _mapping.keyType().annotation().type;
ASTString keyName = _mapping.keyName();

Type const* valueType = _mapping.valueType().annotation().type;
ASTString valueName = _mapping.valueName();

// Convert key type to memory.
keyType = TypeProvider::withLocationIfReference(DataLocation::Memory, keyType);

// Convert value type to storage reference.
valueType = TypeProvider::withLocationIfReference(DataLocation::Storage, valueType);
_mapping.annotation().type = TypeProvider::mapping(keyType, valueType);
_mapping.annotation().type = TypeProvider::mapping(keyType, keyName, valueType, valueName);

// Check if parameter names are conflicting.
if (!keyName.empty())
{
auto childMappingType = dynamic_cast<MappingType const*>(valueType);
ASTString currentValueName = valueName;
bool loop = true;
while (loop)
{
bool isError = false;
// Value type is a mapping.
if (childMappingType)
{
// Compare top mapping's key name with child mapping's key name.
ASTString childKeyName = childMappingType->keyName();
if (keyName == childKeyName)
isError = true;

auto valueType = childMappingType->valueType();
currentValueName = childMappingType->valueName();
childMappingType = dynamic_cast<MappingType const*>(valueType);
}
else
{
// Compare top mapping's key name with the value name.
if (keyName == currentValueName)
isError = true;

loop = false; // We arrived at the end of mapping recursion.
}

// Report error.
if (isError)
{
m_errorReporter.declarationError(
1809_error,
_mapping.location(),
"Conflicting parameter name \"" + keyName + "\" in mapping."
);
}
}
}
}

void DeclarationTypeChecker::endVisit(ArrayTypeName const& _typeName)
Expand Down
15 changes: 13 additions & 2 deletions libsolidity/ast/AST.h
Original file line number Diff line number Diff line change
Expand Up @@ -1428,18 +1428,29 @@ class Mapping: public TypeName
int64_t _id,
SourceLocation const& _location,
ASTPointer<TypeName> _keyType,
ASTPointer<TypeName> _valueType
ASTPointer<ASTString> _keyName,
ASTPointer<TypeName> _valueType,
ASTPointer<ASTString> _valueName
):
TypeName(_id, _location), m_keyType(std::move(_keyType)), m_valueType(std::move(_valueType)) {}
TypeName(_id, _location),
m_keyType(std::move(_keyType)),
m_keyName(std::move(_keyName)),
m_valueType(std::move(_valueType)),
m_valueName(std::move(_valueName))
{}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;

TypeName const& keyType() const { return *m_keyType; }
ASTString keyName() const { return *m_keyName; }
TypeName const& valueType() const { return *m_valueType; }
ASTString valueName() const { return *m_valueName; }

private:
ASTPointer<TypeName> m_keyType;
ASTPointer<ASTString> m_keyName;
ASTPointer<TypeName> m_valueType;
ASTPointer<ASTString> m_valueName;
};

/**
Expand Down
2 changes: 2 additions & 0 deletions libsolidity/ast/ASTJsonExporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -598,7 +598,9 @@ bool ASTJsonExporter::visit(Mapping const& _node)
{
setJsonNode(_node, "Mapping", {
make_pair("keyType", toJson(_node.keyType())),
make_pair("keyName", _node.keyName()),
make_pair("valueType", toJson(_node.valueType())),
make_pair("valueName", _node.valueName()),
make_pair("typeDescriptions", typePointerToJson(_node.annotation().type, true))
});
return false;
Expand Down
4 changes: 3 additions & 1 deletion libsolidity/ast/ASTJsonImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -648,7 +648,9 @@ ASTPointer<Mapping> ASTJsonImporter::createMapping(Json::Value const& _node)
return createASTNode<Mapping>(
_node,
convertJsonToASTNode<TypeName>(member(_node, "keyType")),
convertJsonToASTNode<TypeName>(member(_node, "valueType"))
memberAsASTString(_node, "keyName"),
convertJsonToASTNode<TypeName>(member(_node, "valueType")),
memberAsASTString(_node, "valueName")
);
}

Expand Down
4 changes: 2 additions & 2 deletions libsolidity/ast/TypeProvider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -571,9 +571,9 @@ MagicType const* TypeProvider::meta(Type const* _type)
return createAndGet<MagicType>(_type);
}

MappingType const* TypeProvider::mapping(Type const* _keyType, Type const* _valueType)
MappingType const* TypeProvider::mapping(Type const* _keyType, ASTString _keyName, Type const* _valueType, ASTString _valueName)
{
return createAndGet<MappingType>(_keyType, _valueType);
return createAndGet<MappingType>(_keyType, _keyName, _valueType, _valueName);
}

UserDefinedValueType const* TypeProvider::userDefinedValueType(UserDefinedValueTypeDefinition const& _definition)
Expand Down
2 changes: 1 addition & 1 deletion libsolidity/ast/TypeProvider.h
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ class TypeProvider

static MagicType const* meta(Type const* _type);

static MappingType const* mapping(Type const* _keyType, Type const* _valueType);
static MappingType const* mapping(Type const* _keyType, ASTString _keyName, Type const* _valueType, ASTString _valueName);

static UserDefinedValueType const* userDefinedValueType(UserDefinedValueTypeDefinition const& _definition);

Expand Down
6 changes: 4 additions & 2 deletions libsolidity/ast/Types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2788,14 +2788,16 @@ FunctionType::FunctionType(VariableDeclaration const& _varDecl):
m_declaration(&_varDecl)
{
auto returnType = _varDecl.annotation().type;
ASTString returnName;

while (true)
{
if (auto mappingType = dynamic_cast<MappingType const*>(returnType))
{
m_parameterTypes.push_back(mappingType->keyType());
m_parameterNames.emplace_back("");
m_parameterNames.push_back(mappingType->keyName());
returnType = mappingType->valueType();
returnName = mappingType->valueName();
}
else if (auto arrayType = dynamic_cast<ArrayType const*>(returnType))
{
Expand Down Expand Up @@ -2834,7 +2836,7 @@ FunctionType::FunctionType(VariableDeclaration const& _varDecl):
DataLocation::Memory,
returnType
));
m_returnParameterNames.emplace_back("");
m_returnParameterNames.emplace_back(returnName);
}

solAssert(
Expand Down
8 changes: 6 additions & 2 deletions libsolidity/ast/Types.h
Original file line number Diff line number Diff line change
Expand Up @@ -1510,8 +1510,8 @@ class FunctionType: public Type
class MappingType: public CompositeType
{
public:
MappingType(Type const* _keyType, Type const* _valueType):
m_keyType(_keyType), m_valueType(_valueType) {}
MappingType(Type const* _keyType, ASTString _keyName, Type const* _valueType, ASTString _valueName):
m_keyType(_keyType), m_keyName(_keyName), m_valueType(_valueType), m_valueName(_valueName) {}

Category category() const override { return Category::Mapping; }

Expand All @@ -1531,14 +1531,18 @@ class MappingType: public CompositeType
std::vector<std::tuple<std::string, Type const*>> makeStackItems() const override;

Type const* keyType() const { return m_keyType; }
ASTString keyName() const { return m_keyName; }
Type const* valueType() const { return m_valueType; }
ASTString valueName() const { return m_valueName; }

protected:
std::vector<Type const*> decomposition() const override { return {m_valueType}; }

private:
Type const* m_keyType;
ASTString m_keyName;
Type const* m_valueType;
ASTString m_valueName;
};

/**
Expand Down
12 changes: 11 additions & 1 deletion libsolidity/parsing/Parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1187,11 +1187,21 @@ ASTPointer<Mapping> Parser::parseMapping()
}
else
fatalParserError(1005_error, "Expected elementary type name or identifier for mapping key type");
ASTPointer<ASTString> keyName;
if (m_scanner->currentToken() == Token::Identifier)
keyName = getLiteralAndAdvance();
else
keyName = make_shared<ASTString>("");
zemse marked this conversation as resolved.
Show resolved Hide resolved
Marenz marked this conversation as resolved.
Show resolved Hide resolved
expectToken(Token::DoubleArrow);
ASTPointer<TypeName> valueType = parseTypeName();
ASTPointer<ASTString> valueName;
if (m_scanner->currentToken() == Token::Identifier)
valueName = getLiteralAndAdvance();
else
valueName = make_shared<ASTString>("");
nodeFactory.markEndPosition();
expectToken(Token::RParen);
return nodeFactory.createNode<Mapping>(keyType, valueType);
return nodeFactory.createNode<Mapping>(keyType, keyName, valueType, valueName);
}

ASTPointer<ParameterList> Parser::parseParameterList(
Expand Down
77 changes: 77 additions & 0 deletions test/libsolidity/ABIJson/mapping.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
contract test {
mapping(address owner => mapping(address spender => uint value)) public allowance;
zemse marked this conversation as resolved.
Show resolved Hide resolved
mapping(bytes32 => address sender) public commits;
mapping(bytes32 => bytes32) public something;
}
// ----
// :test
// [
// {
// "inputs":
// [
// {
// "internalType": "address",
// "name": "owner",
// "type": "address"
// },
// {
// "internalType": "address",
// "name": "spender",
// "type": "address"
// }
// ],
// "name": "allowance",
// "outputs":
// [
// {
// "internalType": "uint256",
// "name": "value",
// "type": "uint256"
// }
// ],
// "stateMutability": "view",
// "type": "function"
// },
// {
// "inputs":
// [
// {
// "internalType": "bytes32",
// "name": "",
// "type": "bytes32"
// }
// ],
// "name": "commits",
// "outputs":
// [
// {
// "internalType": "address",
// "name": "sender",
// "type": "address"
// }
// ],
// "stateMutability": "view",
// "type": "function"
// },
// {
// "inputs":
// [
// {
// "internalType": "bytes32",
// "name": "",
// "type": "bytes32"
// }
// ],
// "name": "something",
// "outputs":
// [
// {
// "internalType": "bytes32",
// "name": "",
// "type": "bytes32"
// }
// ],
// "stateMutability": "view",
// "type": "function"
// }
// ]
2 changes: 2 additions & 0 deletions test/libsolidity/ASTJSON/address_payable.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"typeName":
{
"id": 3,
"keyName": "",
"keyType":
{
"id": 1,
Expand All @@ -67,6 +68,7 @@
"typeIdentifier": "t_mapping$_t_address_$_t_address_payable_$",
"typeString": "mapping(address => address payable)"
},
"valueName": "",
"valueType":
{
"id": 2,
Expand Down
Loading