Skip to content

Commit

Permalink
feat[tool]: separate import resolution pass (#4229)
Browse files Browse the repository at this point in the history
this commit separates import resolution into its own pass. there are
two reasons for this:

a) improved performance on certain operations, specifically,
computation of the integrity hash. this is a hotspot for tooling, which
needs to compute the integrity hash of a contract quickly to check if
it needs to be recompiled or not

b) logical separation. after this commit, all the I/O and filepath
computation of the module analysis is moved into its own pass, making
the module analyzer "pure" - it no longer needs to deal about input
bundles or filepaths or anything like that.

misc/refactor: this commit moves integrity hash computation into
a compiler pass. besides that, some some minor code touchups were
performed, but because of moving code into a new file, an effort
was made to keep the semantics of the code largely the same, to ease
reasoning about the diff.
  • Loading branch information
charles-cooper authored Oct 14, 2024
1 parent 6606ded commit 6843e79
Show file tree
Hide file tree
Showing 14 changed files with 465 additions and 414 deletions.
8 changes: 1 addition & 7 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from tests.utils import working_directory
from vyper import compiler
from vyper.codegen.ir_node import IRnode
from vyper.compiler.input_bundle import FilesystemInputBundle, InputBundle
from vyper.compiler.input_bundle import FilesystemInputBundle
from vyper.compiler.settings import OptimizationLevel, Settings, set_global_settings
from vyper.exceptions import EvmVersionException
from vyper.ir import compile_ir, optimizer
Expand Down Expand Up @@ -166,12 +166,6 @@ def fn(sources_dict):
return fn


# for tests which just need an input bundle, doesn't matter what it is
@pytest.fixture
def dummy_input_bundle():
return InputBundle([])


@pytest.fixture(scope="module")
def gas_limit():
# set absurdly high gas limit so that london basefee never adjusts
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/codegen/types/numbers/test_decimals.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ def foo():
compile_code(code)


def test_replace_decimal_nested_intermediate_underflow(dummy_input_bundle):
def test_replace_decimal_nested_intermediate_underflow():
code = """
@external
def foo():
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/ast/nodes/test_hex.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def foo():


@pytest.mark.parametrize("code", code_invalid_checksum)
def test_invalid_checksum(code, dummy_input_bundle):
def test_invalid_checksum(code):
with pytest.raises(InvalidLiteral):
vyper_module = vy_ast.parse_to_ast(code)
semantics.analyze_module(vyper_module, dummy_input_bundle)
semantics.analyze_module(vyper_module)
20 changes: 10 additions & 10 deletions tests/unit/semantics/analysis/test_array_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@


@pytest.mark.parametrize("value", ["address", "Bytes[10]", "decimal", "bool"])
def test_type_mismatch(namespace, value, dummy_input_bundle):
def test_type_mismatch(namespace, value):
code = f"""
a: uint256[3]
Expand All @@ -22,11 +22,11 @@ def foo(b: {value}):
"""
vyper_module = parse_to_ast(code)
with pytest.raises(TypeMismatch):
analyze_module(vyper_module, dummy_input_bundle)
analyze_module(vyper_module)


@pytest.mark.parametrize("value", ["1.0", "0.0", "'foo'", "0x00", "b'\x01'", "False"])
def test_invalid_literal(namespace, value, dummy_input_bundle):
def test_invalid_literal(namespace, value):
code = f"""
a: uint256[3]
Expand All @@ -37,11 +37,11 @@ def foo():
"""
vyper_module = parse_to_ast(code)
with pytest.raises(TypeMismatch):
analyze_module(vyper_module, dummy_input_bundle)
analyze_module(vyper_module)


@pytest.mark.parametrize("value", [-1, 3, -(2**127), 2**127 - 1, 2**256 - 1])
def test_out_of_bounds(namespace, value, dummy_input_bundle):
def test_out_of_bounds(namespace, value):
code = f"""
a: uint256[3]
Expand All @@ -52,11 +52,11 @@ def foo():
"""
vyper_module = parse_to_ast(code)
with pytest.raises(ArrayIndexException):
analyze_module(vyper_module, dummy_input_bundle)
analyze_module(vyper_module)


@pytest.mark.parametrize("value", ["b", "self.b"])
def test_undeclared_definition(namespace, value, dummy_input_bundle):
def test_undeclared_definition(namespace, value):
code = f"""
a: uint256[3]
Expand All @@ -67,11 +67,11 @@ def foo():
"""
vyper_module = parse_to_ast(code)
with pytest.raises(UndeclaredDefinition):
analyze_module(vyper_module, dummy_input_bundle)
analyze_module(vyper_module)


@pytest.mark.parametrize("value", ["a", "foo", "int128"])
def test_invalid_reference(namespace, value, dummy_input_bundle):
def test_invalid_reference(namespace, value):
code = f"""
a: uint256[3]
Expand All @@ -82,4 +82,4 @@ def foo():
"""
vyper_module = parse_to_ast(code)
with pytest.raises(InvalidReference):
analyze_module(vyper_module, dummy_input_bundle)
analyze_module(vyper_module)
24 changes: 12 additions & 12 deletions tests/unit/semantics/analysis/test_cyclic_function_calls.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,20 @@
from vyper.semantics.analysis import analyze_module


def test_self_function_call(dummy_input_bundle):
def test_self_function_call():
code = """
@internal
def foo():
self.foo()
"""
vyper_module = parse_to_ast(code)
with pytest.raises(CallViolation) as e:
analyze_module(vyper_module, dummy_input_bundle)
analyze_module(vyper_module)

assert e.value.message == "Contract contains cyclic function call: foo -> foo"


def test_self_function_call2(dummy_input_bundle):
def test_self_function_call2():
code = """
@external
def foo():
Expand All @@ -30,12 +30,12 @@ def bar():
"""
vyper_module = parse_to_ast(code)
with pytest.raises(CallViolation) as e:
analyze_module(vyper_module, dummy_input_bundle)
analyze_module(vyper_module)

assert e.value.message == "Contract contains cyclic function call: foo -> bar -> bar"


def test_cyclic_function_call(dummy_input_bundle):
def test_cyclic_function_call():
code = """
@internal
def foo():
Expand All @@ -47,12 +47,12 @@ def bar():
"""
vyper_module = parse_to_ast(code)
with pytest.raises(CallViolation) as e:
analyze_module(vyper_module, dummy_input_bundle)
analyze_module(vyper_module)

assert e.value.message == "Contract contains cyclic function call: foo -> bar -> foo"


def test_multi_cyclic_function_call(dummy_input_bundle):
def test_multi_cyclic_function_call():
code = """
@internal
def foo():
Expand All @@ -72,14 +72,14 @@ def potato():
"""
vyper_module = parse_to_ast(code)
with pytest.raises(CallViolation) as e:
analyze_module(vyper_module, dummy_input_bundle)
analyze_module(vyper_module)

expected_message = "Contract contains cyclic function call: foo -> bar -> baz -> potato -> foo"

assert e.value.message == expected_message


def test_multi_cyclic_function_call2(dummy_input_bundle):
def test_multi_cyclic_function_call2():
code = """
@internal
def foo():
Expand All @@ -99,14 +99,14 @@ def potato():
"""
vyper_module = parse_to_ast(code)
with pytest.raises(CallViolation) as e:
analyze_module(vyper_module, dummy_input_bundle)
analyze_module(vyper_module)

expected_message = "Contract contains cyclic function call: foo -> bar -> baz -> potato -> bar"

assert e.value.message == expected_message


def test_global_ann_assign_callable_no_crash(dummy_input_bundle):
def test_global_ann_assign_callable_no_crash():
code = """
balanceOf: public(HashMap[address, uint256])
Expand All @@ -116,5 +116,5 @@ def foo(to : address):
"""
vyper_module = parse_to_ast(code)
with pytest.raises(StructureException) as excinfo:
analyze_module(vyper_module, dummy_input_bundle)
analyze_module(vyper_module)
assert excinfo.value.message == "HashMap[address, uint256] is not callable"
52 changes: 26 additions & 26 deletions tests/unit/semantics/analysis/test_for_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from vyper.semantics.analysis import analyze_module


def test_modify_iterator_function_outside_loop(dummy_input_bundle):
def test_modify_iterator_function_outside_loop():
code = """
a: uint256[3]
Expand All @@ -21,10 +21,10 @@ def bar():
pass
"""
vyper_module = parse_to_ast(code)
analyze_module(vyper_module, dummy_input_bundle)
analyze_module(vyper_module)


def test_pass_memory_var_to_other_function(dummy_input_bundle):
def test_pass_memory_var_to_other_function():
code = """
@internal
Expand All @@ -41,10 +41,10 @@ def bar():
self.foo(a)
"""
vyper_module = parse_to_ast(code)
analyze_module(vyper_module, dummy_input_bundle)
analyze_module(vyper_module)


def test_modify_iterator(dummy_input_bundle):
def test_modify_iterator():
code = """
a: uint256[3]
Expand All @@ -56,10 +56,10 @@ def bar():
"""
vyper_module = parse_to_ast(code)
with pytest.raises(ImmutableViolation):
analyze_module(vyper_module, dummy_input_bundle)
analyze_module(vyper_module)


def test_bad_keywords(dummy_input_bundle):
def test_bad_keywords():
code = """
@internal
Expand All @@ -70,10 +70,10 @@ def bar(n: uint256):
"""
vyper_module = parse_to_ast(code)
with pytest.raises(ArgumentException):
analyze_module(vyper_module, dummy_input_bundle)
analyze_module(vyper_module)


def test_bad_bound(dummy_input_bundle):
def test_bad_bound():
code = """
@internal
Expand All @@ -84,10 +84,10 @@ def bar(n: uint256):
"""
vyper_module = parse_to_ast(code)
with pytest.raises(StructureException):
analyze_module(vyper_module, dummy_input_bundle)
analyze_module(vyper_module)


def test_modify_iterator_function_call(dummy_input_bundle):
def test_modify_iterator_function_call():
code = """
a: uint256[3]
Expand All @@ -103,10 +103,10 @@ def bar():
"""
vyper_module = parse_to_ast(code)
with pytest.raises(ImmutableViolation):
analyze_module(vyper_module, dummy_input_bundle)
analyze_module(vyper_module)


def test_modify_iterator_recursive_function_call(dummy_input_bundle):
def test_modify_iterator_recursive_function_call():
code = """
a: uint256[3]
Expand All @@ -126,10 +126,10 @@ def baz():
"""
vyper_module = parse_to_ast(code)
with pytest.raises(ImmutableViolation):
analyze_module(vyper_module, dummy_input_bundle)
analyze_module(vyper_module)


def test_modify_iterator_recursive_function_call_topsort(dummy_input_bundle):
def test_modify_iterator_recursive_function_call_topsort():
# test the analysis works no matter the order of functions
code = """
a: uint256[3]
Expand All @@ -149,12 +149,12 @@ def foo():
"""
vyper_module = parse_to_ast(code)
with pytest.raises(ImmutableViolation) as e:
analyze_module(vyper_module, dummy_input_bundle)
analyze_module(vyper_module)

assert e.value._message == "Cannot modify loop variable `a`"


def test_modify_iterator_through_struct(dummy_input_bundle):
def test_modify_iterator_through_struct():
# GH issue 3429
code = """
struct A:
Expand All @@ -170,12 +170,12 @@ def foo():
"""
vyper_module = parse_to_ast(code)
with pytest.raises(ImmutableViolation) as e:
analyze_module(vyper_module, dummy_input_bundle)
analyze_module(vyper_module)

assert e.value._message == "Cannot modify loop variable `a`"


def test_modify_iterator_complex_expr(dummy_input_bundle):
def test_modify_iterator_complex_expr():
# GH issue 3429
# avoid false positive!
code = """
Expand All @@ -189,10 +189,10 @@ def foo():
self.b[self.a[1]] = i
"""
vyper_module = parse_to_ast(code)
analyze_module(vyper_module, dummy_input_bundle)
analyze_module(vyper_module)


def test_modify_iterator_siblings(dummy_input_bundle):
def test_modify_iterator_siblings():
# test we can modify siblings in an access tree
code = """
struct Foo:
Expand All @@ -207,10 +207,10 @@ def foo():
self.f.b += i
"""
vyper_module = parse_to_ast(code)
analyze_module(vyper_module, dummy_input_bundle)
analyze_module(vyper_module)


def test_modify_subscript_barrier(dummy_input_bundle):
def test_modify_subscript_barrier():
# test that Subscript nodes are a barrier for analysis
code = """
struct Foo:
Expand All @@ -229,7 +229,7 @@ def foo():
"""
vyper_module = parse_to_ast(code)
with pytest.raises(ImmutableViolation) as e:
analyze_module(vyper_module, dummy_input_bundle)
analyze_module(vyper_module)

assert e.value._message == "Cannot modify loop variable `b`"

Expand Down Expand Up @@ -269,7 +269,7 @@ def foo():


@pytest.mark.parametrize("code", iterator_inference_codes)
def test_iterator_type_inference_checker(code, dummy_input_bundle):
def test_iterator_type_inference_checker(code):
vyper_module = parse_to_ast(code)
with pytest.raises(TypeMismatch):
analyze_module(vyper_module, dummy_input_bundle)
analyze_module(vyper_module)
2 changes: 1 addition & 1 deletion vyper/compiler/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def build_archive_b64(compiler_data: CompilerData) -> str:


def build_integrity(compiler_data: CompilerData) -> str:
return compiler_data.compilation_target._metadata["type"].integrity_sum
return compiler_data.resolved_imports.integrity_sum


def build_external_interface_output(compiler_data: CompilerData) -> str:
Expand Down
Loading

0 comments on commit 6843e79

Please sign in to comment.