diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 1173cbeb99..308e89b371 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -555,626 +555,6 @@ pub fn (mut c Checker) expand_iface_embeds(idecl &ast.InterfaceDecl, level int, return ares } -fn (mut c Checker) check_div_mod_by_zero(expr ast.Expr, op_kind token.Kind) { - match expr { - ast.FloatLiteral { - if expr.val.f64() == 0.0 { - oper := if op_kind == .div { 'division' } else { 'modulo' } - c.error('$oper by zero', expr.pos) - } - } - ast.IntegerLiteral { - if expr.val.int() == 0 { - oper := if op_kind == .div { 'division' } else { 'modulo' } - c.error('$oper by zero', expr.pos) - } - } - ast.CastExpr { - c.check_div_mod_by_zero(expr.expr, op_kind) - } - else {} - } -} - -pub fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type { - former_expected_type := c.expected_type - defer { - c.expected_type = former_expected_type - } - mut left_type := c.expr(node.left) - node.left_type = left_type - c.expected_type = left_type - mut right_type := c.expr(node.right) - node.right_type = right_type - if left_type.is_number() && !left_type.is_ptr() - && right_type in [ast.int_literal_type, ast.float_literal_type] { - node.right_type = left_type - } - if right_type.is_number() && !right_type.is_ptr() - && left_type in [ast.int_literal_type, ast.float_literal_type] { - node.left_type = right_type - } - mut right_sym := c.table.sym(right_type) - right_final := c.table.final_sym(right_type) - mut left_sym := c.table.sym(left_type) - left_final := c.table.final_sym(left_type) - left_pos := node.left.pos() - right_pos := node.right.pos() - left_right_pos := left_pos.extend(right_pos) - if left_type.is_any_kind_of_pointer() - && node.op in [.plus, .minus, .mul, .div, .mod, .xor, .amp, .pipe] { - if !c.pref.translated && ((right_type.is_any_kind_of_pointer() && node.op != .minus) - || (!right_type.is_any_kind_of_pointer() && node.op !in [.plus, .minus])) { - left_name := c.table.type_to_str(left_type) - right_name := c.table.type_to_str(right_type) - c.error('invalid operator `$node.op` to `$left_name` and `$right_name`', left_right_pos) - } else if node.op in [.plus, .minus] { - if !c.inside_unsafe && !node.left.is_auto_deref_var() && !node.right.is_auto_deref_var() { - c.warn('pointer arithmetic is only allowed in `unsafe` blocks', left_right_pos) - } - if left_type == ast.voidptr_type && !c.pref.translated { - c.error('`$node.op` cannot be used with `voidptr`', left_pos) - } - } - } - mut return_type := left_type - - if node.op != .key_is { - match mut node.left { - ast.Ident, ast.SelectorExpr { - if node.left.is_mut { - c.error('the `mut` keyword is invalid here', node.left.mut_pos) - } - } - else {} - } - } - match mut node.right { - ast.Ident, ast.SelectorExpr { - if node.right.is_mut { - c.error('the `mut` keyword is invalid here', node.right.mut_pos) - } - } - else {} - } - eq_ne := node.op in [.eq, .ne] - // Single side check - // Place these branches according to ops' usage frequency to accelerate. - // TODO: First branch includes ops where single side check is not needed, or needed but hasn't been implemented. - // TODO: Some of the checks are not single side. Should find a better way to organize them. - match node.op { - // .eq, .ne, .gt, .lt, .ge, .le, .and, .logical_or, .dot, .key_as, .right_shift {} - .eq, .ne { - is_mismatch := - (left_sym.kind == .alias && right_sym.kind in [.struct_, .array, .sum_type]) - || (right_sym.kind == .alias && left_sym.kind in [.struct_, .array, .sum_type]) - if is_mismatch { - c.error('possible type mismatch of compared values of `$node.op` operation', - left_right_pos) - } else if left_type in ast.integer_type_idxs && right_type in ast.integer_type_idxs { - is_left_type_signed := left_type in ast.signed_integer_type_idxs - is_right_type_signed := right_type in ast.signed_integer_type_idxs - if !is_left_type_signed && mut node.right is ast.IntegerLiteral { - if node.right.val.int() < 0 && left_type in ast.int_promoted_type_idxs { - lt := c.table.sym(left_type).name - c.error('`$lt` cannot be compared with negative value', node.right.pos) - } - } else if !is_right_type_signed && mut node.left is ast.IntegerLiteral { - if node.left.val.int() < 0 && right_type in ast.int_promoted_type_idxs { - rt := c.table.sym(right_type).name - c.error('negative value cannot be compared with `$rt`', node.left.pos) - } - } else if is_left_type_signed != is_right_type_signed - && left_type != ast.int_literal_type_idx - && right_type != ast.int_literal_type_idx { - ls, _ := c.table.type_size(left_type) - rs, _ := c.table.type_size(right_type) - // prevent e.g. `u32 == i16` but not `u16 == i32` as max_u16 fits in i32 - // TODO u32 == i32, change < to <= - if !c.pref.translated && ((is_left_type_signed && ls < rs) - || (is_right_type_signed && rs < ls)) { - lt := c.table.sym(left_type).name - rt := c.table.sym(right_type).name - c.error('`$lt` cannot be compared with `$rt`', node.pos) - } - } - } - } - .key_in, .not_in { - match right_final.kind { - .array { - if left_sym.kind !in [.sum_type, .interface_] { - elem_type := right_final.array_info().elem_type - c.check_expected(left_type, elem_type) or { - c.error('left operand to `$node.op` does not match the array element type: $err.msg()', - left_right_pos) - } - } - } - .map { - map_info := right_final.map_info() - c.check_expected(left_type, map_info.key_type) or { - c.error('left operand to `$node.op` does not match the map key type: $err.msg()', - left_right_pos) - } - node.left_type = map_info.key_type - } - .array_fixed { - if left_sym.kind !in [.sum_type, .interface_] { - elem_type := right_final.array_fixed_info().elem_type - c.check_expected(left_type, elem_type) or { - c.error('left operand to `$node.op` does not match the fixed array element type: $err.msg()', - left_right_pos) - } - } - } - else { - c.error('`$node.op.str()` can only be used with arrays and maps', - node.pos) - } - } - return ast.bool_type - } - .plus, .minus, .mul, .div, .mod, .xor, .amp, .pipe { - // binary operators that expect matching types - if right_sym.info is ast.Alias && (right_sym.info as ast.Alias).language != .c - && c.mod == c.table.type_to_str(right_type).split('.')[0] - && c.table.sym((right_sym.info as ast.Alias).parent_type).is_primitive() { - right_sym = c.table.sym((right_sym.info as ast.Alias).parent_type) - } - if left_sym.info is ast.Alias && (left_sym.info as ast.Alias).language != .c - && c.mod == c.table.type_to_str(left_type).split('.')[0] - && c.table.sym((left_sym.info as ast.Alias).parent_type).is_primitive() { - left_sym = c.table.sym((left_sym.info as ast.Alias).parent_type) - } - - if c.pref.translated && node.op in [.plus, .minus, .mul] - && left_type.is_any_kind_of_pointer() && right_type.is_any_kind_of_pointer() { - return_type = left_type - } else if !c.pref.translated && left_sym.kind == .alias && left_sym.info is ast.Alias - && !(c.table.sym((left_sym.info as ast.Alias).parent_type).is_primitive()) { - if left_sym.has_method(node.op.str()) { - if method := left_sym.find_method(node.op.str()) { - return_type = method.return_type - } else { - return_type = left_type - } - } else { - left_name := c.table.type_to_str(left_type) - right_name := c.table.type_to_str(right_type) - if left_name == right_name { - c.error('undefined operation `$left_name` $node.op.str() `$right_name`', - left_right_pos) - } else { - c.error('mismatched types `$left_name` and `$right_name`', left_right_pos) - } - } - } else if !c.pref.translated && right_sym.kind == .alias && right_sym.info is ast.Alias - && !(c.table.sym((right_sym.info as ast.Alias).parent_type).is_primitive()) { - if right_sym.has_method(node.op.str()) { - if method := right_sym.find_method(node.op.str()) { - return_type = method.return_type - } else { - return_type = right_type - } - } else { - left_name := c.table.type_to_str(left_type) - right_name := c.table.type_to_str(right_type) - if left_name == right_name { - c.error('undefined operation `$left_name` $node.op.str() `$right_name`', - left_right_pos) - } else { - c.error('mismatched types `$left_name` and `$right_name`', left_right_pos) - } - } - } - - if !c.pref.translated && left_sym.kind in [.array, .array_fixed, .map, .struct_] { - if left_sym.has_method_with_generic_parent(node.op.str()) { - if method := left_sym.find_method_with_generic_parent(node.op.str()) { - return_type = method.return_type - } else { - return_type = left_type - } - } else { - left_name := c.table.type_to_str(left_type) - right_name := c.table.type_to_str(right_type) - if left_name == right_name { - c.error('undefined operation `$left_name` $node.op.str() `$right_name`', - left_right_pos) - } else { - c.error('mismatched types `$left_name` and `$right_name`', left_right_pos) - } - } - } else if !c.pref.translated && right_sym.kind in [.array, .array_fixed, .map, .struct_] { - if right_sym.has_method_with_generic_parent(node.op.str()) { - if method := right_sym.find_method_with_generic_parent(node.op.str()) { - return_type = method.return_type - } else { - return_type = right_type - } - } else { - left_name := c.table.type_to_str(left_type) - right_name := c.table.type_to_str(right_type) - if left_name == right_name { - c.error('undefined operation `$left_name` $node.op.str() `$right_name`', - left_right_pos) - } else { - c.error('mismatched types `$left_name` and `$right_name`', left_right_pos) - } - } - } else if node.left.is_auto_deref_var() || node.right.is_auto_deref_var() { - deref_left_type := if node.left.is_auto_deref_var() { - left_type.deref() - } else { - left_type - } - deref_right_type := if node.right.is_auto_deref_var() { - right_type.deref() - } else { - right_type - } - left_name := c.table.type_to_str(ast.mktyp(deref_left_type)) - right_name := c.table.type_to_str(ast.mktyp(deref_right_type)) - if left_name != right_name { - c.error('mismatched types `$left_name` and `$right_name`', left_right_pos) - } - } else { - unaliased_left_type := c.table.unalias_num_type(left_type) - unalias_right_type := c.table.unalias_num_type(right_type) - mut promoted_type := c.promote(unaliased_left_type, unalias_right_type) - // substract pointers is allowed in unsafe block - is_allowed_pointer_arithmetic := left_type.is_any_kind_of_pointer() - && right_type.is_any_kind_of_pointer() && node.op == .minus - if is_allowed_pointer_arithmetic { - promoted_type = ast.int_type - } - if promoted_type.idx() == ast.void_type_idx { - left_name := c.table.type_to_str(left_type) - right_name := c.table.type_to_str(right_type) - c.error('mismatched types `$left_name` and `$right_name`', left_right_pos) - } else if promoted_type.has_flag(.optional) { - s := c.table.type_to_str(promoted_type) - c.error('`$node.op` cannot be used with `$s`', node.pos) - } else if promoted_type.is_float() { - if node.op in [.mod, .xor, .amp, .pipe] { - side := if left_type == promoted_type { 'left' } else { 'right' } - pos := if left_type == promoted_type { left_pos } else { right_pos } - name := if left_type == promoted_type { - left_sym.name - } else { - right_sym.name - } - if node.op == .mod { - c.error('float modulo not allowed, use math.fmod() instead', - pos) - } else { - c.error('$side type of `$node.op.str()` cannot be non-integer type `$name`', - pos) - } - } - } - if node.op in [.div, .mod] { - c.check_div_mod_by_zero(node.right, node.op) - } - - return_type = promoted_type - } - } - .gt, .lt, .ge, .le { - if left_sym.kind in [.array, .array_fixed] && right_sym.kind in [.array, .array_fixed] { - c.error('only `==` and `!=` are defined on arrays', node.pos) - } else if left_sym.kind == .struct_ - && (left_sym.info as ast.Struct).generic_types.len > 0 { - return ast.bool_type - } else if left_sym.kind == .struct_ && right_sym.kind == .struct_ - && node.op in [.eq, .lt] { - if !(left_sym.has_method(node.op.str()) && right_sym.has_method(node.op.str())) { - left_name := c.table.type_to_str(left_type) - right_name := c.table.type_to_str(right_type) - if left_name == right_name { - if !(node.op == .lt && c.pref.translated) { - // Allow `&Foo < &Foo` in translated code. - // TODO maybe in unsafe as well? - c.error('undefined operation `$left_name` $node.op.str() `$right_name`', - left_right_pos) - } - } else { - c.error('mismatched types `$left_name` and `$right_name`', left_right_pos) - } - } - } - if left_sym.kind == .struct_ && right_sym.kind == .struct_ { - if !left_sym.has_method('<') && node.op in [.ge, .le] { - c.error('cannot use `$node.op` as `<` operator method is not defined', - left_right_pos) - } else if !left_sym.has_method('<') && node.op == .gt { - c.error('cannot use `>` as `<=` operator method is not defined', left_right_pos) - } - } else if left_type.has_flag(.generic) && right_type.has_flag(.generic) { - // Try to unwrap the generic type to make sure that - // the below check works as expected - left_gen_type := c.unwrap_generic(left_type) - gen_sym := c.table.sym(left_gen_type) - need_overload := gen_sym.kind in [.struct_, .interface_] - if need_overload && !gen_sym.has_method_with_generic_parent('<') - && node.op in [.ge, .le] { - c.error('cannot use `$node.op` as `<` operator method is not defined', - left_right_pos) - } else if need_overload && !gen_sym.has_method_with_generic_parent('<') - && node.op == .gt { - c.error('cannot use `>` as `<=` operator method is not defined', left_right_pos) - } - } else if left_type in ast.integer_type_idxs && right_type in ast.integer_type_idxs { - is_left_type_signed := left_type in ast.signed_integer_type_idxs - || left_type == ast.int_literal_type_idx - is_right_type_signed := right_type in ast.signed_integer_type_idxs - || right_type == ast.int_literal_type_idx - if is_left_type_signed != is_right_type_signed { - if is_right_type_signed { - if mut node.right is ast.IntegerLiteral { - if node.right.val.int() < 0 { - c.error('unsigned integer cannot be compared with negative value', - node.right.pos) - } - } - } else if is_left_type_signed { - if mut node.left is ast.IntegerLiteral { - if node.left.val.int() < 0 { - c.error('unsigned integer cannot be compared with negative value', - node.left.pos) - } - } - } - } - } else if left_type.has_flag(.optional) || right_type.has_flag(.optional) { - opt_comp_pos := if left_type.has_flag(.optional) { left_pos } else { right_pos } - c.error('unwrapped optional cannot be compared in an infix expression', - opt_comp_pos) - } - } - .left_shift { - if left_final.kind == .array { - if !node.is_stmt { - c.error('array append cannot be used in an expression', node.pos) - } - // `array << elm` - c.check_expr_opt_call(node.right, right_type) - node.auto_locked, _ = c.fail_if_immutable(node.left) - left_value_type := c.table.value_type(c.unwrap_generic(left_type)) - left_value_sym := c.table.sym(c.unwrap_generic(left_value_type)) - if left_value_sym.kind == .interface_ { - if right_final.kind != .array { - // []Animal << Cat - if c.type_implements(right_type, left_value_type, right_pos) { - if !right_type.is_ptr() && !right_type.is_pointer() && !c.inside_unsafe - && right_sym.kind != .interface_ { - c.mark_as_referenced(mut &node.right, true) - } - } - } else { - // []Animal << []Cat - c.type_implements(c.table.value_type(right_type), left_value_type, - right_pos) - } - return ast.void_type - } else if left_value_sym.kind == .sum_type { - if right_final.kind != .array { - if !c.table.is_sumtype_or_in_variant(left_value_type, ast.mktyp(right_type)) { - c.error('cannot append `$right_sym.name` to `$left_sym.name`', - right_pos) - } - } else { - right_value_type := c.table.value_type(right_type) - if !c.table.is_sumtype_or_in_variant(left_value_type, ast.mktyp(right_value_type)) { - c.error('cannot append `$right_sym.name` to `$left_sym.name`', - right_pos) - } - } - return ast.void_type - } - // []T << T or []T << []T - unwrapped_right_type := c.unwrap_generic(right_type) - if c.check_types(unwrapped_right_type, left_value_type) { - // []&T << T is wrong: we check for that, !(T.is_ptr()) && ?(&T).is_ptr() - if !(!unwrapped_right_type.is_ptr() && left_value_type.is_ptr() - && left_value_type.share() == .mut_t) { - return ast.void_type - } - } else if c.check_types(unwrapped_right_type, c.unwrap_generic(left_type)) { - return ast.void_type - } - c.error('cannot append `$right_sym.name` to `$left_sym.name`', right_pos) - return ast.void_type - } else { - return c.check_shift(mut node, left_type, right_type) - } - } - .right_shift { - return c.check_shift(mut node, left_type, right_type) - } - .unsigned_right_shift { - modified_left_type := if !left_type.is_int() { - c.error('invalid operation: shift on type `${c.table.sym(left_type).name}`', - left_pos) - ast.void_type_idx - } else if left_type.is_int_literal() { - // int literal => i64 - ast.u32_type_idx - } else if left_type.is_unsigned() { - left_type - } else { - // signed types' idx adds with 5 will get correct relative unsigned type - // i8 => byte - // i16 => u16 - // int => u32 - // i64 => u64 - // isize => usize - // i128 => u128 NOT IMPLEMENTED YET - left_type.idx() + ast.u32_type_idx - ast.int_type_idx - } - - if modified_left_type == 0 { - return ast.void_type - } - - node = ast.InfixExpr{ - left: ast.CastExpr{ - expr: node.left - typ: modified_left_type - typname: c.table.type_str(modified_left_type) - pos: node.pos - } - left_type: left_type - op: .right_shift - right: node.right - right_type: right_type - is_stmt: false - pos: node.pos - auto_locked: node.auto_locked - or_block: node.or_block - } - - return c.check_shift(mut node, left_type, right_type) - } - .key_is, .not_is { - right_expr := node.right - mut typ := match right_expr { - ast.TypeNode { - right_expr.typ - } - ast.None { - ast.none_type_idx - } - else { - c.error('invalid type `$right_expr`', right_expr.pos()) - ast.Type(0) - } - } - if typ != ast.Type(0) { - typ_sym := c.table.sym(typ) - op := node.op.str() - if typ_sym.kind == .placeholder { - c.error('$op: type `$typ_sym.name` does not exist', right_expr.pos()) - } - if left_sym.kind == .aggregate { - parent_left_type := (left_sym.info as ast.Aggregate).sum_type - left_sym = c.table.sym(parent_left_type) - } - if left_sym.kind !in [.interface_, .sum_type] { - c.error('`$op` can only be used with interfaces and sum types', node.pos) - } else if mut left_sym.info is ast.SumType { - if typ !in left_sym.info.variants { - c.error('`$left_sym.name` has no variant `$right_sym.name`', node.pos) - } - } - } - return ast.bool_type - } - .arrow { // `chan <- elem` - if left_sym.kind == .chan { - chan_info := left_sym.chan_info() - elem_type := chan_info.elem_type - if !c.check_types(right_type, elem_type) { - c.error('cannot push `$right_sym.name` on `$left_sym.name`', right_pos) - } - if chan_info.is_mut { - // TODO: The error message of the following could be more specific... - c.fail_if_immutable(node.right) - } - if elem_type.is_ptr() && !right_type.is_ptr() { - c.error('cannot push non-reference `$right_sym.name` on `$left_sym.name`', - right_pos) - } - c.stmts_ending_with_expression(node.or_block.stmts) - } else { - c.error('cannot push on non-channel `$left_sym.name`', left_pos) - } - return ast.void_type - } - .and, .logical_or { - if !c.pref.translated && !c.file.is_translated { - if node.left_type != ast.bool_type_idx { - c.error('left operand for `$node.op` is not a boolean', node.left.pos()) - } - if node.right_type != ast.bool_type_idx { - c.error('right operand for `$node.op` is not a boolean', node.right.pos()) - } - } - if mut node.left is ast.InfixExpr { - if node.left.op != node.op && node.left.op in [.logical_or, .and] { - // for example: `(a && b) || c` instead of `a && b || c` - c.error('ambiguous boolean expression. use `()` to ensure correct order of operations', - node.pos) - } - } - } - else {} - } - // TODO: Absorb this block into the above single side check block to accelerate. - if left_type == ast.bool_type && node.op !in [.eq, .ne, .logical_or, .and] { - c.error('bool types only have the following operators defined: `==`, `!=`, `||`, and `&&`', - node.pos) - } else if left_type == ast.string_type && node.op !in [.plus, .eq, .ne, .lt, .gt, .le, .ge] { - // TODO broken !in - c.error('string types only have the following operators defined: `==`, `!=`, `<`, `>`, `<=`, `>=`, and `+`', - node.pos) - } else if left_sym.kind == .enum_ && right_sym.kind == .enum_ && !eq_ne { - left_enum := left_sym.info as ast.Enum - right_enum := right_sym.info as ast.Enum - if left_enum.is_flag && right_enum.is_flag { - // `[flag]` tagged enums are a special case that allow also `|` and `&` binary operators - if node.op !in [.pipe, .amp] { - c.error('only `==`, `!=`, `|` and `&` are defined on `[flag]` tagged `enum`, use an explicit cast to `int` if needed', - node.pos) - } - } else if !c.pref.translated && !c.file.is_translated { - // Regular enums - c.error('only `==` and `!=` are defined on `enum`, use an explicit cast to `int` if needed', - node.pos) - } - } - // sum types can't have any infix operation except of `is`, `eq`, `ne`. - // `is` is checked before and doesn't reach this. - if c.table.type_kind(left_type) == .sum_type && !eq_ne { - c.error('cannot use operator `$node.op` with `$left_sym.name`', node.pos) - } else if c.table.type_kind(right_type) == .sum_type && !eq_ne { - c.error('cannot use operator `$node.op` with `$right_sym.name`', node.pos) - } - // TODO move this to symmetric_check? Right now it would break `return 0` for `fn()?int ` - left_is_optional := left_type.has_flag(.optional) - right_is_optional := right_type.has_flag(.optional) - if left_is_optional && right_is_optional { - c.error('unwrapped optionals cannot be used in an infix expression', left_right_pos) - } else if left_is_optional || right_is_optional { - opt_infix_pos := if left_is_optional { left_pos } else { right_pos } - c.error('unwrapped optional cannot be used in an infix expression', opt_infix_pos) - } - // Dual sides check (compatibility check) - if !(c.symmetric_check(left_type, right_type) && c.symmetric_check(right_type, left_type)) - && !c.pref.translated && !c.file.is_translated && !node.left.is_auto_deref_var() - && !node.right.is_auto_deref_var() { - // for type-unresolved consts - if left_type == ast.void_type || right_type == ast.void_type { - return ast.void_type - } - if left_type.nr_muls() > 0 && right_type.is_int() { - // pointer arithmetic is fine, it is checked in other places - return return_type - } - c.error('infix expr: cannot use `$right_sym.name` (right expression) as `$left_sym.name`', - left_right_pos) - } - /* - if (node.left is ast.InfixExpr && - (node.left as ast.InfixExpr).op == .inc) || - (node.right is ast.InfixExpr && (node.right as ast.InfixExpr).op == .inc) { - c.warn('`++` and `--` are statements, not expressions', node.pos) - } - */ - return if node.op.is_relational() { ast.bool_type } else { return_type } -} - // returns name and position of variable that needs write lock // also sets `is_changed` to true (TODO update the name to reflect this?) fn (mut c Checker) fail_if_immutable(expr_ ast.Expr) (string, token.Pos) { diff --git a/vlib/v/checker/infix.v b/vlib/v/checker/infix.v new file mode 100644 index 0000000000..1452c5707f --- /dev/null +++ b/vlib/v/checker/infix.v @@ -0,0 +1,625 @@ +module checker + +import v.ast +import v.pref +import v.token + +pub fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type { + former_expected_type := c.expected_type + defer { + c.expected_type = former_expected_type + } + mut left_type := c.expr(node.left) + node.left_type = left_type + c.expected_type = left_type + mut right_type := c.expr(node.right) + node.right_type = right_type + if left_type.is_number() && !left_type.is_ptr() + && right_type in [ast.int_literal_type, ast.float_literal_type] { + node.right_type = left_type + } + if right_type.is_number() && !right_type.is_ptr() + && left_type in [ast.int_literal_type, ast.float_literal_type] { + node.left_type = right_type + } + mut right_sym := c.table.sym(right_type) + right_final := c.table.final_sym(right_type) + mut left_sym := c.table.sym(left_type) + left_final := c.table.final_sym(left_type) + left_pos := node.left.pos() + right_pos := node.right.pos() + left_right_pos := left_pos.extend(right_pos) + if left_type.is_any_kind_of_pointer() + && node.op in [.plus, .minus, .mul, .div, .mod, .xor, .amp, .pipe] { + if !c.pref.translated && ((right_type.is_any_kind_of_pointer() && node.op != .minus) + || (!right_type.is_any_kind_of_pointer() && node.op !in [.plus, .minus])) { + left_name := c.table.type_to_str(left_type) + right_name := c.table.type_to_str(right_type) + c.error('invalid operator `$node.op` to `$left_name` and `$right_name`', left_right_pos) + } else if node.op in [.plus, .minus] { + if !c.inside_unsafe && !node.left.is_auto_deref_var() && !node.right.is_auto_deref_var() { + c.warn('pointer arithmetic is only allowed in `unsafe` blocks', left_right_pos) + } + if left_type == ast.voidptr_type && !c.pref.translated { + c.error('`$node.op` cannot be used with `voidptr`', left_pos) + } + } + } + mut return_type := left_type + + if node.op != .key_is { + match mut node.left { + ast.Ident, ast.SelectorExpr { + if node.left.is_mut { + c.error('the `mut` keyword is invalid here', node.left.mut_pos) + } + } + else {} + } + } + match mut node.right { + ast.Ident, ast.SelectorExpr { + if node.right.is_mut { + c.error('the `mut` keyword is invalid here', node.right.mut_pos) + } + } + else {} + } + eq_ne := node.op in [.eq, .ne] + // Single side check + // Place these branches according to ops' usage frequency to accelerate. + // TODO: First branch includes ops where single side check is not needed, or needed but hasn't been implemented. + // TODO: Some of the checks are not single side. Should find a better way to organize them. + match node.op { + // .eq, .ne, .gt, .lt, .ge, .le, .and, .logical_or, .dot, .key_as, .right_shift {} + .eq, .ne { + is_mismatch := + (left_sym.kind == .alias && right_sym.kind in [.struct_, .array, .sum_type]) + || (right_sym.kind == .alias && left_sym.kind in [.struct_, .array, .sum_type]) + if is_mismatch { + c.error('possible type mismatch of compared values of `$node.op` operation', + left_right_pos) + } else if left_type in ast.integer_type_idxs && right_type in ast.integer_type_idxs { + is_left_type_signed := left_type in ast.signed_integer_type_idxs + is_right_type_signed := right_type in ast.signed_integer_type_idxs + if !is_left_type_signed && mut node.right is ast.IntegerLiteral { + if node.right.val.int() < 0 && left_type in ast.int_promoted_type_idxs { + lt := c.table.sym(left_type).name + c.error('`$lt` cannot be compared with negative value', node.right.pos) + } + } else if !is_right_type_signed && mut node.left is ast.IntegerLiteral { + if node.left.val.int() < 0 && right_type in ast.int_promoted_type_idxs { + rt := c.table.sym(right_type).name + c.error('negative value cannot be compared with `$rt`', node.left.pos) + } + } else if is_left_type_signed != is_right_type_signed + && left_type != ast.int_literal_type_idx + && right_type != ast.int_literal_type_idx { + ls, _ := c.table.type_size(left_type) + rs, _ := c.table.type_size(right_type) + // prevent e.g. `u32 == i16` but not `u16 == i32` as max_u16 fits in i32 + // TODO u32 == i32, change < to <= + if !c.pref.translated && ((is_left_type_signed && ls < rs) + || (is_right_type_signed && rs < ls)) { + lt := c.table.sym(left_type).name + rt := c.table.sym(right_type).name + c.error('`$lt` cannot be compared with `$rt`', node.pos) + } + } + } + } + .key_in, .not_in { + match right_final.kind { + .array { + if left_sym.kind !in [.sum_type, .interface_] { + elem_type := right_final.array_info().elem_type + c.check_expected(left_type, elem_type) or { + c.error('left operand to `$node.op` does not match the array element type: $err.msg()', + left_right_pos) + } + } + } + .map { + map_info := right_final.map_info() + c.check_expected(left_type, map_info.key_type) or { + c.error('left operand to `$node.op` does not match the map key type: $err.msg()', + left_right_pos) + } + node.left_type = map_info.key_type + } + .array_fixed { + if left_sym.kind !in [.sum_type, .interface_] { + elem_type := right_final.array_fixed_info().elem_type + c.check_expected(left_type, elem_type) or { + c.error('left operand to `$node.op` does not match the fixed array element type: $err.msg()', + left_right_pos) + } + } + } + else { + c.error('`$node.op.str()` can only be used with arrays and maps', + node.pos) + } + } + return ast.bool_type + } + .plus, .minus, .mul, .div, .mod, .xor, .amp, .pipe { + // binary operators that expect matching types + if right_sym.info is ast.Alias && (right_sym.info as ast.Alias).language != .c + && c.mod == c.table.type_to_str(right_type).split('.')[0] + && c.table.sym((right_sym.info as ast.Alias).parent_type).is_primitive() { + right_sym = c.table.sym((right_sym.info as ast.Alias).parent_type) + } + if left_sym.info is ast.Alias && (left_sym.info as ast.Alias).language != .c + && c.mod == c.table.type_to_str(left_type).split('.')[0] + && c.table.sym((left_sym.info as ast.Alias).parent_type).is_primitive() { + left_sym = c.table.sym((left_sym.info as ast.Alias).parent_type) + } + + if c.pref.translated && node.op in [.plus, .minus, .mul] + && left_type.is_any_kind_of_pointer() && right_type.is_any_kind_of_pointer() { + return_type = left_type + } else if !c.pref.translated && left_sym.kind == .alias && left_sym.info is ast.Alias + && !(c.table.sym((left_sym.info as ast.Alias).parent_type).is_primitive()) { + if left_sym.has_method(node.op.str()) { + if method := left_sym.find_method(node.op.str()) { + return_type = method.return_type + } else { + return_type = left_type + } + } else { + left_name := c.table.type_to_str(left_type) + right_name := c.table.type_to_str(right_type) + if left_name == right_name { + c.error('undefined operation `$left_name` $node.op.str() `$right_name`', + left_right_pos) + } else { + c.error('mismatched types `$left_name` and `$right_name`', left_right_pos) + } + } + } else if !c.pref.translated && right_sym.kind == .alias && right_sym.info is ast.Alias + && !(c.table.sym((right_sym.info as ast.Alias).parent_type).is_primitive()) { + if right_sym.has_method(node.op.str()) { + if method := right_sym.find_method(node.op.str()) { + return_type = method.return_type + } else { + return_type = right_type + } + } else { + left_name := c.table.type_to_str(left_type) + right_name := c.table.type_to_str(right_type) + if left_name == right_name { + c.error('undefined operation `$left_name` $node.op.str() `$right_name`', + left_right_pos) + } else { + c.error('mismatched types `$left_name` and `$right_name`', left_right_pos) + } + } + } + + if !c.pref.translated && left_sym.kind in [.array, .array_fixed, .map, .struct_] { + if left_sym.has_method_with_generic_parent(node.op.str()) { + if method := left_sym.find_method_with_generic_parent(node.op.str()) { + return_type = method.return_type + } else { + return_type = left_type + } + } else { + left_name := c.table.type_to_str(left_type) + right_name := c.table.type_to_str(right_type) + if left_name == right_name { + c.error('undefined operation `$left_name` $node.op.str() `$right_name`', + left_right_pos) + } else { + c.error('mismatched types `$left_name` and `$right_name`', left_right_pos) + } + } + } else if !c.pref.translated && right_sym.kind in [.array, .array_fixed, .map, .struct_] { + if right_sym.has_method_with_generic_parent(node.op.str()) { + if method := right_sym.find_method_with_generic_parent(node.op.str()) { + return_type = method.return_type + } else { + return_type = right_type + } + } else { + left_name := c.table.type_to_str(left_type) + right_name := c.table.type_to_str(right_type) + if left_name == right_name { + c.error('undefined operation `$left_name` $node.op.str() `$right_name`', + left_right_pos) + } else { + c.error('mismatched types `$left_name` and `$right_name`', left_right_pos) + } + } + } else if node.left.is_auto_deref_var() || node.right.is_auto_deref_var() { + deref_left_type := if node.left.is_auto_deref_var() { + left_type.deref() + } else { + left_type + } + deref_right_type := if node.right.is_auto_deref_var() { + right_type.deref() + } else { + right_type + } + left_name := c.table.type_to_str(ast.mktyp(deref_left_type)) + right_name := c.table.type_to_str(ast.mktyp(deref_right_type)) + if left_name != right_name { + c.error('mismatched types `$left_name` and `$right_name`', left_right_pos) + } + } else { + unaliased_left_type := c.table.unalias_num_type(left_type) + unalias_right_type := c.table.unalias_num_type(right_type) + mut promoted_type := c.promote(unaliased_left_type, unalias_right_type) + // substract pointers is allowed in unsafe block + is_allowed_pointer_arithmetic := left_type.is_any_kind_of_pointer() + && right_type.is_any_kind_of_pointer() && node.op == .minus + if is_allowed_pointer_arithmetic { + promoted_type = ast.int_type + } + if promoted_type.idx() == ast.void_type_idx { + left_name := c.table.type_to_str(left_type) + right_name := c.table.type_to_str(right_type) + c.error('mismatched types `$left_name` and `$right_name`', left_right_pos) + } else if promoted_type.has_flag(.optional) { + s := c.table.type_to_str(promoted_type) + c.error('`$node.op` cannot be used with `$s`', node.pos) + } else if promoted_type.is_float() { + if node.op in [.mod, .xor, .amp, .pipe] { + side := if left_type == promoted_type { 'left' } else { 'right' } + pos := if left_type == promoted_type { left_pos } else { right_pos } + name := if left_type == promoted_type { + left_sym.name + } else { + right_sym.name + } + if node.op == .mod { + c.error('float modulo not allowed, use math.fmod() instead', + pos) + } else { + c.error('$side type of `$node.op.str()` cannot be non-integer type `$name`', + pos) + } + } + } + if node.op in [.div, .mod] { + c.check_div_mod_by_zero(node.right, node.op) + } + + return_type = promoted_type + } + } + .gt, .lt, .ge, .le { + if left_sym.kind in [.array, .array_fixed] && right_sym.kind in [.array, .array_fixed] { + c.error('only `==` and `!=` are defined on arrays', node.pos) + } else if left_sym.kind == .struct_ + && (left_sym.info as ast.Struct).generic_types.len > 0 { + return ast.bool_type + } else if left_sym.kind == .struct_ && right_sym.kind == .struct_ + && node.op in [.eq, .lt] { + if !(left_sym.has_method(node.op.str()) && right_sym.has_method(node.op.str())) { + left_name := c.table.type_to_str(left_type) + right_name := c.table.type_to_str(right_type) + if left_name == right_name { + if !(node.op == .lt && c.pref.translated) { + // Allow `&Foo < &Foo` in translated code. + // TODO maybe in unsafe as well? + c.error('undefined operation `$left_name` $node.op.str() `$right_name`', + left_right_pos) + } + } else { + c.error('mismatched types `$left_name` and `$right_name`', left_right_pos) + } + } + } + if left_sym.kind == .struct_ && right_sym.kind == .struct_ { + if !left_sym.has_method('<') && node.op in [.ge, .le] { + c.error('cannot use `$node.op` as `<` operator method is not defined', + left_right_pos) + } else if !left_sym.has_method('<') && node.op == .gt { + c.error('cannot use `>` as `<=` operator method is not defined', left_right_pos) + } + } else if left_type.has_flag(.generic) && right_type.has_flag(.generic) { + // Try to unwrap the generic type to make sure that + // the below check works as expected + left_gen_type := c.unwrap_generic(left_type) + gen_sym := c.table.sym(left_gen_type) + need_overload := gen_sym.kind in [.struct_, .interface_] + if need_overload && !gen_sym.has_method_with_generic_parent('<') + && node.op in [.ge, .le] { + c.error('cannot use `$node.op` as `<` operator method is not defined', + left_right_pos) + } else if need_overload && !gen_sym.has_method_with_generic_parent('<') + && node.op == .gt { + c.error('cannot use `>` as `<=` operator method is not defined', left_right_pos) + } + } else if left_type in ast.integer_type_idxs && right_type in ast.integer_type_idxs { + is_left_type_signed := left_type in ast.signed_integer_type_idxs + || left_type == ast.int_literal_type_idx + is_right_type_signed := right_type in ast.signed_integer_type_idxs + || right_type == ast.int_literal_type_idx + if is_left_type_signed != is_right_type_signed { + if is_right_type_signed { + if mut node.right is ast.IntegerLiteral { + if node.right.val.int() < 0 { + c.error('unsigned integer cannot be compared with negative value', + node.right.pos) + } + } + } else if is_left_type_signed { + if mut node.left is ast.IntegerLiteral { + if node.left.val.int() < 0 { + c.error('unsigned integer cannot be compared with negative value', + node.left.pos) + } + } + } + } + } else if left_type.has_flag(.optional) || right_type.has_flag(.optional) { + opt_comp_pos := if left_type.has_flag(.optional) { left_pos } else { right_pos } + c.error('unwrapped optional cannot be compared in an infix expression', + opt_comp_pos) + } + } + .left_shift { + if left_final.kind == .array { + if !node.is_stmt { + c.error('array append cannot be used in an expression', node.pos) + } + // `array << elm` + c.check_expr_opt_call(node.right, right_type) + node.auto_locked, _ = c.fail_if_immutable(node.left) + left_value_type := c.table.value_type(c.unwrap_generic(left_type)) + left_value_sym := c.table.sym(c.unwrap_generic(left_value_type)) + if left_value_sym.kind == .interface_ { + if right_final.kind != .array { + // []Animal << Cat + if c.type_implements(right_type, left_value_type, right_pos) { + if !right_type.is_ptr() && !right_type.is_pointer() && !c.inside_unsafe + && right_sym.kind != .interface_ { + c.mark_as_referenced(mut &node.right, true) + } + } + } else { + // []Animal << []Cat + c.type_implements(c.table.value_type(right_type), left_value_type, + right_pos) + } + return ast.void_type + } else if left_value_sym.kind == .sum_type { + if right_final.kind != .array { + if !c.table.is_sumtype_or_in_variant(left_value_type, ast.mktyp(right_type)) { + c.error('cannot append `$right_sym.name` to `$left_sym.name`', + right_pos) + } + } else { + right_value_type := c.table.value_type(right_type) + if !c.table.is_sumtype_or_in_variant(left_value_type, ast.mktyp(right_value_type)) { + c.error('cannot append `$right_sym.name` to `$left_sym.name`', + right_pos) + } + } + return ast.void_type + } + // []T << T or []T << []T + unwrapped_right_type := c.unwrap_generic(right_type) + if c.check_types(unwrapped_right_type, left_value_type) { + // []&T << T is wrong: we check for that, !(T.is_ptr()) && ?(&T).is_ptr() + if !(!unwrapped_right_type.is_ptr() && left_value_type.is_ptr() + && left_value_type.share() == .mut_t) { + return ast.void_type + } + } else if c.check_types(unwrapped_right_type, c.unwrap_generic(left_type)) { + return ast.void_type + } + c.error('cannot append `$right_sym.name` to `$left_sym.name`', right_pos) + return ast.void_type + } else { + return c.check_shift(mut node, left_type, right_type) + } + } + .right_shift { + return c.check_shift(mut node, left_type, right_type) + } + .unsigned_right_shift { + modified_left_type := if !left_type.is_int() { + c.error('invalid operation: shift on type `${c.table.sym(left_type).name}`', + left_pos) + ast.void_type_idx + } else if left_type.is_int_literal() { + // int literal => i64 + ast.u32_type_idx + } else if left_type.is_unsigned() { + left_type + } else { + // signed types' idx adds with 5 will get correct relative unsigned type + // i8 => byte + // i16 => u16 + // int => u32 + // i64 => u64 + // isize => usize + // i128 => u128 NOT IMPLEMENTED YET + left_type.idx() + ast.u32_type_idx - ast.int_type_idx + } + + if modified_left_type == 0 { + return ast.void_type + } + + node = ast.InfixExpr{ + left: ast.CastExpr{ + expr: node.left + typ: modified_left_type + typname: c.table.type_str(modified_left_type) + pos: node.pos + } + left_type: left_type + op: .right_shift + right: node.right + right_type: right_type + is_stmt: false + pos: node.pos + auto_locked: node.auto_locked + or_block: node.or_block + } + + return c.check_shift(mut node, left_type, right_type) + } + .key_is, .not_is { + right_expr := node.right + mut typ := match right_expr { + ast.TypeNode { + right_expr.typ + } + ast.None { + ast.none_type_idx + } + else { + c.error('invalid type `$right_expr`', right_expr.pos()) + ast.Type(0) + } + } + if typ != ast.Type(0) { + typ_sym := c.table.sym(typ) + op := node.op.str() + if typ_sym.kind == .placeholder { + c.error('$op: type `$typ_sym.name` does not exist', right_expr.pos()) + } + if left_sym.kind == .aggregate { + parent_left_type := (left_sym.info as ast.Aggregate).sum_type + left_sym = c.table.sym(parent_left_type) + } + if left_sym.kind !in [.interface_, .sum_type] { + c.error('`$op` can only be used with interfaces and sum types', node.pos) + } else if mut left_sym.info is ast.SumType { + if typ !in left_sym.info.variants { + c.error('`$left_sym.name` has no variant `$right_sym.name`', node.pos) + } + } + } + return ast.bool_type + } + .arrow { // `chan <- elem` + if left_sym.kind == .chan { + chan_info := left_sym.chan_info() + elem_type := chan_info.elem_type + if !c.check_types(right_type, elem_type) { + c.error('cannot push `$right_sym.name` on `$left_sym.name`', right_pos) + } + if chan_info.is_mut { + // TODO: The error message of the following could be more specific... + c.fail_if_immutable(node.right) + } + if elem_type.is_ptr() && !right_type.is_ptr() { + c.error('cannot push non-reference `$right_sym.name` on `$left_sym.name`', + right_pos) + } + c.stmts_ending_with_expression(node.or_block.stmts) + } else { + c.error('cannot push on non-channel `$left_sym.name`', left_pos) + } + return ast.void_type + } + .and, .logical_or { + if !c.pref.translated && !c.file.is_translated { + if node.left_type != ast.bool_type_idx { + c.error('left operand for `$node.op` is not a boolean', node.left.pos()) + } + if node.right_type != ast.bool_type_idx { + c.error('right operand for `$node.op` is not a boolean', node.right.pos()) + } + } + if mut node.left is ast.InfixExpr { + if node.left.op != node.op && node.left.op in [.logical_or, .and] { + // for example: `(a && b) || c` instead of `a && b || c` + c.error('ambiguous boolean expression. use `()` to ensure correct order of operations', + node.pos) + } + } + } + else {} + } + // TODO: Absorb this block into the above single side check block to accelerate. + if left_type == ast.bool_type && node.op !in [.eq, .ne, .logical_or, .and] { + c.error('bool types only have the following operators defined: `==`, `!=`, `||`, and `&&`', + node.pos) + } else if left_type == ast.string_type && node.op !in [.plus, .eq, .ne, .lt, .gt, .le, .ge] { + // TODO broken !in + c.error('string types only have the following operators defined: `==`, `!=`, `<`, `>`, `<=`, `>=`, and `+`', + node.pos) + } else if left_sym.kind == .enum_ && right_sym.kind == .enum_ && !eq_ne { + left_enum := left_sym.info as ast.Enum + right_enum := right_sym.info as ast.Enum + if left_enum.is_flag && right_enum.is_flag { + // `[flag]` tagged enums are a special case that allow also `|` and `&` binary operators + if node.op !in [.pipe, .amp] { + c.error('only `==`, `!=`, `|` and `&` are defined on `[flag]` tagged `enum`, use an explicit cast to `int` if needed', + node.pos) + } + } else if !c.pref.translated && !c.file.is_translated { + // Regular enums + c.error('only `==` and `!=` are defined on `enum`, use an explicit cast to `int` if needed', + node.pos) + } + } + // sum types can't have any infix operation except of `is`, `eq`, `ne`. + // `is` is checked before and doesn't reach this. + if c.table.type_kind(left_type) == .sum_type && !eq_ne { + c.error('cannot use operator `$node.op` with `$left_sym.name`', node.pos) + } else if c.table.type_kind(right_type) == .sum_type && !eq_ne { + c.error('cannot use operator `$node.op` with `$right_sym.name`', node.pos) + } + // TODO move this to symmetric_check? Right now it would break `return 0` for `fn()?int ` + left_is_optional := left_type.has_flag(.optional) + right_is_optional := right_type.has_flag(.optional) + if left_is_optional && right_is_optional { + c.error('unwrapped optionals cannot be used in an infix expression', left_right_pos) + } else if left_is_optional || right_is_optional { + opt_infix_pos := if left_is_optional { left_pos } else { right_pos } + c.error('unwrapped optional cannot be used in an infix expression', opt_infix_pos) + } + // Dual sides check (compatibility check) + if !(c.symmetric_check(left_type, right_type) && c.symmetric_check(right_type, left_type)) + && !c.pref.translated && !c.file.is_translated && !node.left.is_auto_deref_var() + && !node.right.is_auto_deref_var() { + // for type-unresolved consts + if left_type == ast.void_type || right_type == ast.void_type { + return ast.void_type + } + if left_type.nr_muls() > 0 && right_type.is_int() { + // pointer arithmetic is fine, it is checked in other places + return return_type + } + c.error('infix expr: cannot use `$right_sym.name` (right expression) as `$left_sym.name`', + left_right_pos) + } + /* + if (node.left is ast.InfixExpr && + (node.left as ast.InfixExpr).op == .inc) || + (node.right is ast.InfixExpr && (node.right as ast.InfixExpr).op == .inc) { + c.warn('`++` and `--` are statements, not expressions', node.pos) + } + */ + return if node.op.is_relational() { ast.bool_type } else { return_type } +} + +fn (mut c Checker) check_div_mod_by_zero(expr ast.Expr, op_kind token.Kind) { + match expr { + ast.FloatLiteral { + if expr.val.f64() == 0.0 { + oper := if op_kind == .div { 'division' } else { 'modulo' } + c.error('$oper by zero', expr.pos) + } + } + ast.IntegerLiteral { + if expr.val.int() == 0 { + oper := if op_kind == .div { 'division' } else { 'modulo' } + c.error('$oper by zero', expr.pos) + } + } + ast.CastExpr { + c.check_div_mod_by_zero(expr.expr, op_kind) + } + else {} + } +}