def test_subscript_negative(build_node, namespace): node = build_node("foo[-1]") type_def = Int128Definition() namespace["foo"] = ArrayDefinition(type_def, 3) with pytest.raises(ArrayIndexException): get_possible_types_from_node(node)
def test_boolop(build_node, namespace, op, left, right): node = build_node(f"{left} {op} {right}") with namespace.enter_scope(): types_list = get_possible_types_from_node(node) assert len(types_list) == 1 assert isinstance(types_list[0], BoolDefinition)
def visit_UnaryOp(self, node, type_): if type_ is None: type_ = get_possible_types_from_node(node.operand) if len(type_) == 1: type_ = type_.pop() node._metadata["type"] = type_ self.visit(node.operand, type_)
def test_tuple_subscript(build_node, namespace): node = build_node("(foo, bar)[1]") namespace["foo"] = Int128Definition() namespace["bar"] = AddressDefinition() types_list = get_possible_types_from_node(node) assert types_list == [namespace["bar"]]
def test_tuple(build_node, namespace): node = build_node("(foo, bar)") namespace["foo"] = Int128Definition() namespace["bar"] = AddressDefinition() types_list = get_possible_types_from_node(node) assert types_list[0].value_type == [namespace["foo"], namespace["bar"]]
def visit_List(self, node, type_): if type_ is None: type_ = get_possible_types_from_node(node) if len(type_) >= 1: type_ = type_.pop() node._metadata["type"] = type_ for element in node.elements: self.visit(element, type_.value_type)
def test_list(build_node, namespace, left, right): node = build_node(f"[{left}, {right}]") with namespace.enter_scope(): types_list = get_possible_types_from_node(node) assert types_list for item in types_list: assert isinstance(item, (DynamicArrayDefinition, ArrayDefinition))
def visit_List(self, node, type_): if type_ is None: type_ = get_possible_types_from_node(node) # CMC 2022-04-14 this seems sus. try to only annotate # if get_possible_types only returns 1 type if len(type_) >= 1: type_ = type_.pop() node._metadata["type"] = type_ for element in node.elements: self.visit(element, type_.value_type)
def visit_Subscript(self, node, type_): if isinstance(node.value, vy_ast.List): possible_base_types = get_possible_types_from_node(node.value) if len(possible_base_types) == 1: base_type = possible_base_types.pop() elif type_ and len(possible_base_types) > 1: for possible_type in possible_base_types: if isinstance(possible_type.value_type, type(type_)): base_type = possible_type break else: base_type = get_exact_type_from_node(node.value) if isinstance(base_type, BaseTypeDefinition): # in the vast majority of cases `base_type` is a type definition, # however there are some edge cases with args to builtin functions self.visit(node.slice, base_type.get_index_type(node.slice.value)) self.visit(node.value, base_type)
def visit_Subscript(self, node, type_): node._metadata["type"] = type_ if isinstance(type_, TypeTypeDefinition): # don't recurse; can't annotate AST children of type definition return if isinstance(node.value, vy_ast.List): possible_base_types = get_possible_types_from_node(node.value) if len(possible_base_types) == 1: base_type = possible_base_types.pop() elif type_ is not None and len(possible_base_types) > 1: for possible_type in possible_base_types: if isinstance(possible_type.value_type, type(type_)): base_type = possible_type break else: base_type = get_exact_type_from_node(node.value) self.visit(node.slice, base_type.get_index_type()) self.visit(node.value, base_type)
def test_attribute_not_member_type(build_node, namespace): node = build_node("foo.bar") with namespace.enter_scope(): namespace["foo"] = Int128Definition() with pytest.raises(StructureException): get_possible_types_from_node(node)
def visit_Constant(self, node, type_): if type_ is None: possible_types = get_possible_types_from_node(node) if len(possible_types) == 1: type_ = possible_types.pop() node._metadata["type"] = type_
def test_subscript(build_node, namespace): node = build_node("foo[1]") type_def = Int128Definition() namespace["foo"] = ArrayDefinition(type_def, 3) assert get_possible_types_from_node(node) == [type_def]
def test_attribute(build_node, namespace): node = build_node("self.foo") type_def = Int128Definition() with namespace.enter_scope(): namespace["self"].add_member("foo", type_def) assert get_possible_types_from_node(node) == [type_def]
def visit_For(self, node): if isinstance(node.iter, vy_ast.Subscript): raise StructureException("Cannot iterate over a nested list", node.iter) if isinstance(node.iter, vy_ast.Call): # iteration via range() if node.iter.get("func.id") != "range": raise IteratorException( "Cannot iterate over the result of a function call", node.iter) validate_call_args(node.iter, (1, 2)) args = node.iter.args if len(args) == 1: # range(CONSTANT) if not isinstance(args[0], vy_ast.Num): raise StateAccessViolation("Value must be a literal", node) if args[0].value <= 0: raise StructureException( "For loop must have at least 1 iteration", args[0]) validate_expected_type(args[0], IntegerAbstractType()) type_list = get_possible_types_from_node(args[0]) else: validate_expected_type(args[0], IntegerAbstractType()) type_list = get_common_types(*args) if not isinstance(args[0], vy_ast.Constant): # range(x, x + CONSTANT) if not isinstance(args[1], vy_ast.BinOp) or not isinstance( args[1].op, vy_ast.Add): raise StructureException( "Second element must be the first element plus a literal value", args[0]) if not vy_ast.compare_nodes(args[0], args[1].left): raise StructureException( "First and second variable must be the same", args[1].left) if not isinstance(args[1].right, vy_ast.Int): raise InvalidLiteral("Literal must be an integer", args[1].right) if args[1].right.value < 1: raise StructureException( f"For loop has invalid number of iterations ({args[1].right.value})," " the value must be greater than zero", args[1].right, ) else: # range(CONSTANT, CONSTANT) if not isinstance(args[1], vy_ast.Int): raise InvalidType("Value must be a literal integer", args[1]) validate_expected_type(args[1], IntegerAbstractType()) if args[0].value >= args[1].value: raise StructureException( "Second value must be > first value", args[1]) else: # iteration over a variable or literal list type_list = [ i.value_type for i in get_possible_types_from_node(node.iter) if isinstance(i, (DynamicArrayDefinition, ArrayDefinition)) ] if not type_list: raise InvalidType("Not an iterable type", node.iter) if isinstance(node.iter, (vy_ast.Name, vy_ast.Attribute)): # check for references to the iterated value within the body of the loop assign = _check_iterator_modification(node.iter, node) if assign: raise ImmutableViolation( "Cannot modify array during iteration", assign) # Check if `iter` is a storage variable. get_descendants` is used to check for # nested `self` (e.g. structs) iter_is_storage_var = (isinstance(node.iter, vy_ast.Attribute) and len( node.iter.get_descendants(vy_ast.Name, {"id": "self"})) > 0) if iter_is_storage_var: # check if iterated value may be modified by function calls inside the loop iter_name = node.iter.attr for call_node in node.get_descendants(vy_ast.Call, {"func.value.id": "self"}): fn_name = call_node.func.attr fn_node = self.vyper_module.get_children( vy_ast.FunctionDef, {"name": fn_name})[0] if _check_iterator_modification(node.iter, fn_node): # check for direct modification raise ImmutableViolation( f"Cannot call '{fn_name}' inside for loop, it potentially " f"modifies iterated storage variable '{iter_name}'", call_node, ) for name in self.namespace["self"].members[ fn_name].recursive_calls: # check for indirect modification fn_node = self.vyper_module.get_children( vy_ast.FunctionDef, {"name": name})[0] if _check_iterator_modification(node.iter, fn_node): raise ImmutableViolation( f"Cannot call '{fn_name}' inside for loop, it may call to '{name}' " f"which potentially modifies iterated storage variable '{iter_name}'", call_node, ) self.expr_visitor.visit(node.iter) for_loop_exceptions = [] iter_name = node.target.id for type_ in type_list: # type check the for loop body using each possible type for iterator value type_ = copy.deepcopy(type_) type_.is_constant = True with self.namespace.enter_scope(): try: self.namespace[iter_name] = type_ except VyperException as exc: raise exc.with_annotation(node) from None try: for n in node.body: self.visit(n) # type information is applied directly because the scope is # closed prior to the call to `StatementAnnotationVisitor` node.target._metadata["type"] = type_ return except (TypeMismatch, InvalidOperation) as exc: for_loop_exceptions.append(exc) if len(set(str(i) for i in for_loop_exceptions)) == 1: # if every attempt at type checking raised the same exception raise for_loop_exceptions[0] # return an aggregate TypeMismatch that shows all possible exceptions # depending on which type is used types_str = [str(i) for i in type_list] given_str = f"{', '.join(types_str[:1])} or {types_str[-1]}" raise TypeMismatch( f"Iterator value '{iter_name}' may be cast as {given_str}, " "but type checking fails with all possible types:", node, *((f"Casting '{iter_name}' as {type_}: {exc.message}", exc.annotations[0]) for type_, exc in zip(type_list, for_loop_exceptions)), )
def test_binop_invalid_decimal_pow(build_node, namespace): node = build_node("2.1 ** 2.1") with namespace.enter_scope(): with pytest.raises(InvalidOperation): get_possible_types_from_node(node)
def test_binop_invalid_op(build_node, namespace, op, left, right): node = build_node(f"{left} {op} {right}") with namespace.enter_scope(): with pytest.raises(InvalidOperation): get_possible_types_from_node(node)
def test_binop_type_mismatch(build_node, namespace, op, left, right): node = build_node(f"{left}{op}{right}") with namespace.enter_scope(): with pytest.raises(TypeMismatch): get_possible_types_from_node(node)
def test_name_unknown(build_node, namespace): node = build_node("foo") with pytest.raises(UndeclaredDefinition): get_possible_types_from_node(node)
def test_attribute_missing_self(build_node, namespace): node = build_node("foo") with namespace.enter_scope(): namespace["self"].add_member("foo", Int128Definition()) with pytest.raises(InvalidReference): get_possible_types_from_node(node)
def test_attribute_unknown(build_node, namespace): node = build_node("foo.bar") with namespace.enter_scope(): namespace["foo"] = AddressDefinition() with pytest.raises(UnknownAttribute): get_possible_types_from_node(node)
def test_attribute_not_in_self(build_node, namespace): node = build_node("self.foo") with namespace.enter_scope(): namespace["foo"] = Int128Definition() with pytest.raises(InvalidReference): get_possible_types_from_node(node)
def test_binop(build_node, namespace, op, left, right): node = build_node(f"{left}{op}{right}") with namespace.enter_scope(): get_possible_types_from_node(node)
def test_name(build_node, namespace): node = build_node("foo") type_def = Int128Definition() namespace["foo"] = type_def assert get_possible_types_from_node(node) == [type_def]