From d141e272d418262281896bd504f29011dc118154 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Mon, 4 Mar 2024 17:18:17 -0800 Subject: [PATCH 1/7] fix up some initializer errors --- vyper/semantics/analysis/global_.py | 3 +-- vyper/semantics/analysis/module.py | 23 +++++++++++++++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/vyper/semantics/analysis/global_.py b/vyper/semantics/analysis/global_.py index a632c9194c..74bf31f922 100644 --- a/vyper/semantics/analysis/global_.py +++ b/vyper/semantics/analysis/global_.py @@ -60,8 +60,7 @@ def _validate_global_initializes_constraint(module_t: ModuleT): if u not in all_initialized_modules: found_module = module_t.find_module_info(u) if found_module is not None: - hint = f"add `initializes: {found_module.alias}` to the top level of " - hint += "your main contract" + hint = f"add `initializes: {found_module.alias}` to `{module_t._module.path}`" else: # CMC 2024-02-06 is this actually reachable? hint = f"ensure `{module_t}` is imported in your main contract!" diff --git a/vyper/semantics/analysis/module.py b/vyper/semantics/analysis/module.py index b8b4bf48f2..42fad17532 100644 --- a/vyper/semantics/analysis/module.py +++ b/vyper/semantics/analysis/module.py @@ -374,7 +374,11 @@ def validate_initialized_modules(self): msg = "not initialized!" hint = f"add `{s.module_info.alias}.__init__()` to " hint += "your `__init__()` function" - err_list.append(InitializerException(msg, s.node, hint=hint)) + + # grab the init function AST node for error message + # (it could be None, it's ok since it's just for diagnostics) + init_func_node = module_t.init_function.decl_node + err_list.append(InitializerException(msg, init_func_node, s.node, hint=hint)) err_list.raise_if_not_empty() @@ -489,7 +493,22 @@ def visit_InitializesDecl(self, node): item = next(iter(used_modules.values())) # just pick one msg = f"`{module_info.alias}` uses `{item.alias}`, but it is not " msg += f"initialized with `{item.alias}`" - hint = f"add `{item.alias}` to its initializer list" + + lhs = item.alias + rhs = None + # find the alias of the uninitialized module in this contract + # to fill out the error message with. + for k, v in self.namespace.items(): + if isinstance(v, ModuleInfo) and v.module_t == item.module_t: + rhs = k + break + + if rhs is None: + hint = "try importing {item.alias} first" + elif isinstance(module_ref, vy_ast.Name): + hint = f"did you mean {module_ref.id}[{lhs} := {rhs}]?" + else: + hint = f"add `{lhs} := {rhs}` to its initializer list" raise InitializerException(msg, node, hint=hint) # note: try to refactor. not a huge fan of mutating the From c1aac9fb4d2e2b2407f437b30a02b956e7cb1043 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Mon, 4 Mar 2024 17:33:42 -0800 Subject: [PATCH 2/7] fix tests --- tests/functional/syntax/modules/test_initializers.py | 2 +- vyper/semantics/analysis/global_.py | 7 ++++++- vyper/semantics/analysis/module.py | 4 +++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/functional/syntax/modules/test_initializers.py b/tests/functional/syntax/modules/test_initializers.py index 8f18e18a26..d59122cb54 100644 --- a/tests/functional/syntax/modules/test_initializers.py +++ b/tests/functional/syntax/modules/test_initializers.py @@ -383,7 +383,7 @@ def foo(): with pytest.raises(InitializerException) as e: compile_code(main, input_bundle=input_bundle) assert e.value._message == "`lib2` uses `lib1`, but it is not initialized with `lib1`" - assert e.value._hint == "add `lib1` to its initializer list" + assert e.value._hint == "did you mean lib2[lib1 := lib1]?" def test_missing_uses(make_input_bundle): diff --git a/vyper/semantics/analysis/global_.py b/vyper/semantics/analysis/global_.py index 74bf31f922..8b3e0544a5 100644 --- a/vyper/semantics/analysis/global_.py +++ b/vyper/semantics/analysis/global_.py @@ -60,7 +60,12 @@ def _validate_global_initializes_constraint(module_t: ModuleT): if u not in all_initialized_modules: found_module = module_t.find_module_info(u) if found_module is not None: - hint = f"add `initializes: {found_module.alias}` to `{module_t._module.path}`" + # TODO: do something about these constants + if str(module_t) in ("", "VyperContract.vy"): + module_str = "the top level of your main contract" + else: + module_str = f"`{module_t}`" + hint = f"add `initializes: {found_module.alias}` to {module_str}" else: # CMC 2024-02-06 is this actually reachable? hint = f"ensure `{module_t}` is imported in your main contract!" diff --git a/vyper/semantics/analysis/module.py b/vyper/semantics/analysis/module.py index 42fad17532..5bcd988208 100644 --- a/vyper/semantics/analysis/module.py +++ b/vyper/semantics/analysis/module.py @@ -377,7 +377,9 @@ def validate_initialized_modules(self): # grab the init function AST node for error message # (it could be None, it's ok since it's just for diagnostics) - init_func_node = module_t.init_function.decl_node + init_func_node = None + if module_t.init_function: + init_func_node = module_t.init_function.decl_node err_list.append(InitializerException(msg, init_func_node, s.node, hint=hint)) err_list.raise_if_not_empty() From 22b97e8973c38866ced98e8849fc605c1a82b0d9 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Thu, 14 Mar 2024 09:52:27 -0400 Subject: [PATCH 3/7] fix a check --- vyper/semantics/analysis/module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/semantics/analysis/module.py b/vyper/semantics/analysis/module.py index 5bcd988208..0f7fd7160b 100644 --- a/vyper/semantics/analysis/module.py +++ b/vyper/semantics/analysis/module.py @@ -507,7 +507,7 @@ def visit_InitializesDecl(self, node): if rhs is None: hint = "try importing {item.alias} first" - elif isinstance(module_ref, vy_ast.Name): + elif isinstance(module_ref, vy_ast.NamedExpr): hint = f"did you mean {module_ref.id}[{lhs} := {rhs}]?" else: hint = f"add `{lhs} := {rhs}` to its initializer list" From c79c159a0748b16edfb6ce65d5319d2ae73b1b2d Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Thu, 14 Mar 2024 10:02:26 -0400 Subject: [PATCH 4/7] fix an f-string --- vyper/semantics/analysis/module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/semantics/analysis/module.py b/vyper/semantics/analysis/module.py index 0f7fd7160b..c866146382 100644 --- a/vyper/semantics/analysis/module.py +++ b/vyper/semantics/analysis/module.py @@ -506,7 +506,7 @@ def visit_InitializesDecl(self, node): break if rhs is None: - hint = "try importing {item.alias} first" + hint = f"try importing {item.alias} first" elif isinstance(module_ref, vy_ast.NamedExpr): hint = f"did you mean {module_ref.id}[{lhs} := {rhs}]?" else: From ab1a2e33192af200a5652f7c82fc7bbb0437b82f Mon Sep 17 00:00:00 2001 From: cyberthirst Date: Fri, 5 Apr 2024 09:42:27 +0200 Subject: [PATCH 5/7] fix hint for initializer w no references --- vyper/semantics/analysis/module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/semantics/analysis/module.py b/vyper/semantics/analysis/module.py index c866146382..a67e64adf6 100644 --- a/vyper/semantics/analysis/module.py +++ b/vyper/semantics/analysis/module.py @@ -507,7 +507,7 @@ def visit_InitializesDecl(self, node): if rhs is None: hint = f"try importing {item.alias} first" - elif isinstance(module_ref, vy_ast.NamedExpr): + elif not isinstance(module_ref.parent, vy_ast.Subscript): hint = f"did you mean {module_ref.id}[{lhs} := {rhs}]?" else: hint = f"add `{lhs} := {rhs}` to its initializer list" From 713007e249c7639a195a870765aa8c3d6d7eba34 Mon Sep 17 00:00:00 2001 From: cyberthirst Date: Fri, 5 Apr 2024 09:42:55 +0200 Subject: [PATCH 6/7] add tests for initializer hints --- .../syntax/modules/test_initializers.py | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/tests/functional/syntax/modules/test_initializers.py b/tests/functional/syntax/modules/test_initializers.py index e1464d28ef..9825e4618f 100644 --- a/tests/functional/syntax/modules/test_initializers.py +++ b/tests/functional/syntax/modules/test_initializers.py @@ -1228,3 +1228,67 @@ def use_lib1(): compile_code(main, input_bundle=input_bundle, output_formats=["annotated_ast_dict"]) is not None ) + + +def test_hint_for_missing_initializer_in_list(make_input_bundle): + lib1 = """ +counter: uint256 + """ + lib3 = """ +counter: uint256 + """ + lib2 = """ +import lib1 +import lib3 + +uses: lib1 +uses: lib3 + +counter: uint256 + +@internal +def foo(): + lib1.counter += 1 + lib3.counter += 1 + """ + main = """ +import lib1 +import lib2 +import lib3 + +initializes: lib2[lib1:=lib1] +initializes: lib1 +initializes: lib3 + """ + input_bundle = make_input_bundle({"lib1.vy": lib1, "lib2.vy": lib2, "lib3.vy": lib3}) + with pytest.raises(InitializerException) as e: + compile_code(main, input_bundle=input_bundle) + assert e.value._message == "`lib2` uses `lib3`, but it is not initialized with `lib3`" + assert e.value._hint == "add `lib3 := lib3` to its initializer list" + + +def test_hint_for_missing_initializer_when_no_import(make_input_bundle): + lib1 = """ +counter: uint256 + """ + lib2 = """ +import lib1 + +uses: lib1 + +counter: uint256 + +@internal +def foo(): + lib1.counter += 1 + """ + main = """ +import lib2 + +initializes: lib2 + """ + input_bundle = make_input_bundle({"lib1.vy": lib1, "lib2.vy": lib2}) + with pytest.raises(InitializerException) as e: + compile_code(main, input_bundle=input_bundle) + assert e.value._message == "`lib2` uses `lib1`, but it is not initialized with `lib1`" + assert e.value._hint == "try importing lib1 first" From b3855c696c970e9cccbdcc2ddcb6ff30e01de1a1 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Sun, 7 Apr 2024 08:51:54 -0400 Subject: [PATCH 7/7] fix a check --- vyper/semantics/analysis/module.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/vyper/semantics/analysis/module.py b/vyper/semantics/analysis/module.py index a67e64adf6..54459fd821 100644 --- a/vyper/semantics/analysis/module.py +++ b/vyper/semantics/analysis/module.py @@ -436,8 +436,10 @@ def visit_UsesDecl(self, node): node._metadata["uses_info"] = UsesInfo(used_modules, node) def visit_InitializesDecl(self, node): - module_ref = node.annotation + annotation = node.annotation + dependencies_ast = () + module_ref = annotation if isinstance(module_ref, vy_ast.Subscript): dependencies_ast = vy_ast.as_tuple(module_ref.slice) module_ref = module_ref.value @@ -507,7 +509,8 @@ def visit_InitializesDecl(self, node): if rhs is None: hint = f"try importing {item.alias} first" - elif not isinstance(module_ref.parent, vy_ast.Subscript): + elif not isinstance(annotation, vy_ast.Subscript): + # it's `initializes: foo` instead of `initializes: foo[...]` hint = f"did you mean {module_ref.id}[{lhs} := {rhs}]?" else: hint = f"add `{lhs} := {rhs}` to its initializer list"