Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat[ux]: improve error message for importing ERC20 #3816

Merged
merged 3 commits into from
Feb 26, 2024
Merged
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
23 changes: 23 additions & 0 deletions tests/functional/syntax/test_interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
InterfaceViolation,
InvalidReference,
InvalidType,
ModuleNotFound,
NamespaceCollision,
StructureException,
SyntaxException,
Expand Down Expand Up @@ -398,3 +399,25 @@ def foobar():
"""

assert compiler.compile_code(code, input_bundle=input_bundle) is not None


def test_builtins_not_found():
code = """
from vyper.interfaces import foobar
"""
with pytest.raises(ModuleNotFound) as e:
compiler.compile_code(code)

assert e.value._message == "vyper.interfaces.foobar"
assert e.value._hint == "try renaming `vyper.interfaces` to `ethereum.ercs`"


@pytest.mark.parametrize("erc", ("ERC20", "ERC721", "ERC4626"))
def test_builtins_not_found2(erc):
code = f"""
from ethereum.ercs import {erc}
"""
with pytest.raises(ModuleNotFound) as e:
compiler.compile_code(code)
assert e.value._message == f"ethereum.ercs.{erc}"
assert e.value._hint == f"try renaming `{erc}` to `I{erc}`"
28 changes: 15 additions & 13 deletions vyper/semantics/analysis/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -724,16 +724,7 @@ def _load_import(self, node: vy_ast.VyperNode, level: int, module_str: str, alia
def _load_import_helper(
self, node: vy_ast.VyperNode, level: int, module_str: str, alias: str
) -> Any:
if module_str.startswith("vyper.interfaces"):
hint = "try renaming `vyper.interfaces` to `ethereum.ercs`"
raise ModuleNotFound(module_str, hint=hint)
if _is_builtin(module_str):
components = module_str.split(".")
# hint: rename ERC20 to IERC20
if components[-1].startswith("ERC"):
module_prefix = components[-1]
hint = f"try renaming `{module_prefix}` to `I{module_prefix}`"
raise ModuleNotFound(module_str, hint=hint)
return _load_builtin_import(level, module_str)

path = _import_to_path(level, module_str)
Expand Down Expand Up @@ -795,9 +786,13 @@ def _load_import_helper(
except FileNotFoundError:
pass

hint = None
if module_str.startswith("vyper.interfaces"):
hint = "try renaming `vyper.interfaces` to `ethereum.ercs`"

# copy search_paths, makes debugging a bit easier
search_paths = self.input_bundle.search_paths.copy() # noqa: F841
raise ModuleNotFound(module_str, node) from err
raise ModuleNotFound(module_str, hint=hint) from err


def _parse_and_fold_ast(file: FileInput) -> vy_ast.Module:
Expand Down Expand Up @@ -842,7 +837,7 @@ def _is_builtin(module_str):

def _load_builtin_import(level: int, module_str: str) -> InterfaceT:
if not _is_builtin(module_str):
raise ModuleNotFoundError(f"Not a builtin: {module_str}")
raise ModuleNotFound(module_str)

builtins_path = vyper.builtins.interfaces.__path__[0]
# hygiene: convert to relpath to avoid leaking user directory info
Expand All @@ -866,8 +861,15 @@ def _load_builtin_import(level: int, module_str: str) -> InterfaceT:
try:
file = input_bundle.load_file(path)
assert isinstance(file, FileInput) # mypy hint
except FileNotFoundError:
raise ModuleNotFoundError(f"Not a builtin: {module_str}") from None
except FileNotFoundError as e:
hint = None
components = module_str.split(".")
# common issue for upgrading codebases from v0.3.x to v0.4.x -
# hint: rename ERC20 to IERC20
if components[-1].startswith("ERC"):
module_prefix = components[-1]
hint = f"try renaming `{module_prefix}` to `I{module_prefix}`"
raise ModuleNotFound(module_str, hint=hint) from e

# TODO: it might be good to cache this computation
interface_ast = _parse_and_fold_ast(file)
Expand Down
Loading