diff --git a/tests/functional/syntax/modules/test_exports.py b/tests/functional/syntax/modules/test_exports.py index 4314c1bbf0..c7937e3c86 100644 --- a/tests/functional/syntax/modules/test_exports.py +++ b/tests/functional/syntax/modules/test_exports.py @@ -202,10 +202,9 @@ def foo(): """ input_bundle = make_input_bundle({"lib1.vy": lib1}) with pytest.raises(NamespaceCollision) as e: - # TODO: make the error message reference the export compile_code(main, contract_path="main.vy", input_bundle=input_bundle) - assert e.value._message == "Member 'foo' already exists in self" + assert e.value._message == "Member 'foo' already exists in self (when exporting `lib1.foo`)" assert e.value.annotations[0].lineno == 4 assert e.value.annotations[0].node_source_code == "lib1.foo" @@ -235,7 +234,6 @@ def bar(): """ input_bundle = make_input_bundle({"lib1.vy": lib1}) with pytest.raises(StructureException) as e: - # TODO: make the error message reference the export compile_code(main, contract_path="main.vy", input_bundle=input_bundle) assert e.value._message == "already exported!" @@ -266,7 +264,6 @@ def bar(): """ input_bundle = make_input_bundle({"lib1.vy": lib1}) with pytest.raises(StructureException) as e: - # TODO: make the error message reference the export compile_code(main, contract_path="main.vy", input_bundle=input_bundle) assert e.value._message == "already exported!" @@ -300,7 +297,6 @@ def bar(): """ input_bundle = make_input_bundle({"lib1.vy": lib1}) with pytest.raises(StructureException) as e: - # TODO: make the error message reference the export compile_code(main, contract_path="main.vy", input_bundle=input_bundle) assert e.value._message == "already exported!" diff --git a/vyper/semantics/analysis/module.py b/vyper/semantics/analysis/module.py index 3fe847dd35..1782058c7f 100644 --- a/vyper/semantics/analysis/module.py +++ b/vyper/semantics/analysis/module.py @@ -13,6 +13,7 @@ InterfaceViolation, InvalidLiteral, InvalidType, + NamespaceCollision, StateAccessViolation, StructureException, UndeclaredDefinition, @@ -565,7 +566,8 @@ def visit_ExportsDecl(self, node): self._add_exposed_function(func_t, item, relax=False) with tag_exceptions(item): # tag exceptions with specific item - self._self_t.typ.add_member(func_t.name, func_t) + export_name = item.node_source_code + self._self_t.typ.add_member_with_export_context(func_t.name, func_t, export_name) exported_funcs.append(func_t) @@ -588,7 +590,8 @@ def _add_exposed_function(self, func_t, node, relax=True): # call this before self._self_t.typ.add_member() for exception raising # priority if not relax and (prev_decl := self._all_functions.get(func_t)) is not None: - raise StructureException("already exported!", node, prev_decl=prev_decl) + export_name = node.node_source_code + raise StructureException(f"export `{export_name}` already exported!", node, prev_decl=prev_decl) self._all_functions[func_t] = node diff --git a/vyper/semantics/types/module.py b/vyper/semantics/types/module.py index 7eecf3a9b0..9e76c7d6df 100644 --- a/vyper/semantics/types/module.py +++ b/vyper/semantics/types/module.py @@ -366,6 +366,21 @@ def __init__(self, module: vy_ast.Module, name: Optional[str] = None): # allow `module.__interface__` (in type position) self._helper.add_member("__interface__", TYPE_T(self.interface)) + def _check_add_member(self, name, export_context=None): + if (prev_type := self.members.get(name)) is not None: + if export_context: + msg = f"Member '{name}' already exists in {self} (when exporting `{export_context}`)" + else: + msg = f"Member '{name}' already exists in {self}" + raise NamespaceCollision(msg, prev_decl=prev_type.decl_node) + + def add_member_with_export_context(self, name: str, type_: "VyperType", export_context: str = None) -> None: + """Add a member with optional export context for better error messages.""" + from vyper.ast.identifiers import validate_identifier + validate_identifier(name) + self._check_add_member(name, export_context) + self.members[name] = type_ + # __eq__ is very strict on ModuleT - object equality! this is because we # don't want to reason about where a module came from (i.e. input bundle, # search path, symlinked vs normalized path, etc.)