Repository: vyperlang/vyper

Issue:
VIP: Change Mapping Syntax
## Preamble
VIP: 564
Title: Change Mapping Syntax
Author: @fubuloubu @grosu
Type: Standard
Status: Draft
Created: 2017-12-08
Requires: #563


## Abstract
Mapping syntax looks very similar to the array syntax, so we should change the syntax so it is not confusing

## Motivation
See above

## Specification
Change Mapping Syntax to 
`my_mapping: map(basetype1, type2)`

Nested map Syntax to look as follows:
`my_mapping: map(basetype1, map(basetype1, basetype2))`

## Backwards Compatibility
Not Backwards compatible

## Copyright
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/)

---

EDIT: Previous proposed syntax was `my_mapping: basetype1 -> type2`



Discussion:
@fubuloubu Could you add an example of how nested mappings would look 
(maybe `my_mapping: basetype1 -> (basetype2 -> basetype3`?)). 
`my_mapping basetype1 -> basetype2 -> type3` I think that's pretty clear actually.

The harder part is how to represent the access e.g. `my_mapping[acces1][access2]` is how we do it now, but `[ ]` is the array syntax, I think we need to change it to something else.

There's also something like this in Haskell (I believe): `my_mapping: (basetype1, basetype2) -> type3`
Because this is how it works under the hood right? It hashes the two types together to obtain the location, right?
Meeting Notes:

Syntax would be more explicit like this
```python
my_mapping1: map(basetype1 -> type2)
my_mapping2: map(basetype1 -> basetype2 -> type3)
my_mapping3: map(bytes <= 20 -> type2)
```
I like the syntax proposed by @jacqueswww : 
```a: public(map({int128: {bytes32: int128}})``` in case of a nested map.
I sorta like it, although the syntax of structures using braces is also fairly close. Only difference is that only types can be used. Seems confusing.
I'm actually a fan of the syntax now. It's one of the things that I genuinely think is an improvement over most major languages. It's short, simple, and intuitive once you do it once or twice.

It could be interesting to use something else to enclose it perhaps. Maybe a declaration along the lines of `my_mapping: basetype1<basetype2>` which would make it instantly distinguishable from a list, but would keep the syntax similar.  To access, you could do something like `my_mapping<access>` which parallels the declaration as well. It could also be a cool distinguishing feature of the language, and if < and > are used it could become know as _Vyper Fangs_.
@jacqueswww I think we need to discuss this one on our next call. Lots of options here.
This proposal was updated per discussion in #1076
I'm a fan of the new proposed `my_mapping: map(basetype1, type2)`
Starting on this.
@jacqueswww maybe we should _deprecate_ the old syntax with a warning to move to the new one so people know to switch?

Resolving patch:
diff --git a/vyper/parser/global_context.py b/vyper/parser/global_context.py
--- a/vyper/parser/global_context.py
+++ b/vyper/parser/global_context.py
@@ -164,6 +164,10 @@ def get_item_name_and_attributes(self, item, attributes):
             return self.get_item_name_and_attributes(item.annotation, attributes)
         elif isinstance(item, ast.Subscript):
             return self.get_item_name_and_attributes(item.value, attributes)
+        elif isinstance(item, ast.Call) and item.func.id == 'map':
+            if len(item.args) != 2:
+                raise StructureException("Map type expects two type arguments map(type1, type2)", item.func)
+            return self.get_item_name_and_attributes(item.args, attributes)
         # elif ist
         elif isinstance(item, ast.Call):
             attributes[item.func.id] = True
@@ -279,7 +283,6 @@ def add_globals_and_events(self, item):
 
         elif len(self._defs):
             raise StructureException("Global variables must all come before function definitions", item)
-
         # If the type declaration is of the form public(<type here>), then proceed with
         # the underlying type but also add getters
         elif isinstance(item.annotation, ast.Call) and item.annotation.func.id == "address":
diff --git a/vyper/types/types.py b/vyper/types/types.py
--- a/vyper/types/types.py
+++ b/vyper/types/types.py
@@ -1,6 +1,7 @@
 import abc
 import ast
 import copy
+import warnings
 
 from vyper.exceptions import InvalidTypeException
 from vyper.utils import (
@@ -159,7 +160,7 @@ def eq(self, other):
         return other.keytype == self.keytype and other.valuetype == self.valuetype
 
     def __repr__(self):
-        return repr(self.valuetype) + '[' + repr(self.keytype) + ']'
+        return 'map(' + repr(self.valuetype) + ', ' + repr(self.keytype) + ')'
 
 
 # Data structure for a struct, e.g. {a: <type>, b: <type>}
@@ -275,6 +276,14 @@ def parse_type(item, location, sigs=None, custom_units=None):
             raise InvalidTypeException("Invalid base type: " + item.id, item)
     # Units, e.g. num (1/sec) or contracts
     elif isinstance(item, ast.Call):
+        # Mapping type.
+        if item.func.id == 'map':
+            if location == 'memory':
+                raise InvalidTypeException("No mappings allowed for in-memory types, only fixed-size arrays", item)
+            keytype = parse_type(item.args[0], None)
+            if not isinstance(keytype, (BaseType, ByteArrayType)):
+                raise InvalidTypeException("Mapping keys must be base or bytes types", item.slice.value)
+            return MappingType(keytype, parse_type(item.args[1], location, custom_units=custom_units))
         # Contract_types
         if item.func.id == 'address':
             if sigs and item.args[0].id in sigs:
@@ -313,12 +322,13 @@ def parse_type(item, location, sigs=None, custom_units=None):
                 return ListType(parse_type(item.value, location, custom_units=custom_units), item.slice.value.n)
         # Mappings, e.g. num[address]
         else:
-            if location == 'memory':
-                raise InvalidTypeException("No mappings allowed for in-memory types, only fixed-size arrays", item)
-            keytype = parse_type(item.slice.value, None)
-            if not isinstance(keytype, (BaseType, ByteArrayType)):
-                raise InvalidTypeException("Mapping keys must be base or bytes types", item.slice.value)
-            return MappingType(keytype, parse_type(item.value, location, custom_units=custom_units))
+            warnings.warn(
+                "Mapping definitions using subscript have deprecated (see VIP564). "
+                "Use map(type1, type2) instead.",
+                DeprecationWarning
+            )
+            raise InvalidTypeException('Unknown list type.', item)
+
     # Dicts, used to represent mappings, e.g. {uint: uint}. Key must be a base type
     elif isinstance(item, ast.Dict):
         o = {}
diff --git a/vyper/utils.py b/vyper/utils.py
--- a/vyper/utils.py
+++ b/vyper/utils.py
@@ -143,7 +143,7 @@ def in_bounds(cls, type_str, value):
 
 # Cannot be used for variable or member naming
 reserved_words = [
-    'int128', 'uint256', 'address', 'bytes32',
+    'int128', 'uint256', 'address', 'bytes32', 'map',
     'if', 'for', 'while', 'until',
     'pass', 'def', 'push', 'dup', 'swap', 'send', 'call',
     'selfdestruct', 'assert', 'stop', 'throw',


Test patch:
diff --git a/tests/parser/exceptions/test_structure_exception.py b/tests/parser/exceptions/test_structure_exception.py
--- a/tests/parser/exceptions/test_structure_exception.py
+++ b/tests/parser/exceptions/test_structure_exception.py
@@ -167,7 +167,7 @@ def foo() -> int128:
     return self.q
     """,
     """
-b: bytes32[int128]
+b: map(int128, bytes32)
 @public
 def foo():
     del self.b[0], self.b[1]
diff --git a/tests/parser/features/test_assignment.py b/tests/parser/features/test_assignment.py
--- a/tests/parser/features/test_assignment.py
+++ b/tests/parser/features/test_assignment.py
@@ -130,8 +130,8 @@ def foo2() -> uint256:
 # See #838. Confirm that nested keys and structs work properly.
 def test_nested_map_key_works(get_contract_with_gas_estimation):
     code = """
-test_map1: {a: int128, b: int128 }[int128]
-test_map2: {c: int128, d: int128}[int128]
+test_map1: map(int128, {a: int128, b: int128 })
+test_map2: map(int128, {c: int128, d: int128})
 
 @public
 def set():
@@ -151,8 +151,8 @@ def get(i: int128) -> int128:
 
 def test_nested_map_key_problem(get_contract_with_gas_estimation):
     code = """
-test_map1: {a: int128, b: int128 }[int128]
-test_map2: {c: int128, d: int128}[int128]
+test_map1: map(int128, {a: int128, b: int128 })
+test_map2: map(int128, {c: int128, d: int128})
 
 @public
 def set():
diff --git a/tests/parser/features/test_bytes_map_keys.py b/tests/parser/features/test_bytes_map_keys.py
--- a/tests/parser/features/test_bytes_map_keys.py
+++ b/tests/parser/features/test_bytes_map_keys.py
@@ -4,7 +4,7 @@
 
 def test_basic_bytes_keys(w3, get_contract):
     code = """
-mapped_bytes: int128[bytes[5]]
+mapped_bytes: map(bytes[5], int128)
 
 @public
 def set(k: bytes[5], v: int128):
@@ -24,7 +24,7 @@ def get(k: bytes[5]) -> int128:
 
 def test_basic_bytes_literal_key(get_contract):
     code = """
-mapped_bytes: int128[bytes[5]]
+mapped_bytes: map(bytes[5], int128)
 
 @public
 def set(v: int128):
@@ -44,7 +44,7 @@ def get(k: bytes[5]) -> int128:
 
 def test_basic_long_bytes_as_keys(get_contract):
     code = """
-mapped_bytes: int128[bytes[34]]
+mapped_bytes: map(bytes[34], int128)
 
 @public
 def set(k: bytes[34], v: int128):
@@ -64,7 +64,7 @@ def get(k: bytes[34]) -> int128:
 
 def test_mismatched_byte_length(get_contract):
     code = """
-mapped_bytes: int128[bytes[34]]
+mapped_bytes: map(bytes[34], int128)
 
 @public
 def set(k: bytes[35], v: int128):
@@ -77,7 +77,7 @@ def set(k: bytes[35], v: int128):
 
 def test_extended_bytes_key_from_storage(get_contract):
     code = """
-a: int128[bytes[100000]]
+a: map(bytes[100000], int128)
 
 @public
 def __init__():
diff --git a/tests/parser/features/test_map_delete.py b/tests/parser/features/test_map_delete.py
--- a/tests/parser/features/test_map_delete.py
+++ b/tests/parser/features/test_map_delete.py
@@ -2,7 +2,7 @@
 
 def test_map_delete(get_contract_with_gas_estimation):
     code = """
-big_storage: bytes32[bytes32]
+big_storage: map(bytes32, bytes32)
 
 @public
 def set(key: bytes32, value: bytes32):
@@ -28,7 +28,7 @@ def delete(key: bytes32):
 
 def test_map_delete_nested(get_contract_with_gas_estimation):
     code = """
-big_storage: bytes32[bytes32][bytes32]
+big_storage: map(bytes32, map(bytes32, bytes32))
 
 @public
 def set(key1: bytes32, key2: bytes32, value: bytes32):
@@ -54,11 +54,10 @@ def delete(key1: bytes32, key2: bytes32):
 
 def test_map_delete_struct(get_contract_with_gas_estimation):
     code = """
-structmap: {
+structmap: map(int128, {
     a: int128,
     b: int128
-}[int128]
-
+})
 
 @public
 def set():
diff --git a/tests/parser/functions/test_create_with_code_of.py b/tests/parser/functions/test_create_with_code_of.py
--- a/tests/parser/functions/test_create_with_code_of.py
+++ b/tests/parser/functions/test_create_with_code_of.py
@@ -1,10 +1,3 @@
-import logging
-import sys
-
-logger = logging.getLogger('evm')
-handler = logging.StreamHandler(sys.stdout)
-logger.setLevel(logging.TRACE)
-handler.setLevel(logging.TRACE)
 
 
 def test_create_with_code_of_create(get_contract):
diff --git a/tests/parser/globals/test_getters.py b/tests/parser/globals/test_getters.py
--- a/tests/parser/globals/test_getters.py
+++ b/tests/parser/globals/test_getters.py
@@ -1,6 +1,6 @@
 def test_state_accessor(get_contract_with_gas_estimation_for_constants):
     state_accessor = """
-y: int128[int128]
+y: map(int128, int128)
 
 @public
 def oo():
@@ -23,15 +23,15 @@ def test_getter_code(get_contract_with_gas_estimation_for_constants):
 x: public(wei_value)
 y: public(int128[5])
 z: public(bytes[100])
-w: public({
+w: public(map(int128, {
     a: wei_value,
     b: int128[7],
     c: bytes[100],
-    d: int128[address],
+    d: map(address, int128),
     e: int128[3][3],
     f: timestamp,
     g: wei_value
-}[int128])
+}))
 
 @public
 def __init__():
diff --git a/tests/parser/integration/test_crowdfund.py b/tests/parser/integration/test_crowdfund.py
--- a/tests/parser/integration/test_crowdfund.py
+++ b/tests/parser/integration/test_crowdfund.py
@@ -1,7 +1,7 @@
 def test_crowdfund(w3, tester, get_contract_with_gas_estimation_for_constants):
     crowdfund = """
 
-funders: {sender: address, value: wei_value}[int128]
+funders: map(int128, {sender: address, value: wei_value})
 nextFunderIndex: int128
 beneficiary: address
 deadline: public(timestamp)
@@ -93,7 +93,7 @@ def refund():
 def test_crowdfund2(w3, tester, get_contract_with_gas_estimation_for_constants):
     crowdfund2 = """
 
-funders: {sender: address, value: wei_value}[int128]
+funders: map(int128, {sender: address, value: wei_value})
 nextFunderIndex: int128
 beneficiary: address
 deadline: public(timestamp)
diff --git a/tests/parser/syntax/test_block.py b/tests/parser/syntax/test_block.py
--- a/tests/parser/syntax/test_block.py
+++ b/tests/parser/syntax/test_block.py
@@ -46,14 +46,14 @@ def foo():
     y = min(block.timestamp + 30 - block.timestamp, block.timestamp)
     """,
     """
-a: int128[timestamp]
+a: map(timestamp, int128)
 
 @public
 def add_record():
     self.a[block.timestamp] = block.timestamp + 20
     """,
     """
-a: timestamp[int128]
+a: map(int128, timestamp)
 
 @public
 def add_record():
@@ -97,8 +97,7 @@ def test_block_fail(bad_code):
 
 valid_list = [
     """
-a: timestamp[timestamp]
-
+a: map(timestamp, timestamp)
 
 @public
 def add_record():
diff --git a/tests/parser/syntax/test_invalids.py b/tests/parser/syntax/test_invalids.py
--- a/tests/parser/syntax/test_invalids.py
+++ b/tests/parser/syntax/test_invalids.py
@@ -105,42 +105,42 @@ def foo():
 """, TypeMismatchException)
 
 must_succeed("""
-b: int128[int128]
+b: map(int128, int128)
 @public
 def foo():
     x: int128 = self.b[5]
 """)
 
 must_fail("""
-b: int128[int128]
+b: map(int128, int128)
 @public
 def foo():
     x: int128 = self.b[5.7]
 """, TypeMismatchException)
 
 must_succeed("""
-b: int128[decimal]
+b: map(decimal, int128)
 @public
 def foo():
     x: int128 = self.b[5]
 """)
 
 must_fail("""
-b: int128[int128]
+b: map(int128, int128)
 @public
 def foo():
     self.b[3] = 5.6
 """, TypeMismatchException)
 
 must_succeed("""
-b: int128[int128]
+b: map(int128, int128)
 @public
 def foo():
     self.b[3] = -5
 """)
 
 must_succeed("""
-b: int128[int128]
+b: map(int128, int128)
 @public
 def foo():
     self.b[-3] = 5
diff --git a/tests/parser/syntax/test_maps.py b/tests/parser/syntax/test_maps.py
--- a/tests/parser/syntax/test_maps.py
+++ b/tests/parser/syntax/test_maps.py
@@ -70,13 +70,13 @@ def foo():
     self.mom = {a: self.nom, b: self.nom}
     """,
     """
-nom: {a: {c: int128}[int128], b: int128}
+nom: {a: map(int128, {c: int128}), b: int128}
 @public
 def foo():
     self.nom = None
     """,
     """
-nom: {a: {c: int128}[int128], b: int128}
+nom: {a: map(int128, {c: int128}), b: int128}
 @public
 def foo():
     self.nom = {a: [{c: 5}], b: 7}
@@ -147,7 +147,7 @@ def foo():
     self.nom = self.mom.a
     """,
     """
-nom: {a: {c: int128}[int128], b: int128}
+nom: {a: map(int128, {c: int128}), b: int128}
 @public
 def foo():
     self.nom.a[135] = {c: 6}
diff --git a/tests/parser/types/test_node_types.py b/tests/parser/types/test_node_types.py
--- a/tests/parser/types/test_node_types.py
+++ b/tests/parser/types/test_node_types.py
@@ -42,7 +42,7 @@ def test_mapping_node_types():
     node1 = MappingType(BaseType('int128'), BaseType('int128'))
     node2 = MappingType(BaseType('int128'), BaseType('int128'))
     assert node1 == node2
-    assert str(node1) == "int128[int128]"
+    assert str(node1) == "map(int128, int128)"
 
 
 def test_tuple_node_types():