checker: cleanup in infix_expr() (#10368)

pull/10399/head
yuyi 2021-06-09 05:06:29 +08:00 committed by GitHub
parent a2243054a5
commit 49de1f8e64
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 119 additions and 118 deletions

View File

@ -982,66 +982,63 @@ fn (mut c Checker) check_div_mod_by_zero(expr ast.Expr, op_kind token.Kind) {
}
}
pub fn (mut c Checker) infix_expr(mut infix_expr ast.InfixExpr) ast.Type {
// println('checker: infix expr(op $infix_expr.op.str())')
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
}
left_type := c.expr(infix_expr.left)
// left_type = c.unwrap_genric(c.expr(infix_expr.left))
infix_expr.left_type = left_type
left_type := c.expr(node.left)
node.left_type = left_type
c.expected_type = left_type
right_type := c.expr(infix_expr.right)
// right_type = c.unwrap_genric(c.expr(infix_expr.right))
infix_expr.right_type = right_type
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] {
infix_expr.right_type = left_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] {
infix_expr.left_type = right_type
node.left_type = right_type
}
mut right := c.table.get_type_symbol(right_type)
mut right_sym := c.table.get_type_symbol(right_type)
right_final := c.table.get_final_type_symbol(right_type)
mut left := c.table.get_type_symbol(left_type)
mut left_sym := c.table.get_type_symbol(left_type)
left_final := c.table.get_final_type_symbol(left_type)
left_pos := infix_expr.left.position()
right_pos := infix_expr.right.position()
left_pos := node.left.position()
right_pos := node.right.position()
left_right_pos := left_pos.extend(right_pos)
if (left_type.is_ptr() || left.is_pointer()) && infix_expr.op in [.plus, .minus] {
if !c.inside_unsafe && !infix_expr.left.is_auto_deref_var()
&& !infix_expr.right.is_auto_deref_var() {
if (left_type.is_ptr() || left_sym.is_pointer()) && 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_pos)
}
if left_type == ast.voidptr_type {
c.error('`$infix_expr.op` cannot be used with `voidptr`', left_pos)
c.error('`$node.op` cannot be used with `voidptr`', left_pos)
}
}
mut return_type := left_type
if infix_expr.op != .key_is {
match mut infix_expr.left {
if node.op != .key_is {
match mut node.left {
ast.Ident, ast.SelectorExpr {
if infix_expr.left.is_mut {
c.error('remove unnecessary `mut`', infix_expr.left.mut_pos)
if node.left.is_mut {
c.error('remove unnecessary `mut`', node.left.mut_pos)
}
}
else {}
}
}
eq_ne := infix_expr.op in [.eq, .ne]
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 infix_expr.op {
match node.op {
// .eq, .ne, .gt, .lt, .ge, .le, .and, .logical_or, .dot, .key_as, .right_shift {}
.eq, .ne {
is_mismatch := (left.kind == .alias && right.kind in [.struct_, .array, .sum_type])
|| (right.kind == .alias && left.kind in [.struct_, .array, .sum_type])
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 `$infix_expr.op` operation',
c.error('possible type mismatch of compared values of `$node.op` operation',
left_right_pos)
}
}
@ -1051,48 +1048,48 @@ pub fn (mut c Checker) infix_expr(mut infix_expr ast.InfixExpr) ast.Type {
elem_type := right_final.array_info().elem_type
// if left_default.kind != right_sym.kind {
c.check_expected(left_type, elem_type) or {
c.error('left operand to `$infix_expr.op` does not match the array element type: $err.msg',
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 `$infix_expr.op` does not match the map key type: $err.msg',
c.error('left operand to `$node.op` does not match the map key type: $err.msg',
left_right_pos)
}
infix_expr.left_type = map_info.key_type
node.left_type = map_info.key_type
}
.string {
c.warn('use `str.contains(substr)` instead of `substr in str`', left_right_pos)
c.check_expected(left_type, right_type) or {
c.error('left operand to `$infix_expr.op` does not match: $err.msg',
c.error('left operand to `$node.op` does not match: $err.msg',
left_right_pos)
}
}
else {
c.error('`$infix_expr.op.str()` can only be used with an array/map/string',
infix_expr.pos)
c.error('`$node.op.str()` can only be used with an array/map/string',
node.pos)
}
}
return ast.bool_type
}
.plus, .minus, .mul, .div, .mod, .xor, .amp, .pipe { // binary operators that expect matching types
if right.info is ast.Alias && (right.info as ast.Alias).language != .c
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.get_type_symbol((right.info as ast.Alias).parent_type).is_primitive() {
right = c.table.get_type_symbol((right.info as ast.Alias).parent_type)
&& c.table.get_type_symbol((right_sym.info as ast.Alias).parent_type).is_primitive() {
right_sym = c.table.get_type_symbol((right_sym.info as ast.Alias).parent_type)
}
if left.info is ast.Alias && (left.info as ast.Alias).language != .c
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.get_type_symbol((left.info as ast.Alias).parent_type).is_primitive() {
left = c.table.get_type_symbol((left.info as ast.Alias).parent_type)
&& c.table.get_type_symbol((left_sym.info as ast.Alias).parent_type).is_primitive() {
left_sym = c.table.get_type_symbol((left_sym.info as ast.Alias).parent_type)
}
// Check if the alias type is not a primitive then allow using operator overloading for aliased `arrays` and `maps`
if left.kind == .alias && left.info is ast.Alias
&& !(c.table.get_type_symbol((left.info as ast.Alias).parent_type).is_primitive()) {
if left.has_method(infix_expr.op.str()) {
if method := left.find_method(infix_expr.op.str()) {
if left_sym.kind == .alias && left_sym.info is ast.Alias
&& !(c.table.get_type_symbol((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
@ -1101,16 +1098,16 @@ pub fn (mut c Checker) infix_expr(mut infix_expr ast.InfixExpr) ast.Type {
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` $infix_expr.op.str() `$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 right.kind == .alias && right.info is ast.Alias
&& !(c.table.get_type_symbol((right.info as ast.Alias).parent_type).is_primitive()) {
if right.has_method(infix_expr.op.str()) {
if method := right.find_method(infix_expr.op.str()) {
} else if right_sym.kind == .alias && right_sym.info is ast.Alias
&& !(c.table.get_type_symbol((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
@ -1119,16 +1116,16 @@ pub fn (mut c Checker) infix_expr(mut infix_expr ast.InfixExpr) ast.Type {
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` $infix_expr.op.str() `$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 left.kind in [.array, .array_fixed, .map, .struct_] {
if left.has_method(infix_expr.op.str()) {
if method := left.find_method(infix_expr.op.str()) {
if left_sym.kind in [.array, .array_fixed, .map, .struct_] {
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
@ -1137,15 +1134,15 @@ pub fn (mut c Checker) infix_expr(mut infix_expr ast.InfixExpr) ast.Type {
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` $infix_expr.op.str() `$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 right.kind in [.array, .array_fixed, .map, .struct_] {
if right.has_method(infix_expr.op.str()) {
if method := right.find_method(infix_expr.op.str()) {
} else if right_sym.kind in [.array, .array_fixed, .map, .struct_] {
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
@ -1154,7 +1151,7 @@ pub fn (mut c Checker) infix_expr(mut infix_expr ast.InfixExpr) ast.Type {
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` $infix_expr.op.str() `$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)
@ -1168,59 +1165,64 @@ pub fn (mut c Checker) infix_expr(mut infix_expr ast.InfixExpr) ast.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('`$infix_expr.op` cannot be used with `$s`', infix_expr.pos)
c.error('`$node.op` cannot be used with `$s`', node.pos)
} else if promoted_type.is_float() {
if infix_expr.op in [.mod, .xor, .amp, .pipe] {
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.name } else { right.name }
if infix_expr.op == .mod {
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 `$infix_expr.op.str()` cannot be non-integer type `$name`',
c.error('$side type of `$node.op.str()` cannot be non-integer type `$name`',
pos)
}
}
}
if infix_expr.op in [.div, .mod] {
c.check_div_mod_by_zero(infix_expr.right, infix_expr.op)
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.kind in [.array, .array_fixed] && right.kind in [.array, .array_fixed] {
c.error('only `==` and `!=` are defined on arrays', infix_expr.pos)
} else if left.kind == .struct_ && right.kind == .struct_ && infix_expr.op in [.eq, .lt] {
if !(left.has_method(infix_expr.op.str()) && right.has_method(infix_expr.op.str())) {
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_ && 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 {
c.error('undefined operation `$left_name` $infix_expr.op.str() `$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 left.kind == .struct_ && right.kind == .struct_ {
if !left.has_method('<') && infix_expr.op in [.ge, .le] {
c.error('cannot use `$infix_expr.op` as `<` operator method is not defined',
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.has_method('<') && infix_expr.op == .gt {
} else if !left_sym.has_method('<') && node.op == .gt {
c.error('cannot use `>` as `<=` operator method is not defined', left_right_pos)
}
}
}
.left_shift {
if left_final.kind == .array {
if !infix_expr.is_stmt {
c.error('array append cannot be used in an expression', infix_expr.pos)
if !node.is_stmt {
c.error('array append cannot be used in an expression', node.pos)
}
// `array << elm`
c.check_expr_opt_call(infix_expr.right, right_type)
infix_expr.auto_locked, _ = c.fail_if_immutable(infix_expr.left)
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(left_type)
left_value_sym := c.table.get_type_symbol(left_value_type)
if left_value_sym.kind == .interface_ {
@ -1244,7 +1246,7 @@ pub fn (mut c Checker) infix_expr(mut infix_expr ast.InfixExpr) ast.Type {
// []T << []T
return ast.void_type
}
c.error('cannot append `$right.name` to `$left.name`', right_pos)
c.error('cannot append `$right_sym.name` to `$left_sym.name`', right_pos)
return ast.void_type
} else {
return c.check_shift(left_type, right_type, left_pos, right_pos)
@ -1254,7 +1256,7 @@ pub fn (mut c Checker) infix_expr(mut infix_expr ast.InfixExpr) ast.Type {
return c.check_shift(left_type, right_type, left_pos, right_pos)
}
.key_is, .not_is {
right_expr := infix_expr.right
right_expr := node.right
mut typ := match right_expr {
ast.TypeNode {
right_expr.typ
@ -1269,90 +1271,89 @@ pub fn (mut c Checker) infix_expr(mut infix_expr ast.InfixExpr) ast.Type {
}
if typ != ast.Type(0) {
typ_sym := c.table.get_type_symbol(typ)
op := infix_expr.op.str()
op := node.op.str()
if typ_sym.kind == .placeholder {
c.error('$op: type `$typ_sym.name` does not exist', right_expr.position())
}
if left.kind !in [.interface_, .sum_type] {
c.error('`$op` can only be used with interfaces and sum types', infix_expr.pos)
} else if mut left.info is ast.SumType {
if typ !in left.info.variants {
c.error('`$left.name` has no variant `$right.name`', infix_expr.pos)
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.kind == .chan {
chan_info := left.chan_info()
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.name` on `$left.name`', right_pos)
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(infix_expr.right)
c.fail_if_immutable(node.right)
}
if elem_type.is_ptr() && !right_type.is_ptr() {
c.error('cannot push non-reference `$right.name` on `$left.name`',
c.error('cannot push non-reference `$right_sym.name` on `$left_sym.name`',
right_pos)
}
c.stmts(infix_expr.or_block.stmts)
c.stmts(node.or_block.stmts)
} else {
c.error('cannot push on non-channel `$left.name`', left_pos)
c.error('cannot push on non-channel `$left_sym.name`', left_pos)
}
return ast.void_type
}
.and, .logical_or {
if !c.pref.translated {
if infix_expr.left_type != ast.bool_type_idx {
c.error('left operand for `$infix_expr.op` is not a boolean', infix_expr.left.position())
if node.left_type != ast.bool_type_idx {
c.error('left operand for `$node.op` is not a boolean', node.left.position())
}
if infix_expr.right_type != ast.bool_type_idx {
c.error('right operand for `$infix_expr.op` is not a boolean', infix_expr.right.position())
if node.right_type != ast.bool_type_idx {
c.error('right operand for `$node.op` is not a boolean', node.right.position())
}
}
if mut infix_expr.left is ast.InfixExpr {
if infix_expr.left.op != infix_expr.op && infix_expr.left.op in [.logical_or, .and] {
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',
infix_expr.pos)
node.pos)
}
}
}
else {}
}
// TODO: Absorb this block into the above single side check block to accelerate.
if left_type == ast.bool_type && infix_expr.op !in [.eq, .ne, .logical_or, .and] {
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 `&&`',
infix_expr.pos)
} else if left_type == ast.string_type
&& infix_expr.op !in [.plus, .eq, .ne, .lt, .gt, .le, .ge] {
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 `+`',
infix_expr.pos)
} else if left.kind == .enum_ && right.kind == .enum_ && !eq_ne {
left_enum := left.info as ast.Enum
right_enum := right.info as ast.Enum
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 infix_expr.op !in [.pipe, .amp] {
if node.op !in [.pipe, .amp] {
c.error('only `==`, `!=`, `|` and `&` are defined on `[flag]` tagged `enum`, use an explicit cast to `int` if needed',
infix_expr.pos)
node.pos)
}
} else if !c.pref.translated {
// Regular enums
c.error('only `==` and `!=` are defined on `enum`, use an explicit cast to `int` if needed',
infix_expr.pos)
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 `$infix_expr.op` with `$left.name`', infix_expr.pos)
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 `$infix_expr.op` with `$right.name`', infix_expr.pos)
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)
@ -1371,17 +1372,17 @@ pub fn (mut c Checker) infix_expr(mut infix_expr ast.InfixExpr) ast.Type {
// pointer arithmetic is fine, it is checked in other places
return return_type
}
c.error('infix expr: cannot use `$right.name` (right expression) as `$left.name`',
c.error('infix expr: cannot use `$right_sym.name` (right expression) as `$left_sym.name`',
left_right_pos)
}
/*
if (infix_expr.left is ast.InfixExpr &&
(infix_expr.left as ast.InfixExpr).op == .inc) ||
(infix_expr.right is ast.InfixExpr && (infix_expr.right as ast.InfixExpr).op == .inc) {
c.warn('`++` and `--` are statements, not expressions', infix_expr.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 infix_expr.op.is_relational() { ast.bool_type } else { return_type }
return if node.op.is_relational() { ast.bool_type } else { return_type }
}
// returns name and position of variable that needs write lock