From 8de8950676a383e28627513c48398ced86ff68b4 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 26 Mar 2024 10:24:22 -0400 Subject: [PATCH] fix[lang]: transitive exports (#3888) this commit fixes transitive exports, i.e. where `module1` exports `module2.foo`, but it was previously not exportable from `module1`. this is because previously exported functions were not added to the `ModuleT`'s `exposed_functions` list. a test had previously been written for this case, but it had been marked xfail since the desired behavior was not clear until user feedback was received. this commit removes the `xfail` marker from that test case. --- tests/functional/codegen/modules/test_exports.py | 7 +------ vyper/exceptions.py | 3 ++- vyper/semantics/analysis/module.py | 2 +- vyper/semantics/types/module.py | 4 ++++ 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/functional/codegen/modules/test_exports.py b/tests/functional/codegen/modules/test_exports.py index 042d231211..2dc90bfe74 100644 --- a/tests/functional/codegen/modules/test_exports.py +++ b/tests/functional/codegen/modules/test_exports.py @@ -1,6 +1,3 @@ -import pytest - - def test_simple_export(make_input_bundle, get_contract): lib1 = """ @external @@ -131,9 +128,7 @@ def foo() -> uint256: assert c.foo() == 5 -# not sure if this one should work -@pytest.mark.xfail(reason="ambiguous spec") -def test_recursive_export(make_input_bundle, get_contract): +def test_transitive_export(make_input_bundle, get_contract): lib1 = """ @external def foo() -> uint256: diff --git a/vyper/exceptions.py b/vyper/exceptions.py index ced249f247..3897f0ea41 100644 --- a/vyper/exceptions.py +++ b/vyper/exceptions.py @@ -127,8 +127,9 @@ def format_annotation(self, value): return None if isinstance(node, vy_ast.VyperNode): - module_node = node.get_ancestor(vy_ast.Module) + module_node = node.module_node + # TODO: handle cases where module is None or vy_ast.Module if module_node.get("path") not in (None, ""): node_msg = f'{node_msg}contract "{module_node.path}:{node.lineno}", ' diff --git a/vyper/semantics/analysis/module.py b/vyper/semantics/analysis/module.py index f4b7db129f..b4f4381444 100644 --- a/vyper/semantics/analysis/module.py +++ b/vyper/semantics/analysis/module.py @@ -524,7 +524,7 @@ def visit_ExportsDecl(self, node): decl_node = func_t.decl_node if not isinstance(func_t, ContractFunctionT): - raise StructureException("not a function!", decl_node, item) + raise StructureException(f"not a function: `{func_t}`", decl_node, item) if not func_t.is_external: raise StructureException("can't export non-external functions!", decl_node, item) diff --git a/vyper/semantics/types/module.py b/vyper/semantics/types/module.py index 0d2b343e0d..4557fc9612 100644 --- a/vyper/semantics/types/module.py +++ b/vyper/semantics/types/module.py @@ -296,6 +296,10 @@ def __init__(self, module: vy_ast.Module, name: Optional[str] = None): # note: this checks for collisions self.add_member(f.name, f._metadata["func_type"]) + for item in self.exports_decls: + for fn_t in item._metadata["exports_info"].functions: + self.add_member(fn_t.name, fn_t) + for e in self.event_defs: # add the type of the event so it can be used in call position self.add_member(e.name, TYPE_T(e._metadata["event_type"])) # type: ignore