vyperlang / vyper

Pythonic Smart Contract Language for the EVM
https://vyperlang.org
Other
4.84k stars 788 forks source link

`TypeCheckFailure` when zeroing with a smaller `DynArray` type. #3274

Open trocher opened 1 year ago

trocher commented 1 year ago

Version Information

What's your issue about?

When trying to use empty(DynArray[TYPE,x]) where DynArray[TYPE,y] is expected given x<y, the compiler emits a TypeCheckFailure instead of exiting with a nice error.

a:DynArray[uint256,4]
@external
def foo():
    self.a = empty(DynArray[uint256,3])
Error compiling: tests/customs/code.vy
Traceback (most recent call last):
  File "/Users/trocher/Documents/thesis/vyper/venv/bin/vyper", line 11, in <module>
    load_entry_point('vyper==0.3.8', 'console_scripts', 'vyper')()
  File "/Users/trocher/Documents/thesis/vyper/vyper/cli/vyper_compile.py", line 57, in _parse_cli_args
    return _parse_args(sys.argv[1:])
  File "/Users/trocher/Documents/thesis/vyper/vyper/cli/vyper_compile.py", line 154, in _parse_args
    compiled = compile_files(
  File "/Users/trocher/Documents/thesis/vyper/vyper/cli/vyper_compile.py", line 294, in compile_files
    compiler_data = vyper.compile_codes(
  File "/Users/trocher/Documents/thesis/vyper/vyper/evm/opcodes.py", line 226, in _wrapper
    return fn(*args, **kwargs)
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/__init__.py", line 141, in compile_codes
    exc_handler(contract_name, exc)
  File "/Users/trocher/Documents/thesis/vyper/vyper/cli/vyper_compile.py", line 189, in exc_handler
    raise exception
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/__init__.py", line 138, in compile_codes
    out[contract_name][output_format] = OUTPUT_FORMATS[output_format](compiler_data)
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/output.py", line 82, in build_ir_output
    return compiler_data.ir_nodes
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/phases.py", line 126, in ir_nodes
    ir, ir_runtime, sigs = self._ir_output
  File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/functools.py", line 966, in __get__
    val = self.func(instance)
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/phases.py", line 122, in _ir_output
    return generate_ir_nodes(self.global_ctx, self.no_optimize)
  File "/Users/trocher/Documents/thesis/vyper/vyper/compiler/phases.py", line 258, in generate_ir_nodes
    ir_nodes, ir_runtime, function_sigs = module.generate_ir_for_module(global_ctx)
  File "/Users/trocher/Documents/thesis/vyper/vyper/codegen/module.py", line 162, in generate_ir_for_module
    runtime, internal_functions = _runtime_ir(runtime_functions, all_sigs, global_ctx)
  File "/Users/trocher/Documents/thesis/vyper/vyper/codegen/module.py", line 103, in _runtime_ir
    func_ir = generate_ir_for_function(func_ast, all_sigs, global_ctx, skip_nonpayable_check)
  File "/Users/trocher/Documents/thesis/vyper/vyper/codegen/function_definitions/common.py", line 62, in generate_ir_for_function
    o = generate_ir_for_external_function(code, sig, context, skip_nonpayable_check)
  File "/Users/trocher/Documents/thesis/vyper/vyper/codegen/function_definitions/external_function.py", line 199, in generate_ir_for_external_function
    body += [parse_body(code.body, context, ensure_terminated=True)]
  File "/Users/trocher/Documents/thesis/vyper/vyper/codegen/stmt.py", line 414, in parse_body
    ir = parse_stmt(stmt, context)
  File "/Users/trocher/Documents/thesis/vyper/vyper/codegen/stmt.py", line 388, in parse_stmt
    return Stmt(stmt, context).ir_node
  File "/Users/trocher/Documents/thesis/vyper/vyper/codegen/stmt.py", line 40, in __init__
    self.ir_node = fn()
  File "/Users/trocher/Documents/thesis/vyper/vyper/codegen/stmt.py", line 78, in parse_Assign
    ir_node = make_setter(target, sub)
  File "/Users/trocher/Documents/thesis/vyper/vyper/codegen/core.py", line 819, in make_setter
    check_assign(left, right)
  File "/Users/trocher/Documents/thesis/vyper/vyper/codegen/core.py", line 767, in check_assign
    _check_assign_list(left, right)
  File "/Users/trocher/Documents/thesis/vyper/vyper/codegen/core.py", line 713, in _check_assign_list
    raise TypeCheckFailure(
vyper.exceptions.TypeCheckFailure: Bad type for clearing bytes: expected DynArray[uint256, 4] but got DynArray[uint256, 3]

This is an unhandled internal compiler error. Please create an issue on Github to notify the developers.
https://github.com/vyperlang/vyper/issues/new?template=bug.md

How can it be fixed?

I would guess that raising TypeMismatch would be more appropriate given that the same behaviour for String and Bytes make use of it.

tserg commented 1 year ago

I took a look into this. This should have been caught in the semantics analysis phase in visit_Assign (or visit_AnnAssign) where we validate that the value is of the expected type - e.g.: https://github.com/vyperlang/vyper/blob/187ab0eec8efbe19ed5046e4e947249e9d43141c/vyper/semantics/analysis/local.py#L244-L254

The reason dynamic arrays of a shorter length are not called is because of the compare_type() implementation for DArrayT, where a dynamic array value is considered of the same type if it is of a shorter length (see L269): https://github.com/vyperlang/vyper/blob/187ab0eec8efbe19ed5046e4e947249e9d43141c/vyper/semantics/types/subscriptable.py#L264-L271

These are my findings so far, but I do not have a good handle on what an appropriate fix is yet. I still think this should be handled at the typechecking phase.

trocher commented 1 year ago

Note that the TypeMismatch emitted for String and Bytes in the same case is in fact emitted from _check_assign_bytes in the codegen, so even if the exception looks nicer, this probably should also have been caught at type-checking