From d35248e6faa3edf21c3e4eccc206127611795529 Mon Sep 17 00:00:00 2001 From: Ben Hauser Date: Thu, 25 Jun 2020 18:11:02 +0400 Subject: [PATCH 1/2] refactor: remove `enter_builtin_scope` from namespace --- vyper/context/README.md | 7 +--- vyper/context/namespace.py | 61 ++++++++++------------------ vyper/context/validation/__init__.py | 2 +- 3 files changed, 23 insertions(+), 47 deletions(-) diff --git a/vyper/context/README.md b/vyper/context/README.md index 9501432744..ae2940f3a5 100644 --- a/vyper/context/README.md +++ b/vyper/context/README.md @@ -49,8 +49,7 @@ consists of three steps: ### 1. Preparing the builtin namespace The [`Namespace`](namespace.py) object represents the namespace for a contract. -Prior to beginning type checking, builtins are added via the `Namespace.enter_builtin_scope` -method. This includes: +Builtins are added upon initialization of the object. This includes: * Adding primitive type classes from the [`types/`](types) subpackage * Adding environment variables and builtin constants from [`environment.py`](environment.py) @@ -188,10 +187,6 @@ with namespace.enter_scope(): namespace['foo'] # this raises an UndeclaredDefinition ``` -Prior to beginning type checking, the first scope **must** be initiated using -`Namespace.enter_builtin_scope`. This ensures that all builtin objects have -been added, and resets the content of `self` and `log`. - ### Validation Validation is handled by calling methods within each type object. In general: diff --git a/vyper/context/namespace.py b/vyper/context/namespace.py index 7d76f441b6..08503bf40b 100644 --- a/vyper/context/namespace.py +++ b/vyper/context/namespace.py @@ -13,14 +13,18 @@ class Namespace(dict): ---------- _scopes : List[Set] List of sets containing the key names for each scope - _has_builtins: bool - Boolean indicating if constant environment variables have been added """ def __init__(self): super().__init__() self._scopes = [] - self._has_builtins = False + from vyper.context import environment + from vyper.context.types import get_types + from vyper.functions.functions import get_builtin_functions + + self.update(get_types()) + self.update(environment.get_constant_vars()) + self.update(get_builtin_functions()) def __eq__(self, other): return self is other @@ -49,33 +53,6 @@ def __exit__(self, exc_type, exc_value, traceback): for key in self._scopes.pop(): del self[key] - def enter_builtin_scope(self): - """ - Add types and builtin values to the namespace. - - Called as a context manager, e.g. `with namespace.enter_builtin_scope():` - It must be the first scope that is entered prior to type checking. - """ - # prevent circular import issues - from vyper.context import environment - from vyper.context.types import get_types - from vyper.functions.functions import get_builtin_functions - - if self._scopes: - raise CompilerPanic("Namespace has a currently active scope") - - if not self._has_builtins: - # constant builtins are only added once - self.update(get_types()) - self.update(environment.get_constant_vars()) - self.update(get_builtin_functions()) - self._has_builtins = True - - # mutable builtins are always added - self._scopes.append(set()) - self.update(environment.get_mutable_vars()) - return self - def enter_scope(self): """ Enter a new scope within the namespace. @@ -83,9 +60,14 @@ def enter_scope(self): Called as a context manager, e.g. `with namespace.enter_scope():` All items that are added within the context are removed upon exit. """ - if not self._scopes: - raise CompilerPanic("First scope must be entered via `enter_builtin_scope`") + from vyper.context import environment + self._scopes.append(set()) + + if len(self._scopes) == 1: + # add mutable vars (`self`) to the initial scope + self.update(environment.get_mutable_vars()) + return self def update(self, other): @@ -94,17 +76,16 @@ def update(self, other): def clear(self): super().clear() - self._scopes = [] - self._has_builtins = False - - -_namespace = Namespace() + self.__init__() def get_namespace(): """ Get the active namespace object. - - This method should always be used over directly importing `_namespace`. """ - return _namespace + global _namespace + try: + return _namespace + except NameError: + _namespace = Namespace() + return _namespace diff --git a/vyper/context/validation/__init__.py b/vyper/context/validation/__init__.py index 484960cb92..f87eecbbd2 100644 --- a/vyper/context/validation/__init__.py +++ b/vyper/context/validation/__init__.py @@ -6,6 +6,6 @@ def validate_semantics(vyper_ast, interface_codes): namespace = get_namespace() - with namespace.enter_builtin_scope(): + with namespace.enter_scope(): add_module_namespace(vyper_ast, interface_codes) validate_functions(vyper_ast) From e9a66654d6f301b034d7c28ada122a7fc552b48d Mon Sep 17 00:00:00 2001 From: Ben Hauser Date: Thu, 25 Jun 2020 18:11:51 +0400 Subject: [PATCH 2/2] test: update references to namespace in tests --- tests/functional/context/test_namespace.py | 54 ++++++------------- .../context/types/test_type_from_abi.py | 22 ++++---- .../types/test_type_from_annotation.py | 42 ++++++--------- .../validation/test_cyclic_function_calls.py | 4 +- .../validation/test_potential_types.py | 30 +++++------ 5 files changed, 60 insertions(+), 92 deletions(-) diff --git a/tests/functional/context/test_namespace.py b/tests/functional/context/test_namespace.py index adbb0878bd..35778451b3 100644 --- a/tests/functional/context/test_namespace.py +++ b/tests/functional/context/test_namespace.py @@ -10,40 +10,34 @@ ) -def test_get_namespace(namespace): +def test_get_namespace(): ns = get_namespace() ns2 = get_namespace() - assert ns == ns2 == namespace - - -def test_clear(namespace): - namespace["foo"] = 42 - assert "foo" in namespace - - namespace.clear() - assert "foo" not in namespace + assert ns == ns2 def test_builtin_context_manager(namespace): namespace["foo"] = 42 - with namespace.enter_builtin_scope(): + with namespace.enter_scope(): namespace["bar"] = 1337 assert namespace["foo"] == 42 assert "bar" not in namespace -def test_builtin_context_manager_types(namespace): - with namespace.enter_builtin_scope(): - for key, value in get_types().items(): - assert namespace[key] == value +def test_builtin_types(namespace): + for key, value in get_types().items(): + assert namespace[key] == value + +def test_builtin_types_persist_after_clear(namespace): + namespace.clear() for key, value in get_types().items(): assert namespace[key] == value -def test_builtin_context_manager_conamespacetant_vars(namespace): - with namespace.enter_builtin_scope(): +def test_context_manager_constant_vars(namespace): + with namespace.enter_scope(): for key in environment.CONSTANT_ENVIRONMENT_VARS.keys(): assert key in namespace @@ -51,8 +45,8 @@ def test_builtin_context_manager_conamespacetant_vars(namespace): assert key in namespace -def test_builtin_context_manager_mutable_vars(namespace): - with namespace.enter_builtin_scope(): +def test_context_manager_mutable_vars(namespace): + with namespace.enter_scope(): for key in environment.MUTABLE_ENVIRONMENT_VARS.keys(): assert key in namespace @@ -60,16 +54,8 @@ def test_builtin_context_manager_mutable_vars(namespace): assert key not in namespace -def test_builtin_context_manager_wrong_sequence(namespace): - with namespace.enter_builtin_scope(): - # fails because builtin scope may only be entered once - with pytest.raises(CompilerPanic): - with namespace.enter_builtin_scope(): - pass - - def test_context_manager(namespace): - with namespace.enter_builtin_scope(): + with namespace.enter_scope(): namespace["foo"] = 42 with namespace.enter_scope(): namespace["bar"] = 1337 @@ -80,13 +66,7 @@ def test_context_manager(namespace): assert "foo" not in namespace -def test_context_manager_wrong_sequence(namespace): - with pytest.raises(CompilerPanic): - with namespace.enter_scope(): - pass - - -def test_incorrect_context_invokation(namespace): +def test_incorrect_context_invocation(namespace): with pytest.raises(CompilerPanic): with namespace: pass @@ -99,7 +79,7 @@ def test_namespace_collision(namespace): def test_namespace_collision_across_scopes(namespace): - with namespace.enter_builtin_scope(): + with namespace.enter_scope(): namespace["foo"] = 42 with namespace.enter_scope(): with pytest.raises(NamespaceCollision): @@ -112,7 +92,7 @@ def test_undeclared_definition(namespace): def test_undeclared_definition_across_scopes(namespace): - with namespace.enter_builtin_scope(): + with namespace.enter_scope(): with namespace.enter_scope(): namespace["foo"] = 42 with pytest.raises(UndeclaredDefinition): diff --git a/tests/functional/context/types/test_type_from_abi.py b/tests/functional/context/types/test_type_from_abi.py index b16b9f16d2..81498d9936 100644 --- a/tests/functional/context/types/test_type_from_abi.py +++ b/tests/functional/context/types/test_type_from_abi.py @@ -9,21 +9,19 @@ @pytest.mark.parametrize("type_str", BASE_TYPES) -def test_base_types(namespace, type_str): +def test_base_types(type_str): primitive = get_primitive_types()[type_str] - with namespace.enter_builtin_scope(): - type_definition = get_type_from_abi({"type": type_str}) + type_definition = get_type_from_abi({"type": type_str}) assert isinstance(type_definition, primitive._type) @pytest.mark.parametrize("type_str", BASE_TYPES) -def test_base_types_as_arrays(namespace, type_str): +def test_base_types_as_arrays(type_str): primitive = get_primitive_types()[type_str] - with namespace.enter_builtin_scope(): - type_definition = get_type_from_abi({"type": f"{type_str}[3]"}) + type_definition = get_type_from_abi({"type": f"{type_str}[3]"}) assert isinstance(type_definition, ArrayDefinition) assert type_definition.length == 3 @@ -31,11 +29,10 @@ def test_base_types_as_arrays(namespace, type_str): @pytest.mark.parametrize("type_str", BASE_TYPES) -def test_base_types_as_multidimensional_arrays(namespace, type_str): +def test_base_types_as_multidimensional_arrays(type_str): primitive = get_primitive_types()[type_str] - with namespace.enter_builtin_scope(): - type_definition = get_type_from_abi({"type": f"{type_str}[3][5]"}) + type_definition = get_type_from_abi({"type": f"{type_str}[3][5]"}) assert isinstance(type_definition, ArrayDefinition) assert type_definition.length == 5 @@ -45,7 +42,6 @@ def test_base_types_as_multidimensional_arrays(namespace, type_str): @pytest.mark.parametrize("idx", ["0", "-1", "0x00", "'1'", "foo", "[1]", "(1,)"]) -def test_invalid_index(namespace, idx): - with namespace.enter_builtin_scope(): - with pytest.raises(UnknownType): - get_type_from_abi({"type": f"int128[{idx}]"}) +def test_invalid_index(idx): + with pytest.raises(UnknownType): + get_type_from_abi({"type": f"int128[{idx}]"}) diff --git a/tests/functional/context/types/test_type_from_annotation.py b/tests/functional/context/types/test_type_from_annotation.py index 85a8dd58c6..03daa91030 100644 --- a/tests/functional/context/types/test_type_from_annotation.py +++ b/tests/functional/context/types/test_type_from_annotation.py @@ -16,34 +16,31 @@ @pytest.mark.parametrize("type_str", BASE_TYPES) -def test_base_types(build_node, namespace, type_str): +def test_base_types(build_node, type_str): node = build_node(type_str) primitive = get_primitive_types()[type_str] - with namespace.enter_builtin_scope(): - type_definition = get_type_from_annotation(node, DataLocation.STORAGE) + type_definition = get_type_from_annotation(node, DataLocation.STORAGE) assert isinstance(type_definition, primitive._type) @pytest.mark.parametrize("type_str", ARRAY_VALUE_TYPES) -def test_array_value_types(build_node, namespace, type_str): +def test_array_value_types(build_node, type_str): node = build_node(f"{type_str}[1]") primitive = get_primitive_types()[type_str] - with namespace.enter_builtin_scope(): - type_definition = get_type_from_annotation(node, DataLocation.STORAGE) + type_definition = get_type_from_annotation(node, DataLocation.STORAGE) assert isinstance(type_definition, primitive._type) @pytest.mark.parametrize("type_str", BASE_TYPES) -def test_base_types_as_arrays(build_node, namespace, type_str): +def test_base_types_as_arrays(build_node, type_str): node = build_node(f"{type_str}[3]") primitive = get_primitive_types()[type_str] - with namespace.enter_builtin_scope(): - type_definition = get_type_from_annotation(node, DataLocation.STORAGE) + type_definition = get_type_from_annotation(node, DataLocation.STORAGE) assert isinstance(type_definition, ArrayDefinition) assert type_definition.length == 3 @@ -51,12 +48,11 @@ def test_base_types_as_arrays(build_node, namespace, type_str): @pytest.mark.parametrize("type_str", ARRAY_VALUE_TYPES) -def test_array_value_types_as_arrays(build_node, namespace, type_str): +def test_array_value_types_as_arrays(build_node, type_str): node = build_node(f"{type_str}[1][1]") - with namespace.enter_builtin_scope(): - with pytest.raises(StructureException): - get_type_from_annotation(node, DataLocation.STORAGE) + with pytest.raises(StructureException): + get_type_from_annotation(node, DataLocation.STORAGE) @pytest.mark.parametrize("type_str", BASE_TYPES) @@ -64,8 +60,7 @@ def test_base_types_as_multidimensional_arrays(build_node, namespace, type_str): node = build_node(f"{type_str}[3][5]") primitive = get_primitive_types()[type_str] - with namespace.enter_builtin_scope(): - type_definition = get_type_from_annotation(node, DataLocation.STORAGE) + type_definition = get_type_from_annotation(node, DataLocation.STORAGE) assert isinstance(type_definition, ArrayDefinition) assert type_definition.length == 5 @@ -76,21 +71,19 @@ def test_base_types_as_multidimensional_arrays(build_node, namespace, type_str): @pytest.mark.parametrize("type_str", ["int128", "string"]) @pytest.mark.parametrize("idx", ["0", "-1", "0x00", "'1'", "foo", "[1]", "(1,)"]) -def test_invalid_index(build_node, namespace, idx, type_str): +def test_invalid_index(build_node, idx, type_str): node = build_node(f"{type_str}[{idx}]") - with namespace.enter_builtin_scope(): - with pytest.raises((ArrayIndexException, InvalidType)): - get_type_from_annotation(node, DataLocation.STORAGE) + with pytest.raises((ArrayIndexException, InvalidType)): + get_type_from_annotation(node, DataLocation.STORAGE) @pytest.mark.parametrize("type_str", BASE_TYPES) @pytest.mark.parametrize("type_str2", BASE_TYPES) -def test_mapping(build_node, namespace, type_str, type_str2): +def test_mapping(build_node, type_str, type_str2): node = build_node(f"map({type_str}, {type_str2})") primitives = get_primitive_types() - with namespace.enter_builtin_scope(): - type_definition = get_type_from_annotation(node, DataLocation.STORAGE) + type_definition = get_type_from_annotation(node, DataLocation.STORAGE) assert isinstance(type_definition, MappingDefinition) assert isinstance(type_definition.key_type, primitives[type_str]._type) @@ -99,12 +92,11 @@ def test_mapping(build_node, namespace, type_str, type_str2): @pytest.mark.parametrize("type_str", BASE_TYPES) @pytest.mark.parametrize("type_str2", BASE_TYPES) -def test_multidimensional_mapping(build_node, namespace, type_str, type_str2): +def test_multidimensional_mapping(build_node, type_str, type_str2): node = build_node(f"map({type_str}, map({type_str}, {type_str2}))") primitives = get_primitive_types() - with namespace.enter_builtin_scope(): - type_definition = get_type_from_annotation(node, DataLocation.STORAGE) + type_definition = get_type_from_annotation(node, DataLocation.STORAGE) assert isinstance(type_definition, MappingDefinition) assert isinstance(type_definition.key_type, primitives[type_str]._type) diff --git a/tests/functional/context/validation/test_cyclic_function_calls.py b/tests/functional/context/validation/test_cyclic_function_calls.py index a79f2bc62e..99f3f0c0a1 100644 --- a/tests/functional/context/validation/test_cyclic_function_calls.py +++ b/tests/functional/context/validation/test_cyclic_function_calls.py @@ -16,7 +16,7 @@ def bar(): self.foo() """ vyper_module = parse_to_ast(code) - with namespace.enter_builtin_scope(): + with namespace.enter_scope(): with pytest.raises(CallViolation): ModuleNodeVisitor(vyper_module, {}, namespace) @@ -40,6 +40,6 @@ def potato(): self.foo() """ vyper_module = parse_to_ast(code) - with namespace.enter_builtin_scope(): + with namespace.enter_scope(): with pytest.raises(CallViolation): ModuleNodeVisitor(vyper_module, {}, namespace) diff --git a/tests/functional/context/validation/test_potential_types.py b/tests/functional/context/validation/test_potential_types.py index daa4f0c2fb..b48c3996de 100644 --- a/tests/functional/context/validation/test_potential_types.py +++ b/tests/functional/context/validation/test_potential_types.py @@ -24,14 +24,14 @@ def test_attribute(build_node, namespace): node = build_node("self.foo") type_def = Int128Definition() - with namespace.enter_builtin_scope(): + with namespace.enter_scope(): namespace["self"].add_member("foo", type_def) assert get_possible_types_from_node(node) == [type_def] def test_attribute_missing_self(build_node, namespace): node = build_node("foo") - with namespace.enter_builtin_scope(): + with namespace.enter_scope(): namespace["self"].add_member("foo", Int128Definition()) with pytest.raises(InvalidReference): get_possible_types_from_node(node) @@ -39,7 +39,7 @@ def test_attribute_missing_self(build_node, namespace): def test_attribute_not_in_self(build_node, namespace): node = build_node("self.foo") - with namespace.enter_builtin_scope(): + with namespace.enter_scope(): namespace["foo"] = Int128Definition() with pytest.raises(InvalidReference): get_possible_types_from_node(node) @@ -47,7 +47,7 @@ def test_attribute_not_in_self(build_node, namespace): def test_attribute_unknown(build_node, namespace): node = build_node("foo.bar") - with namespace.enter_builtin_scope(): + with namespace.enter_scope(): namespace["foo"] = AddressDefinition() with pytest.raises(UnknownAttribute): get_possible_types_from_node(node) @@ -55,7 +55,7 @@ def test_attribute_unknown(build_node, namespace): def test_attribute_not_member_type(build_node, namespace): node = build_node("foo.bar") - with namespace.enter_builtin_scope(): + with namespace.enter_scope(): namespace["foo"] = Int128Definition() with pytest.raises(StructureException): get_possible_types_from_node(node) @@ -65,7 +65,7 @@ def test_attribute_not_member_type(build_node, namespace): @pytest.mark.parametrize("left,right", INTEGER_LITERALS + DECIMAL_LITERALS) def test_binop(build_node, namespace, op, left, right): node = build_node(f"{left}{op}{right}") - with namespace.enter_builtin_scope(): + with namespace.enter_scope(): get_possible_types_from_node(node) @@ -73,14 +73,14 @@ def test_binop(build_node, namespace, op, left, right): @pytest.mark.parametrize("left,right", [(42, "2.3"), (-1, 2 ** 128)]) def test_binop_type_mismatch(build_node, namespace, op, left, right): node = build_node(f"{left}{op}{right}") - with namespace.enter_builtin_scope(): + with namespace.enter_scope(): with pytest.raises(TypeMismatch): get_possible_types_from_node(node) def test_binop_invalid_decimal_pow(build_node, namespace): node = build_node("2.1 ** 2.1") - with namespace.enter_builtin_scope(): + with namespace.enter_scope(): with pytest.raises(InvalidOperation): get_possible_types_from_node(node) @@ -89,7 +89,7 @@ def test_binop_invalid_decimal_pow(build_node, namespace): @pytest.mark.parametrize("op", "+-*/%") def test_binop_invalid_op(build_node, namespace, op, left, right): node = build_node(f"{left} {op} {right}") - with namespace.enter_builtin_scope(): + with namespace.enter_scope(): with pytest.raises(InvalidOperation): get_possible_types_from_node(node) @@ -98,7 +98,7 @@ def test_binop_invalid_op(build_node, namespace, op, left, right): @pytest.mark.parametrize("op", ["and", "or"]) def test_boolop(build_node, namespace, op, left, right): node = build_node(f"{left} {op} {right}") - with namespace.enter_builtin_scope(): + with namespace.enter_scope(): types_list = get_possible_types_from_node(node) assert len(types_list) == 1 @@ -109,7 +109,7 @@ def test_boolop(build_node, namespace, op, left, right): @pytest.mark.parametrize("op", ["and", "or"]) def test_boolop_invalid_op(build_node, namespace, op, left, right): node = build_node(f"{left} {op} {right}") - with namespace.enter_builtin_scope(): + with namespace.enter_scope(): with pytest.raises(InvalidOperation): get_possible_types_from_node(node) @@ -118,7 +118,7 @@ def test_boolop_invalid_op(build_node, namespace, op, left, right): @pytest.mark.parametrize("op", ["<", "<=", ">", ">="]) def test_compare_lt_gt(build_node, namespace, op, left, right): node = build_node(f"{left} {op} {right}") - with namespace.enter_builtin_scope(): + with namespace.enter_scope(): types_list = get_possible_types_from_node(node) assert len(types_list) == 1 @@ -131,7 +131,7 @@ def test_compare_lt_gt(build_node, namespace, op, left, right): @pytest.mark.parametrize("op", ["==", "!="]) def test_compare_eq_ne(build_node, namespace, op, left, right): node = build_node(f"{left} {op} {right}") - with namespace.enter_builtin_scope(): + with namespace.enter_scope(): types_list = get_possible_types_from_node(node) assert len(types_list) == 1 @@ -142,7 +142,7 @@ def test_compare_eq_ne(build_node, namespace, op, left, right): @pytest.mark.parametrize("op", ["<", "<=", ">", ">="]) def test_compare_invalid_op(build_node, namespace, op, left, right): node = build_node(f"{left} {op} {right}") - with namespace.enter_builtin_scope(): + with namespace.enter_scope(): with pytest.raises(InvalidOperation): get_possible_types_from_node(node) @@ -165,7 +165,7 @@ def test_name_unknown(build_node, namespace): def test_list(build_node, namespace, left, right): node = build_node(f"[{left}, {right}]") - with namespace.enter_builtin_scope(): + with namespace.enter_scope(): types_list = get_possible_types_from_node(node) assert types_list