Skip to content
Open
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
6 changes: 1 addition & 5 deletions tests/functional/syntax/modules/test_exports.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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!"
Expand Down Expand Up @@ -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!"
Expand Down Expand Up @@ -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!"
Expand Down
7 changes: 5 additions & 2 deletions vyper/semantics/analysis/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
InterfaceViolation,
InvalidLiteral,
InvalidType,
NamespaceCollision,
StateAccessViolation,
StructureException,
UndeclaredDefinition,
Expand Down Expand Up @@ -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)

Expand All @@ -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

Expand Down
15 changes: 15 additions & 0 deletions vyper/semantics/types/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.)
Expand Down