Allow user-defined types as mapping keys in parser and restrict to contracts during type checking.

This commit is contained in:
Daniel Kirchner 2020-02-04 10:59:23 +01:00
parent 7a194ffdab
commit d3cbfb0c5c
25 changed files with 808 additions and 18 deletions

View File

@ -1,7 +1,7 @@
### 0.6.3 (unreleased)
Language Features:
* Allow contract types and enums as keys for mappings.
Compiler Features:

View File

@ -59,7 +59,7 @@ TypeName = ElementaryTypeName
UserDefinedTypeName = Identifier ( '.' Identifier )*
Mapping = 'mapping' '(' ElementaryTypeName '=>' TypeName ')'
Mapping = 'mapping' '(' ( ElementaryTypeName | UserDefinedTypeName ) '=>' TypeName ')'
ArrayTypeName = TypeName '[' Expression? ']'
FunctionTypeName = 'function' FunctionTypeParameterList ( 'internal' | 'external' | StateMutability )*
( 'returns' FunctionTypeParameterList )?

View File

@ -7,9 +7,8 @@ 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 plus ``bytes`` and ``string``. User-defined
or complex types such as contract types, enums, mappings, structs or array types
apart from ``bytes`` and ``string`` are not allowed.
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.
You can think of mappings as `hash tables <https://en.wikipedia.org/wiki/Hash_table>`_, which are virtually initialised

View File

@ -2876,6 +2876,25 @@ void TypeChecker::endVisit(Literal const& _literal)
_literal.annotation().isPure = true;
}
bool TypeChecker::visit(Mapping const& _mapping)
{
if (auto const* keyType = dynamic_cast<UserDefinedTypeName const*>(&_mapping.keyType()))
{
if (
keyType->annotation().type->category() != Type::Category::Contract &&
keyType->annotation().type->category() != Type::Category::Enum
)
m_errorReporter.typeError(
keyType->location(),
"Only elementary types, contract types or enums are allowed as mapping keys."
);
}
else
solAssert(dynamic_cast<ElementaryTypeName const*>(&_mapping.keyType()), "");
return true;
}
bool TypeChecker::contractDependenciesAreCyclic(
ContractDefinition const& _contract,
std::set<ContractDefinition const*> const& _seenContracts

View File

@ -143,6 +143,7 @@ private:
bool visit(Identifier const& _identifier) override;
void endVisit(ElementaryTypeNameExpression const& _expr) override;
void endVisit(Literal const& _literal) override;
bool visit(Mapping const& _mapping) override;
bool contractDependenciesAreCyclic(
ContractDefinition const& _contract,

View File

@ -1152,18 +1152,18 @@ public:
Mapping(
int64_t _id,
SourceLocation const& _location,
ASTPointer<ElementaryTypeName> const& _keyType,
ASTPointer<TypeName> const& _keyType,
ASTPointer<TypeName> const& _valueType
):
TypeName(_id, _location), m_keyType(_keyType), m_valueType(_valueType) {}
void accept(ASTVisitor& _visitor) override;
void accept(ASTConstVisitor& _visitor) const override;
ElementaryTypeName const& keyType() const { return *m_keyType; }
TypeName const& keyType() const { return *m_keyType; }
TypeName const& valueType() const { return *m_valueType; }
private:
ASTPointer<ElementaryTypeName> m_keyType;
ASTPointer<TypeName> m_keyType;
ASTPointer<TypeName> m_valueType;
};

View File

@ -511,7 +511,7 @@ ASTPointer<Mapping> ASTJsonImporter::createMapping(Json::Value const& _node)
{
return createASTNode<Mapping>(
_node,
createElementaryTypeName(member(_node, "keyType")),
convertJsonToASTNode<TypeName>(member(_node, "keyType")),
convertJsonToASTNode<TypeName>(member(_node, "valueType"))
);
}

View File

@ -1019,16 +1019,22 @@ ASTPointer<Mapping> Parser::parseMapping()
ASTNodeFactory nodeFactory(*this);
expectToken(Token::Mapping);
expectToken(Token::LParen);
ASTPointer<ElementaryTypeName> keyType;
ASTPointer<TypeName> keyType;
Token token = m_scanner->currentToken();
if (!TokenTraits::isElementaryTypeName(token))
fatalParserError(string("Expected elementary type name for mapping key type"));
unsigned firstSize;
unsigned secondSize;
tie(firstSize, secondSize) = m_scanner->currentTokenInfo();
ElementaryTypeNameToken elemTypeName(token, firstSize, secondSize);
keyType = ASTNodeFactory(*this).createNode<ElementaryTypeName>(elemTypeName);
m_scanner->next();
if (token == Token::Identifier)
keyType = parseUserDefinedTypeName();
else if (TokenTraits::isElementaryTypeName(token))
{
keyType = ASTNodeFactory(*this).createNode<ElementaryTypeName>(
ElementaryTypeNameToken{token, firstSize, secondSize}
);
m_scanner->next();
}
else
fatalParserError(string("Expected elementary type name or identifier for mapping key type"));
expectToken(Token::Arrow);
bool const allowVar = false;
ASTPointer<TypeName> valueType = parseTypeName(allowVar);

View File

@ -0,0 +1,227 @@
{
"absolutePath": "a",
"exportedSymbols":
{
"C":
[
17
]
},
"id": 18,
"nodeType": "SourceUnit",
"nodes":
[
{
"abstract": false,
"baseContracts": [],
"contractDependencies": [],
"contractKind": "contract",
"documentation": null,
"fullyImplemented": true,
"id": 17,
"linearizedBaseContracts":
[
17
],
"name": "C",
"nodeType": "ContractDefinition",
"nodes":
[
{
"canonicalName": "C.E",
"id": 4,
"members":
[
{
"id": 1,
"name": "A",
"nodeType": "EnumValue",
"src": "26:1:1"
},
{
"id": 2,
"name": "B",
"nodeType": "EnumValue",
"src": "29:1:1"
},
{
"id": 3,
"name": "C",
"nodeType": "EnumValue",
"src": "32:1:1"
}
],
"name": "E",
"nodeType": "EnumDefinition",
"src": "17:18:1"
},
{
"constant": false,
"id": 8,
"name": "a",
"nodeType": "VariableDeclaration",
"overrides": null,
"scope": 17,
"src": "40:20:1",
"stateVariable": true,
"storageLocation": "default",
"typeDescriptions":
{
"typeIdentifier": "t_mapping$_t_contract$_C_$17_$_t_bool_$",
"typeString": "mapping(contract C => bool)"
},
"typeName":
{
"id": 7,
"keyType":
{
"contractScope": null,
"id": 5,
"name": "C",
"nodeType": "UserDefinedTypeName",
"referencedDeclaration": 17,
"src": "48:1:1",
"typeDescriptions":
{
"typeIdentifier": "t_contract$_C_$17",
"typeString": "contract C"
}
},
"nodeType": "Mapping",
"src": "40:18:1",
"typeDescriptions":
{
"typeIdentifier": "t_mapping$_t_contract$_C_$17_$_t_bool_$",
"typeString": "mapping(contract C => bool)"
},
"valueType":
{
"id": 6,
"name": "bool",
"nodeType": "ElementaryTypeName",
"src": "53:4:1",
"typeDescriptions":
{
"typeIdentifier": "t_bool",
"typeString": "bool"
}
}
},
"value": null,
"visibility": "internal"
},
{
"constant": false,
"id": 12,
"name": "b",
"nodeType": "VariableDeclaration",
"overrides": null,
"scope": 17,
"src": "66:26:1",
"stateVariable": true,
"storageLocation": "default",
"typeDescriptions":
{
"typeIdentifier": "t_mapping$_t_address_$_t_bool_$",
"typeString": "mapping(address => bool)"
},
"typeName":
{
"id": 11,
"keyType":
{
"id": 9,
"name": "address",
"nodeType": "ElementaryTypeName",
"src": "74:7:1",
"typeDescriptions":
{
"typeIdentifier": "t_address",
"typeString": "address"
}
},
"nodeType": "Mapping",
"src": "66:24:1",
"typeDescriptions":
{
"typeIdentifier": "t_mapping$_t_address_$_t_bool_$",
"typeString": "mapping(address => bool)"
},
"valueType":
{
"id": 10,
"name": "bool",
"nodeType": "ElementaryTypeName",
"src": "85:4:1",
"typeDescriptions":
{
"typeIdentifier": "t_bool",
"typeString": "bool"
}
}
},
"value": null,
"visibility": "internal"
},
{
"constant": false,
"id": 16,
"name": "c",
"nodeType": "VariableDeclaration",
"overrides": null,
"scope": 17,
"src": "98:20:1",
"stateVariable": true,
"storageLocation": "default",
"typeDescriptions":
{
"typeIdentifier": "t_mapping$_t_enum$_E_$4_$_t_bool_$",
"typeString": "mapping(enum C.E => bool)"
},
"typeName":
{
"id": 15,
"keyType":
{
"contractScope": null,
"id": 13,
"name": "E",
"nodeType": "UserDefinedTypeName",
"referencedDeclaration": 4,
"src": "106:1:1",
"typeDescriptions":
{
"typeIdentifier": "t_enum$_E_$4",
"typeString": "enum C.E"
}
},
"nodeType": "Mapping",
"src": "98:18:1",
"typeDescriptions":
{
"typeIdentifier": "t_mapping$_t_enum$_E_$4_$_t_bool_$",
"typeString": "mapping(enum C.E => bool)"
},
"valueType":
{
"id": 14,
"name": "bool",
"nodeType": "ElementaryTypeName",
"src": "111:4:1",
"typeDescriptions":
{
"typeIdentifier": "t_bool",
"typeString": "bool"
}
}
},
"value": null,
"visibility": "internal"
}
],
"scope": 18,
"src": "0:121:1"
}
],
"src": "0:122:1"
}

View File

@ -0,0 +1,8 @@
contract C {
enum E { A, B, C }
mapping(C => bool) a;
mapping(address => bool) b;
mapping(E => bool) c;
}
// ----

View File

@ -0,0 +1,248 @@
{
"attributes":
{
"absolutePath": "a",
"exportedSymbols":
{
"C":
[
17
]
}
},
"children":
[
{
"attributes":
{
"abstract": false,
"baseContracts":
[
null
],
"contractDependencies":
[
null
],
"contractKind": "contract",
"documentation": null,
"fullyImplemented": true,
"linearizedBaseContracts":
[
17
],
"name": "C",
"scope": 18
},
"children":
[
{
"attributes":
{
"canonicalName": "C.E",
"name": "E"
},
"children":
[
{
"attributes":
{
"name": "A"
},
"id": 1,
"name": "EnumValue",
"src": "26:1:1"
},
{
"attributes":
{
"name": "B"
},
"id": 2,
"name": "EnumValue",
"src": "29:1:1"
},
{
"attributes":
{
"name": "C"
},
"id": 3,
"name": "EnumValue",
"src": "32:1:1"
}
],
"id": 4,
"name": "EnumDefinition",
"src": "17:18:1"
},
{
"attributes":
{
"constant": false,
"name": "a",
"overrides": null,
"scope": 17,
"stateVariable": true,
"storageLocation": "default",
"type": "mapping(contract C => bool)",
"value": null,
"visibility": "internal"
},
"children":
[
{
"attributes":
{
"type": "mapping(contract C => bool)"
},
"children":
[
{
"attributes":
{
"contractScope": null,
"name": "C",
"referencedDeclaration": 17,
"type": "contract C"
},
"id": 5,
"name": "UserDefinedTypeName",
"src": "48:1:1"
},
{
"attributes":
{
"name": "bool",
"type": "bool"
},
"id": 6,
"name": "ElementaryTypeName",
"src": "53:4:1"
}
],
"id": 7,
"name": "Mapping",
"src": "40:18:1"
}
],
"id": 8,
"name": "VariableDeclaration",
"src": "40:20:1"
},
{
"attributes":
{
"constant": false,
"name": "b",
"overrides": null,
"scope": 17,
"stateVariable": true,
"storageLocation": "default",
"type": "mapping(address => bool)",
"value": null,
"visibility": "internal"
},
"children":
[
{
"attributes":
{
"type": "mapping(address => bool)"
},
"children":
[
{
"attributes":
{
"name": "address",
"type": "address"
},
"id": 9,
"name": "ElementaryTypeName",
"src": "74:7:1"
},
{
"attributes":
{
"name": "bool",
"type": "bool"
},
"id": 10,
"name": "ElementaryTypeName",
"src": "85:4:1"
}
],
"id": 11,
"name": "Mapping",
"src": "66:24:1"
}
],
"id": 12,
"name": "VariableDeclaration",
"src": "66:26:1"
},
{
"attributes":
{
"constant": false,
"name": "c",
"overrides": null,
"scope": 17,
"stateVariable": true,
"storageLocation": "default",
"type": "mapping(enum C.E => bool)",
"value": null,
"visibility": "internal"
},
"children":
[
{
"attributes":
{
"type": "mapping(enum C.E => bool)"
},
"children":
[
{
"attributes":
{
"contractScope": null,
"name": "E",
"referencedDeclaration": 4,
"type": "enum C.E"
},
"id": 13,
"name": "UserDefinedTypeName",
"src": "106:1:1"
},
{
"attributes":
{
"name": "bool",
"type": "bool"
},
"id": 14,
"name": "ElementaryTypeName",
"src": "111:4:1"
}
],
"id": 15,
"name": "Mapping",
"src": "98:18:1"
}
],
"id": 16,
"name": "VariableDeclaration",
"src": "98:20:1"
}
],
"id": 17,
"name": "ContractDefinition",
"src": "0:121:1"
}
],
"id": 18,
"name": "SourceUnit",
"src": "0:122:1"
}

View File

@ -0,0 +1,28 @@
interface A {}
contract test {
mapping(A => uint8) table;
function get(A k) public returns (uint8 v) {
return table[k];
}
function set(A k, uint8 v) public {
table[k] = v;
}
}
// ====
// compileViaYul: also
// ----
// get(address): 0 -> 0
// get(address): 0x01 -> 0
// get(address): 0xa7 -> 0
// set(address,uint8): 0x01, 0xa1 ->
// get(address): 0 -> 0
// get(address): 0x01 -> 0xa1
// get(address): 0xa7 -> 0
// set(address,uint8): 0x00, 0xef ->
// get(address): 0 -> 0xef
// get(address): 0x01 -> 0xa1
// get(address): 0xa7 -> 0
// set(address,uint8): 0x01, 0x05 ->
// get(address): 0 -> 0xef
// get(address): 0x01 -> 0x05
// get(address): 0xa7 -> 0

View File

@ -0,0 +1,38 @@
interface A {}
contract test {
mapping(A => uint8) public table;
function set(A k, uint8 v) public {
table[k] = v;
}
function get(A k) public returns (uint8) {
return this.table(k);
}
}
// ----
// table(address): 0 -> 0
// table(address): 0x01 -> 0
// table(address): 0xa7 -> 0
// get(address): 0 -> 0
// get(address): 0x01 -> 0
// get(address): 0xa7 -> 0
// set(address,uint8): 0x01, 0xa1 ->
// table(address): 0 -> 0
// table(address): 0x01 -> 0xa1
// table(address): 0xa7 -> 0
// get(address): 0 -> 0
// get(address): 0x01 -> 0xa1
// get(address): 0xa7 -> 0
// set(address,uint8): 0x00, 0xef ->
// table(address): 0 -> 0xef
// table(address): 0x01 -> 0xa1
// table(address): 0xa7 -> 0
// get(address): 0 -> 0xef
// get(address): 0x01 -> 0xa1
// get(address): 0xa7 -> 0
// set(address,uint8): 0x01, 0x05 ->
// table(address): 0 -> 0xef
// table(address): 0x01 -> 0x05
// table(address): 0xa7 -> 0
// get(address): 0 -> 0xef
// get(address): 0x01 -> 0x05
// get(address): 0xa7 -> 0

View File

@ -0,0 +1,35 @@
interface A {}
library L {
function get(mapping(A => uint8) storage table, A k) external returns (uint8) {
return table[k];
}
function set(mapping(A => uint8) storage table, A k, uint8 v) external {
table[k] = v;
}
}
contract test {
mapping(A => uint8) table;
function get(A k) public returns (uint8 v) {
return L.get(table, k);
}
function set(A k, uint8 v) public {
L.set(table, k, v);
}
}
// ----
// library: L
// get(address): 0 -> 0
// get(address): 0x01 -> 0
// get(address): 0xa7 -> 0
// set(address,uint8): 0x01, 0xa1 ->
// get(address): 0 -> 0
// get(address): 0x01 -> 0xa1
// get(address): 0xa7 -> 0
// set(address,uint8): 0x00, 0xef ->
// get(address): 0 -> 0xef
// get(address): 0x01 -> 0xa1
// get(address): 0xa7 -> 0
// set(address,uint8): 0x01, 0x05 ->
// get(address): 0 -> 0xef
// get(address): 0x01 -> 0x05
// get(address): 0xa7 -> 0

View File

@ -0,0 +1,30 @@
enum E { A, B, C }
contract test {
mapping(E => uint8) table;
function get(E k) public returns (uint8 v) {
return table[k];
}
function set(E k, uint8 v) public {
table[k] = v;
}
}
// ====
// compileViaYul: also
// ----
// get(uint8): 0 -> 0
// get(uint8): 0x01 -> 0
// get(uint8): 0x02 -> 0
// get(uint8): 0x03 -> FAILURE
// get(uint8): 0xa7 -> FAILURE
// set(uint8,uint8): 0x01, 0xa1 ->
// get(uint8): 0 -> 0
// get(uint8): 0x01 -> 0xa1
// get(uint8): 0xa7 -> FAILURE
// set(uint8,uint8): 0x00, 0xef ->
// get(uint8): 0 -> 0xef
// get(uint8): 0x01 -> 0xa1
// get(uint8): 0xa7 -> FAILURE
// set(uint8,uint8): 0x01, 0x05 ->
// get(uint8): 0 -> 0xef
// get(uint8): 0x01 -> 0x05
// get(uint8): 0xa7 -> FAILURE

View File

@ -0,0 +1,38 @@
contract test {
enum E { A, B, C }
mapping(E => uint8) public table;
function set(E k, uint8 v) public {
table[k] = v;
}
function get(E k) public returns (uint8) {
return this.table(k);
}
}
// ----
// table(uint8): 0 -> 0
// table(uint8): 0x01 -> 0
// table(uint8): 0xa7 -> 0
// get(uint8): 0 -> 0
// get(uint8): 0x01 -> 0
// get(uint8): 0xa7 -> FAILURE
// set(uint8,uint8): 0x01, 0xa1 ->
// table(uint8): 0 -> 0
// table(uint8): 0x01 -> 0xa1
// table(uint8): 0xa7 -> 0
// get(uint8): 0 -> 0
// get(uint8): 0x01 -> 0xa1
// get(uint8): 0xa7 -> FAILURE
// set(uint8,uint8): 0x00, 0xef ->
// table(uint8): 0 -> 0xef
// table(uint8): 0x01 -> 0xa1
// table(uint8): 0xa7 -> 0
// get(uint8): 0 -> 0xef
// get(uint8): 0x01 -> 0xa1
// get(uint8): 0xa7 -> FAILURE
// set(uint8,uint8): 0x01, 0x05 ->
// table(uint8): 0 -> 0xef
// table(uint8): 0x01 -> 0x05
// table(uint8): 0xa7 -> 0
// get(uint8): 0 -> 0xef
// get(uint8): 0x01 -> 0x05
// get(uint8): 0xa7 -> FAILURE

View File

@ -0,0 +1,39 @@
pragma experimental ABIEncoderV2;
contract test {
enum E { A, B, C }
mapping(E => uint8) public table;
function set(E k, uint8 v) public {
table[k] = v;
}
function get(E k) public returns (uint8) {
return this.table(k);
}
}
// ----
// table(uint8): 0 -> 0
// table(uint8): 0x01 -> 0
// table(uint8): 0xa7 -> FAILURE
// get(uint8): 0 -> 0
// get(uint8): 0x01 -> 0
// get(uint8): 0xa7 -> FAILURE
// set(uint8,uint8): 0x01, 0xa1 ->
// table(uint8): 0 -> 0
// table(uint8): 0x01 -> 0xa1
// table(uint8): 0xa7 -> FAILURE
// get(uint8): 0 -> 0
// get(uint8): 0x01 -> 0xa1
// get(uint8): 0xa7 -> FAILURE
// set(uint8,uint8): 0x00, 0xef ->
// table(uint8): 0 -> 0xef
// table(uint8): 0x01 -> 0xa1
// table(uint8): 0xa7 -> FAILURE
// get(uint8): 0 -> 0xef
// get(uint8): 0x01 -> 0xa1
// get(uint8): 0xa7 -> FAILURE
// set(uint8,uint8): 0x01, 0x05 ->
// table(uint8): 0 -> 0xef
// table(uint8): 0x01 -> 0x05
// table(uint8): 0xa7 -> FAILURE
// get(uint8): 0 -> 0xef
// get(uint8): 0x01 -> 0x05
// get(uint8): 0xa7 -> FAILURE

View File

@ -0,0 +1,35 @@
enum E { A, B, C }
library L {
function get(mapping(E => uint8) storage table, E k) external returns (uint8) {
return table[k];
}
function set(mapping(E => uint8) storage table, E k, uint8 v) external {
table[k] = v;
}
}
contract test {
mapping(E => uint8) table;
function get(E k) public returns (uint8 v) {
return L.get(table, k);
}
function set(E k, uint8 v) public {
L.set(table, k, v);
}
}
// ----
// library: L
// get(uint8): 0 -> 0
// get(uint8): 0x01 -> 0
// get(uint8): 0xa7 -> FAILURE
// set(uint8,uint8): 0x01, 0xa1 ->
// get(uint8): 0 -> 0
// get(uint8): 0x01 -> 0xa1
// get(uint8): 0xa7 -> FAILURE
// set(uint8,uint8): 0x00, 0xef ->
// get(uint8): 0 -> 0xef
// get(uint8): 0x01 -> 0xa1
// get(uint8): 0xa7 -> FAILURE
// set(uint8,uint8): 0x01, 0x05 ->
// get(uint8): 0 -> 0xef
// get(uint8): 0x01 -> 0x05
// get(uint8): 0xa7 -> FAILURE

View File

@ -0,0 +1,9 @@
interface I {}
contract J {}
contract C {
mapping(I => bool) i;
mapping(J => bool) j;
function f(I x, J y) public view returns (bool, bool) {
return (i[x], j[y]);
}
}

View File

@ -0,0 +1,13 @@
interface I {}
contract J {}
contract C {
mapping(I => bool) i;
mapping(J => bool) j;
function f(I x, J y, address z) public view returns (bool, bool, bool) {
return (i[y], j[x], i[z]);
}
}
// ----
// TypeError: (189-190): Type contract J is not implicitly convertible to expected type contract I.
// TypeError: (195-196): Type contract I is not implicitly convertible to expected type contract J.
// TypeError: (201-202): Type address is not implicitly convertible to expected type contract I.

View File

@ -0,0 +1,7 @@
enum E { A, B, C }
contract C {
mapping(E => bool) e;
function f(E v) public view returns (bool, bool) {
return (e[v], e[E.A]);
}
}

View File

@ -0,0 +1,10 @@
enum E { A, B, C }
contract C {
mapping(E => bool) e;
function f(uint256 a, uint8 b) public view returns (bool, bool) {
return (e[a], e[b]);
}
}
// ----
// TypeError: (146-147): Type uint256 is not implicitly convertible to expected type enum E.
// TypeError: (152-153): Type uint8 is not implicitly convertible to expected type enum E.

View File

@ -5,4 +5,4 @@ contract c {
mapping(S => uint) data;
}
// ----
// ParserError: (47-48): Expected elementary type name for mapping key type
// TypeError: (47-48): Only elementary types, contract types or enums are allowed as mapping keys.

View File

@ -5,4 +5,4 @@ contract c {
mapping(S => uint) data;
}
// ----
// ParserError: (49-50): Expected elementary type name for mapping key type
// TypeError: (49-50): Only elementary types, contract types or enums are allowed as mapping keys.

View File

@ -4,4 +4,4 @@ contract test {
}
}
// ----
// ParserError: (44-47): Expected elementary type name for mapping key type
// ParserError: (44-47): Expected elementary type name or identifier for mapping key type