checker: split up infix.v from checker.v (#14449)
parent
09886f78d3
commit
4549afd642
|
@ -555,626 +555,6 @@ pub fn (mut c Checker) expand_iface_embeds(idecl &ast.InterfaceDecl, level int,
|
||||||
return ares
|
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
|
// returns name and position of variable that needs write lock
|
||||||
// also sets `is_changed` to true (TODO update the name to reflect this?)
|
// 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) {
|
fn (mut c Checker) fail_if_immutable(expr_ ast.Expr) (string, token.Pos) {
|
||||||
|
|
|
@ -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 {}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue