checker: split up checker.v: fn.v, if.v, interface.v, match.v
parent
ea1f398f90
commit
ee6c0a0691
File diff suppressed because it is too large
Load Diff
|
@ -568,3 +568,235 @@ fn (mut c Checker) comptime_if_branch(cond ast.Expr, pos token.Position) bool {
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fn (mut c Checker) check_map_and_filter(is_map bool, elem_typ ast.Type, node ast.CallExpr) {
|
||||
if node.args.len != 1 {
|
||||
c.error('expected 1 argument, but got $node.args.len', node.pos)
|
||||
// Finish early so that it doesn't fail later
|
||||
return
|
||||
}
|
||||
elem_sym := c.table.get_type_symbol(elem_typ)
|
||||
arg_expr := node.args[0].expr
|
||||
match arg_expr {
|
||||
ast.AnonFn {
|
||||
if arg_expr.decl.params.len > 1 {
|
||||
c.error('function needs exactly 1 argument', arg_expr.decl.pos)
|
||||
} else if is_map && (arg_expr.decl.return_type == ast.void_type
|
||||
|| arg_expr.decl.params[0].typ != elem_typ) {
|
||||
c.error('type mismatch, should use `fn(a $elem_sym.name) T {...}`', arg_expr.decl.pos)
|
||||
} else if !is_map && (arg_expr.decl.return_type != ast.bool_type
|
||||
|| arg_expr.decl.params[0].typ != elem_typ) {
|
||||
c.error('type mismatch, should use `fn(a $elem_sym.name) bool {...}`',
|
||||
arg_expr.decl.pos)
|
||||
}
|
||||
}
|
||||
ast.Ident {
|
||||
if arg_expr.kind == .function {
|
||||
func := c.table.find_fn(arg_expr.name) or {
|
||||
c.error('$arg_expr.name does not exist', arg_expr.pos)
|
||||
return
|
||||
}
|
||||
if func.params.len > 1 {
|
||||
c.error('function needs exactly 1 argument', node.pos)
|
||||
} else if is_map
|
||||
&& (func.return_type == ast.void_type || func.params[0].typ != elem_typ) {
|
||||
c.error('type mismatch, should use `fn(a $elem_sym.name) T {...}`',
|
||||
arg_expr.pos)
|
||||
} else if !is_map
|
||||
&& (func.return_type != ast.bool_type || func.params[0].typ != elem_typ) {
|
||||
c.error('type mismatch, should use `fn(a $elem_sym.name) bool {...}`',
|
||||
arg_expr.pos)
|
||||
}
|
||||
} else if arg_expr.kind == .variable {
|
||||
if arg_expr.obj is ast.Var {
|
||||
expr := arg_expr.obj.expr
|
||||
if expr is ast.AnonFn {
|
||||
// copied from above
|
||||
if expr.decl.params.len > 1 {
|
||||
c.error('function needs exactly 1 argument', expr.decl.pos)
|
||||
} else if is_map && (expr.decl.return_type == ast.void_type
|
||||
|| expr.decl.params[0].typ != elem_typ) {
|
||||
c.error('type mismatch, should use `fn(a $elem_sym.name) T {...}`',
|
||||
expr.decl.pos)
|
||||
} else if !is_map && (expr.decl.return_type != ast.bool_type
|
||||
|| expr.decl.params[0].typ != elem_typ) {
|
||||
c.error('type mismatch, should use `fn(a $elem_sym.name) bool {...}`',
|
||||
expr.decl.pos)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
// NOTE: bug accessing typ field on sumtype variant (not cast properly).
|
||||
// leaving this here as the resulting issue is notoriously hard to debug.
|
||||
// if !is_map && arg_expr.info.typ != ast.bool_type {
|
||||
if !is_map && arg_expr.var_info().typ != ast.bool_type {
|
||||
c.error('type mismatch, should be bool', arg_expr.pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
ast.CallExpr {
|
||||
if is_map && arg_expr.return_type in [ast.void_type, 0] {
|
||||
c.error('type mismatch, `$arg_expr.name` does not return anything', arg_expr.pos)
|
||||
} else if !is_map && arg_expr.return_type != ast.bool_type {
|
||||
c.error('type mismatch, `$arg_expr.name` must return a bool', arg_expr.pos)
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut c Checker) map_builtin_method_call(mut node ast.CallExpr, left_type ast.Type, left_sym ast.TypeSymbol) ast.Type {
|
||||
method_name := node.name
|
||||
mut ret_type := ast.void_type
|
||||
match method_name {
|
||||
'clone', 'move' {
|
||||
if method_name[0] == `m` {
|
||||
c.fail_if_immutable(node.left)
|
||||
}
|
||||
if node.left.is_auto_deref_var() {
|
||||
ret_type = left_type.deref()
|
||||
} else {
|
||||
ret_type = left_type
|
||||
}
|
||||
}
|
||||
'keys' {
|
||||
info := left_sym.info as ast.Map
|
||||
typ := c.table.find_or_register_array(info.key_type)
|
||||
ret_type = ast.Type(typ)
|
||||
}
|
||||
'delete' {
|
||||
c.fail_if_immutable(node.left)
|
||||
if node.args.len != 1 {
|
||||
c.error('expected 1 argument, but got $node.args.len', node.pos)
|
||||
}
|
||||
info := left_sym.info as ast.Map
|
||||
arg_type := c.expr(node.args[0].expr)
|
||||
c.check_expected_call_arg(arg_type, info.key_type, node.language, node.args[0]) or {
|
||||
c.error('$err.msg in argument 1 to `Map.delete`', node.args[0].pos)
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
node.receiver_type = left_type.ref()
|
||||
node.return_type = ret_type
|
||||
return node.return_type
|
||||
}
|
||||
|
||||
fn (mut c Checker) array_builtin_method_call(mut node ast.CallExpr, left_type ast.Type, left_sym ast.TypeSymbol) ast.Type {
|
||||
method_name := node.name
|
||||
mut elem_typ := ast.void_type
|
||||
if method_name == 'slice' && !c.is_builtin_mod {
|
||||
c.error('.slice() is a private method, use `x[start..end]` instead', node.pos)
|
||||
}
|
||||
array_info := left_sym.info as ast.Array
|
||||
elem_typ = array_info.elem_type
|
||||
if method_name in ['filter', 'map', 'any', 'all'] {
|
||||
// position of `it` doesn't matter
|
||||
scope_register_it(mut node.scope, node.pos, elem_typ)
|
||||
} else if method_name == 'sort' {
|
||||
if node.left is ast.CallExpr {
|
||||
c.error('the `sort()` method can be called only on mutable receivers, but `$node.left` is a call expression',
|
||||
node.pos)
|
||||
}
|
||||
c.fail_if_immutable(node.left)
|
||||
// position of `a` and `b` doesn't matter, they're the same
|
||||
scope_register_a_b(mut node.scope, node.pos, elem_typ)
|
||||
|
||||
if node.args.len > 1 {
|
||||
c.error('expected 0 or 1 argument, but got $node.args.len', node.pos)
|
||||
} else if node.args.len == 1 {
|
||||
if node.args[0].expr is ast.InfixExpr {
|
||||
if node.args[0].expr.op !in [.gt, .lt] {
|
||||
c.error('`.sort()` can only use `<` or `>` comparison', node.pos)
|
||||
}
|
||||
left_name := '${node.args[0].expr.left}'[0]
|
||||
right_name := '${node.args[0].expr.right}'[0]
|
||||
if left_name !in [`a`, `b`] || right_name !in [`a`, `b`] {
|
||||
c.error('`.sort()` can only use `a` or `b` as argument, e.g. `arr.sort(a < b)`',
|
||||
node.pos)
|
||||
} else if left_name == right_name {
|
||||
c.error('`.sort()` cannot use same argument', node.pos)
|
||||
}
|
||||
if (node.args[0].expr.left !is ast.Ident
|
||||
&& node.args[0].expr.left !is ast.SelectorExpr
|
||||
&& node.args[0].expr.left !is ast.IndexExpr)
|
||||
|| (node.args[0].expr.right !is ast.Ident
|
||||
&& node.args[0].expr.right !is ast.SelectorExpr
|
||||
&& node.args[0].expr.right !is ast.IndexExpr) {
|
||||
c.error('`.sort()` can only use ident, index or selector as argument, \ne.g. `arr.sort(a < b)`, `arr.sort(a.id < b.id)`, `arr.sort(a[0] < b[0])`',
|
||||
node.pos)
|
||||
}
|
||||
} else {
|
||||
c.error(
|
||||
'`.sort()` requires a `<` or `>` comparison as the first and only argument' +
|
||||
'\ne.g. `users.sort(a.id < b.id)`', node.pos)
|
||||
}
|
||||
} else if !(c.table.get_type_symbol(elem_typ).has_method('<')
|
||||
|| c.table.unalias_num_type(elem_typ) in [ast.int_type, ast.int_type.ref(), ast.string_type, ast.string_type.ref(), ast.i8_type, ast.i16_type, ast.i64_type, ast.byte_type, ast.rune_type, ast.u16_type, ast.u32_type, ast.u64_type, ast.f32_type, ast.f64_type, ast.char_type, ast.bool_type, ast.float_literal_type, ast.int_literal_type]) {
|
||||
c.error('custom sorting condition must be supplied for type `${c.table.type_to_str(elem_typ)}`',
|
||||
node.pos)
|
||||
}
|
||||
} else if method_name == 'wait' {
|
||||
elem_sym := c.table.get_type_symbol(elem_typ)
|
||||
if elem_sym.kind == .thread {
|
||||
if node.args.len != 0 {
|
||||
c.error('`.wait()` does not have any arguments', node.args[0].pos)
|
||||
}
|
||||
thread_ret_type := elem_sym.thread_info().return_type
|
||||
if thread_ret_type.has_flag(.optional) {
|
||||
c.error('`.wait()` cannot be called for an array when thread functions return optionals. Iterate over the arrays elements instead and handle each returned optional with `or`.',
|
||||
node.pos)
|
||||
}
|
||||
node.return_type = c.table.find_or_register_array(thread_ret_type)
|
||||
} else {
|
||||
c.error('`$left_sym.name` has no method `wait()` (only thread handles and arrays of them have)',
|
||||
node.left.position())
|
||||
}
|
||||
}
|
||||
// map/filter are supposed to have 1 arg only
|
||||
mut arg_type := left_type
|
||||
for arg in node.args {
|
||||
arg_type = c.check_expr_opt_call(arg.expr, c.expr(arg.expr))
|
||||
}
|
||||
if method_name == 'map' {
|
||||
// check fn
|
||||
c.check_map_and_filter(true, elem_typ, node)
|
||||
arg_sym := c.table.get_type_symbol(arg_type)
|
||||
ret_type := match arg_sym.info {
|
||||
ast.FnType { arg_sym.info.func.return_type }
|
||||
else { arg_type }
|
||||
}
|
||||
node.return_type = c.table.find_or_register_array(c.unwrap_generic(ret_type))
|
||||
} else if method_name == 'filter' {
|
||||
// check fn
|
||||
c.check_map_and_filter(false, elem_typ, node)
|
||||
} else if method_name in ['any', 'all'] {
|
||||
c.check_map_and_filter(false, elem_typ, node)
|
||||
node.return_type = ast.bool_type
|
||||
} else if method_name == 'clone' {
|
||||
// need to return `array_xxx` instead of `array`
|
||||
// in ['clone', 'str'] {
|
||||
node.receiver_type = left_type.ref()
|
||||
if node.left.is_auto_deref_var() {
|
||||
node.return_type = left_type.deref()
|
||||
} else {
|
||||
node.return_type = node.receiver_type.set_nr_muls(0)
|
||||
}
|
||||
} else if method_name == 'sort' {
|
||||
node.return_type = ast.void_type
|
||||
} else if method_name == 'contains' {
|
||||
// c.warn('use `value in arr` instead of `arr.contains(value)`', node.pos)
|
||||
node.return_type = ast.bool_type
|
||||
} else if method_name == 'index' {
|
||||
node.return_type = ast.int_type
|
||||
} else if method_name in ['first', 'last', 'pop'] {
|
||||
node.return_type = array_info.elem_type
|
||||
if method_name == 'pop' {
|
||||
c.fail_if_immutable(node.left)
|
||||
node.receiver_type = left_type.ref()
|
||||
} else {
|
||||
node.receiver_type = left_type
|
||||
}
|
||||
}
|
||||
return node.return_type
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,298 @@
|
|||
// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license that can be found in the LICENSE file.
|
||||
module checker
|
||||
|
||||
import v.ast
|
||||
import v.pref
|
||||
|
||||
pub fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type {
|
||||
if_kind := if node.is_comptime { '\$if' } else { 'if' }
|
||||
mut node_is_expr := false
|
||||
if node.branches.len > 0 && node.has_else {
|
||||
stmts := node.branches[0].stmts
|
||||
if stmts.len > 0 && stmts[stmts.len - 1] is ast.ExprStmt
|
||||
&& (stmts[stmts.len - 1] as ast.ExprStmt).typ != ast.void_type {
|
||||
node_is_expr = true
|
||||
}
|
||||
}
|
||||
if c.expected_type == ast.void_type && node_is_expr {
|
||||
c.expected_type = c.expected_or_type
|
||||
}
|
||||
expr_required := c.expected_type != ast.void_type
|
||||
former_expected_type := c.expected_type
|
||||
node.typ = ast.void_type
|
||||
mut nbranches_with_return := 0
|
||||
mut nbranches_without_return := 0
|
||||
mut should_skip := false // Whether the current branch should be skipped
|
||||
mut found_branch := false // Whether a matching branch was found- skip the rest
|
||||
mut is_comptime_type_is_expr := false // if `$if T is string`
|
||||
for i in 0 .. node.branches.len {
|
||||
mut branch := node.branches[i]
|
||||
if branch.cond is ast.ParExpr {
|
||||
c.error('unnecessary `()` in `$if_kind` condition, use `$if_kind expr {` instead of `$if_kind (expr) {`.',
|
||||
branch.pos)
|
||||
}
|
||||
if !node.has_else || i < node.branches.len - 1 {
|
||||
if node.is_comptime {
|
||||
should_skip = c.comptime_if_branch(branch.cond, branch.pos)
|
||||
node.branches[i].pkg_exist = !should_skip
|
||||
} else {
|
||||
// check condition type is boolean
|
||||
c.expected_type = ast.bool_type
|
||||
cond_typ := c.expr(branch.cond)
|
||||
if (cond_typ.idx() != ast.bool_type_idx || cond_typ.has_flag(.optional))
|
||||
&& !c.pref.translated {
|
||||
c.error('non-bool type `${c.table.type_to_str(cond_typ)}` used as if condition',
|
||||
branch.cond.position())
|
||||
}
|
||||
}
|
||||
}
|
||||
if node.is_comptime { // Skip checking if needed
|
||||
// smartcast field type on comptime if
|
||||
mut comptime_field_name := ''
|
||||
if branch.cond is ast.InfixExpr {
|
||||
if branch.cond.op == .key_is {
|
||||
if branch.cond.right !is ast.TypeNode {
|
||||
c.error('invalid `\$if` condition: expected a type', branch.cond.right.position())
|
||||
return 0
|
||||
}
|
||||
got_type := c.unwrap_generic((branch.cond.right as ast.TypeNode).typ)
|
||||
sym := c.table.get_type_symbol(got_type)
|
||||
if sym.kind == .placeholder || got_type.has_flag(.generic) {
|
||||
c.error('unknown type `$sym.name`', branch.cond.right.position())
|
||||
}
|
||||
left := branch.cond.left
|
||||
if left is ast.SelectorExpr {
|
||||
comptime_field_name = left.expr.str()
|
||||
c.comptime_fields_type[comptime_field_name] = got_type
|
||||
is_comptime_type_is_expr = true
|
||||
} else if branch.cond.right is ast.TypeNode && left is ast.TypeNode
|
||||
&& sym.kind == .interface_ {
|
||||
is_comptime_type_is_expr = true
|
||||
// is interface
|
||||
checked_type := c.unwrap_generic(left.typ)
|
||||
should_skip = !c.table.does_type_implement_interface(checked_type,
|
||||
got_type)
|
||||
} else if left is ast.TypeNode {
|
||||
is_comptime_type_is_expr = true
|
||||
left_type := c.unwrap_generic(left.typ)
|
||||
if left_type != got_type {
|
||||
should_skip = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cur_skip_flags := c.skip_flags
|
||||
if found_branch {
|
||||
c.skip_flags = true
|
||||
} else if should_skip {
|
||||
c.skip_flags = true
|
||||
should_skip = false // Reset the value of `should_skip` for the next branch
|
||||
} else if !is_comptime_type_is_expr {
|
||||
found_branch = true // If a branch wasn't skipped, the rest must be
|
||||
}
|
||||
if c.fn_level == 0 && c.pref.output_cross_c {
|
||||
// do not skip any of the branches for top level `$if OS {`
|
||||
// statements, in `-os cross` mode
|
||||
found_branch = false
|
||||
c.skip_flags = false
|
||||
c.ct_cond_stack << branch.cond
|
||||
}
|
||||
if !c.skip_flags {
|
||||
if node_is_expr {
|
||||
c.stmts_ending_with_expression(branch.stmts)
|
||||
} else {
|
||||
c.stmts(branch.stmts)
|
||||
}
|
||||
} else if c.pref.output_cross_c {
|
||||
mut is_freestanding_block := false
|
||||
if branch.cond is ast.Ident {
|
||||
if branch.cond.name == 'freestanding' {
|
||||
is_freestanding_block = true
|
||||
}
|
||||
}
|
||||
if is_freestanding_block {
|
||||
branch.stmts = []
|
||||
node.branches[i].stmts = []
|
||||
}
|
||||
if node_is_expr {
|
||||
c.stmts_ending_with_expression(branch.stmts)
|
||||
} else {
|
||||
c.stmts(branch.stmts)
|
||||
}
|
||||
} else if !is_comptime_type_is_expr {
|
||||
node.branches[i].stmts = []
|
||||
}
|
||||
if comptime_field_name.len > 0 {
|
||||
c.comptime_fields_type.delete(comptime_field_name)
|
||||
}
|
||||
c.skip_flags = cur_skip_flags
|
||||
if c.fn_level == 0 && c.pref.output_cross_c && c.ct_cond_stack.len > 0 {
|
||||
c.ct_cond_stack.delete_last()
|
||||
}
|
||||
} else {
|
||||
// smartcast sumtypes and interfaces when using `is`
|
||||
c.smartcast_if_conds(branch.cond, mut branch.scope)
|
||||
if node_is_expr {
|
||||
c.stmts_ending_with_expression(branch.stmts)
|
||||
} else {
|
||||
c.stmts(branch.stmts)
|
||||
}
|
||||
}
|
||||
if expr_required {
|
||||
if branch.stmts.len > 0 && branch.stmts[branch.stmts.len - 1] is ast.ExprStmt {
|
||||
mut last_expr := branch.stmts[branch.stmts.len - 1] as ast.ExprStmt
|
||||
c.expected_type = former_expected_type
|
||||
if c.expected_type.has_flag(.optional) {
|
||||
if node.typ == ast.void_type {
|
||||
node.is_expr = true
|
||||
node.typ = c.expected_type
|
||||
}
|
||||
}
|
||||
if c.expected_type.has_flag(.generic) {
|
||||
if node.typ == ast.void_type {
|
||||
node.is_expr = true
|
||||
node.typ = c.unwrap_generic(c.expected_type)
|
||||
}
|
||||
continue
|
||||
}
|
||||
last_expr.typ = c.expr(last_expr.expr)
|
||||
if !c.check_types(last_expr.typ, node.typ) {
|
||||
if node.typ == ast.void_type {
|
||||
// first branch of if expression
|
||||
node.is_expr = true
|
||||
node.typ = last_expr.typ
|
||||
continue
|
||||
} else if node.typ in [ast.float_literal_type, ast.int_literal_type] {
|
||||
if node.typ == ast.int_literal_type {
|
||||
if last_expr.typ.is_int() || last_expr.typ.is_float() {
|
||||
node.typ = last_expr.typ
|
||||
continue
|
||||
}
|
||||
} else { // node.typ == float_literal
|
||||
if last_expr.typ.is_float() {
|
||||
node.typ = last_expr.typ
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
if last_expr.typ in [ast.float_literal_type, ast.int_literal_type] {
|
||||
if last_expr.typ == ast.int_literal_type {
|
||||
if node.typ.is_int() || node.typ.is_float() {
|
||||
continue
|
||||
}
|
||||
} else { // expr_type == float_literal
|
||||
if node.typ.is_float() {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
if node.is_expr
|
||||
&& c.table.get_type_symbol(former_expected_type).kind == .sum_type {
|
||||
continue
|
||||
}
|
||||
if is_noreturn_callexpr(last_expr.expr) {
|
||||
continue
|
||||
}
|
||||
|
||||
c.error('mismatched types `${c.table.type_to_str(node.typ)}` and `${c.table.type_to_str(last_expr.typ)}`',
|
||||
node.pos)
|
||||
}
|
||||
} else {
|
||||
c.error('`$if_kind` expression requires an expression as the last statement of every branch',
|
||||
branch.pos)
|
||||
}
|
||||
for st in branch.stmts {
|
||||
// must not contain C statements
|
||||
st.check_c_expr() or { c.error('`if` expression branch has $err.msg', st.pos) }
|
||||
}
|
||||
}
|
||||
// Also check for returns inside a comp.if's statements, even if its contents aren't parsed
|
||||
if has_return := c.has_return(branch.stmts) {
|
||||
if has_return {
|
||||
nbranches_with_return++
|
||||
} else {
|
||||
nbranches_without_return++
|
||||
}
|
||||
}
|
||||
}
|
||||
if nbranches_with_return > 0 {
|
||||
if nbranches_with_return == node.branches.len {
|
||||
// if/else... where all branches returned
|
||||
c.returns = true
|
||||
}
|
||||
if !node.has_else {
|
||||
// `if cond { return ... }` means that when cond is false, execution continues
|
||||
c.returns = false
|
||||
}
|
||||
if nbranches_without_return > 0 {
|
||||
// some of the branches did not return
|
||||
c.returns = false
|
||||
}
|
||||
}
|
||||
// if only untyped literals were given default to int/f64
|
||||
if node.typ == ast.int_literal_type {
|
||||
node.typ = ast.int_type
|
||||
} else if node.typ == ast.float_literal_type {
|
||||
node.typ = ast.f64_type
|
||||
}
|
||||
if expr_required && !node.has_else {
|
||||
d := if node.is_comptime { '$' } else { '' }
|
||||
c.error('`$if_kind` expression needs `${d}else` clause', node.pos)
|
||||
}
|
||||
return node.typ
|
||||
}
|
||||
|
||||
fn (mut c Checker) smartcast_if_conds(node ast.Expr, mut scope ast.Scope) {
|
||||
if node is ast.InfixExpr {
|
||||
if node.op == .and {
|
||||
c.smartcast_if_conds(node.left, mut scope)
|
||||
c.smartcast_if_conds(node.right, mut scope)
|
||||
} else if node.op == .key_is {
|
||||
right_expr := node.right
|
||||
mut right_type := match right_expr {
|
||||
ast.TypeNode {
|
||||
right_expr.typ
|
||||
}
|
||||
ast.None {
|
||||
ast.none_type_idx
|
||||
}
|
||||
else {
|
||||
c.error('invalid type `$right_expr`', right_expr.position())
|
||||
ast.Type(0)
|
||||
}
|
||||
}
|
||||
right_type = c.unwrap_generic(right_type)
|
||||
if right_type != ast.Type(0) {
|
||||
left_sym := c.table.get_type_symbol(node.left_type)
|
||||
right_sym := c.table.get_type_symbol(right_type)
|
||||
expr_type := c.unwrap_generic(c.expr(node.left))
|
||||
if left_sym.kind == .interface_ {
|
||||
if right_sym.kind != .interface_ {
|
||||
c.type_implements(right_type, expr_type, node.pos)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
} else if !c.check_types(right_type, expr_type) {
|
||||
expect_str := c.table.type_to_str(right_type)
|
||||
expr_str := c.table.type_to_str(expr_type)
|
||||
c.error('cannot use type `$expect_str` as type `$expr_str`', node.pos)
|
||||
}
|
||||
if node.left in [ast.Ident, ast.SelectorExpr] && node.right is ast.TypeNode {
|
||||
is_variable := if mut node.left is ast.Ident {
|
||||
node.left.kind == .variable
|
||||
} else {
|
||||
true
|
||||
}
|
||||
if is_variable {
|
||||
if left_sym.kind in [.interface_, .sum_type] {
|
||||
c.smartcast(node.left, node.left_type, right_type, mut scope)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if node is ast.Likely {
|
||||
c.smartcast_if_conds(node.expr, mut scope)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,234 @@
|
|||
// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved.
|
||||
// Use of this source code is governed by an MIT license
|
||||
// that can be found in the LICENSE file.
|
||||
module checker
|
||||
|
||||
import v.ast
|
||||
import v.token
|
||||
|
||||
pub fn (mut c Checker) interface_decl(mut node ast.InterfaceDecl) {
|
||||
c.check_valid_pascal_case(node.name, 'interface name', node.pos)
|
||||
mut decl_sym := c.table.get_type_symbol(node.typ)
|
||||
is_js := node.language == .js
|
||||
if mut decl_sym.info is ast.Interface {
|
||||
if node.ifaces.len > 0 {
|
||||
all_ifaces := c.expand_iface_embeds(node, 0, node.ifaces)
|
||||
// eprintln('> node.name: $node.name | node.ifaces.len: $node.ifaces.len | all_ifaces: $all_ifaces.len')
|
||||
node.ifaces = all_ifaces
|
||||
mut emnames := map[string]int{}
|
||||
mut emnames_ds := map[string]bool{}
|
||||
mut emnames_ds_info := map[string]bool{}
|
||||
mut efnames := map[string]int{}
|
||||
mut efnames_ds_info := map[string]bool{}
|
||||
for i, m in node.methods {
|
||||
emnames[m.name] = i
|
||||
emnames_ds[m.name] = true
|
||||
emnames_ds_info[m.name] = true
|
||||
}
|
||||
for i, f in node.fields {
|
||||
efnames[f.name] = i
|
||||
efnames_ds_info[f.name] = true
|
||||
}
|
||||
//
|
||||
for iface in all_ifaces {
|
||||
isym := c.table.get_type_symbol(iface.typ)
|
||||
if isym.kind != .interface_ {
|
||||
c.error('interface `$node.name` tries to embed `$isym.name`, but `$isym.name` is not an interface, but `$isym.kind`',
|
||||
iface.pos)
|
||||
continue
|
||||
}
|
||||
isym_info := isym.info as ast.Interface
|
||||
for f in isym_info.fields {
|
||||
if !efnames_ds_info[f.name] {
|
||||
efnames_ds_info[f.name] = true
|
||||
decl_sym.info.fields << f
|
||||
}
|
||||
}
|
||||
for m in isym_info.methods {
|
||||
if !emnames_ds_info[m.name] {
|
||||
emnames_ds_info[m.name] = true
|
||||
decl_sym.info.methods << m.new_method_with_receiver_type(node.typ)
|
||||
}
|
||||
}
|
||||
for m in isym.methods {
|
||||
if !emnames_ds[m.name] {
|
||||
emnames_ds[m.name] = true
|
||||
decl_sym.methods << m.new_method_with_receiver_type(node.typ)
|
||||
}
|
||||
}
|
||||
if iface_decl := c.table.interfaces[iface.typ] {
|
||||
for f in iface_decl.fields {
|
||||
if f.name in efnames {
|
||||
// already existing method name, check for conflicts
|
||||
ifield := node.fields[efnames[f.name]]
|
||||
if field := c.table.find_field_with_embeds(isym, f.name) {
|
||||
if ifield.typ != field.typ {
|
||||
exp := c.table.type_to_str(ifield.typ)
|
||||
got := c.table.type_to_str(field.typ)
|
||||
c.error('embedded interface `$iface_decl.name` conflicts existing field: `$ifield.name`, expecting type: `$exp`, got type: `$got`',
|
||||
ifield.pos)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
efnames[f.name] = node.fields.len
|
||||
node.fields << f
|
||||
}
|
||||
}
|
||||
for m in iface_decl.methods {
|
||||
if m.name in emnames {
|
||||
// already existing field name, check for conflicts
|
||||
imethod := node.methods[emnames[m.name]]
|
||||
if em_fn := decl_sym.find_method(imethod.name) {
|
||||
if m_fn := isym.find_method(m.name) {
|
||||
msg := c.table.is_same_method(m_fn, em_fn)
|
||||
if msg.len > 0 {
|
||||
em_sig := c.table.fn_signature(em_fn, skip_receiver: true)
|
||||
m_sig := c.table.fn_signature(m_fn, skip_receiver: true)
|
||||
c.error('embedded interface `$iface_decl.name` causes conflict: $msg, for interface method `$em_sig` vs `$m_sig`',
|
||||
imethod.pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
emnames[m.name] = node.methods.len
|
||||
mut new_method := m.new_method_with_receiver_type(node.typ)
|
||||
new_method.pos = iface.pos
|
||||
node.methods << new_method
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for i, method in node.methods {
|
||||
if node.language == .v {
|
||||
c.check_valid_snake_case(method.name, 'method name', method.pos)
|
||||
}
|
||||
c.ensure_type_exists(method.return_type, method.return_type_pos) or { return }
|
||||
if is_js {
|
||||
mtyp := c.table.get_type_symbol(method.return_type)
|
||||
if !mtyp.is_js_compatible() {
|
||||
c.error('method $method.name returns non JS type', method.pos)
|
||||
}
|
||||
}
|
||||
for j, param in method.params {
|
||||
if j == 0 && is_js {
|
||||
continue // no need to check first param
|
||||
}
|
||||
c.ensure_type_exists(param.typ, param.pos) or { return }
|
||||
if param.name in reserved_type_names {
|
||||
c.error('invalid use of reserved type `$param.name` as a parameter name',
|
||||
param.pos)
|
||||
}
|
||||
if is_js {
|
||||
ptyp := c.table.get_type_symbol(param.typ)
|
||||
if !ptyp.is_js_compatible() && !(j == method.params.len - 1
|
||||
&& method.is_variadic) {
|
||||
c.error('method `$method.name` accepts non JS type as parameter',
|
||||
method.pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
for field in node.fields {
|
||||
field_sym := c.table.get_type_symbol(field.typ)
|
||||
if field.name == method.name && field_sym.kind == .function {
|
||||
c.error('type `$decl_sym.name` has both field and method named `$method.name`',
|
||||
method.pos)
|
||||
}
|
||||
}
|
||||
for j in 0 .. i {
|
||||
if method.name == node.methods[j].name {
|
||||
c.error('duplicate method name `$method.name`', method.pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
for i, field in node.fields {
|
||||
if node.language == .v {
|
||||
c.check_valid_snake_case(field.name, 'field name', field.pos)
|
||||
}
|
||||
c.ensure_type_exists(field.typ, field.pos) or { return }
|
||||
if is_js {
|
||||
tsym := c.table.get_type_symbol(field.typ)
|
||||
if !tsym.is_js_compatible() {
|
||||
c.error('field `$field.name` uses non JS type', field.pos)
|
||||
}
|
||||
}
|
||||
if field.typ == node.typ && node.language != .js {
|
||||
c.error('recursive interface fields are not allowed because they cannot be initialised',
|
||||
field.type_pos)
|
||||
}
|
||||
for j in 0 .. i {
|
||||
if field.name == node.fields[j].name {
|
||||
c.error('field name `$field.name` duplicate', field.pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut c Checker) resolve_generic_interface(typ ast.Type, interface_type ast.Type, pos token.Position) ast.Type {
|
||||
utyp := c.unwrap_generic(typ)
|
||||
typ_sym := c.table.get_type_symbol(utyp)
|
||||
mut inter_sym := c.table.get_type_symbol(interface_type)
|
||||
|
||||
if mut inter_sym.info is ast.Interface {
|
||||
if inter_sym.info.is_generic {
|
||||
mut inferred_types := []ast.Type{}
|
||||
generic_names := inter_sym.info.generic_types.map(c.table.get_type_name(it))
|
||||
// inferring interface generic types
|
||||
for gt_name in generic_names {
|
||||
mut inferred_type := ast.void_type
|
||||
for ifield in inter_sym.info.fields {
|
||||
if ifield.typ.has_flag(.generic) && c.table.get_type_name(ifield.typ) == gt_name {
|
||||
if field := c.table.find_field_with_embeds(typ_sym, ifield.name) {
|
||||
inferred_type = field.typ
|
||||
}
|
||||
}
|
||||
}
|
||||
for imethod in inter_sym.info.methods {
|
||||
method := typ_sym.find_method(imethod.name) or {
|
||||
typ_sym.find_method_with_generic_parent(imethod.name) or { ast.Fn{} }
|
||||
}
|
||||
if imethod.return_type.has_flag(.generic) {
|
||||
imret_sym := c.table.get_type_symbol(imethod.return_type)
|
||||
mret_sym := c.table.get_type_symbol(method.return_type)
|
||||
if imret_sym.info is ast.MultiReturn && mret_sym.info is ast.MultiReturn {
|
||||
for i, mr_typ in imret_sym.info.types {
|
||||
if mr_typ.has_flag(.generic)
|
||||
&& c.table.get_type_name(mr_typ) == gt_name {
|
||||
inferred_type = mret_sym.info.types[i]
|
||||
}
|
||||
}
|
||||
} else if c.table.get_type_name(imethod.return_type) == gt_name {
|
||||
mut ret_typ := method.return_type
|
||||
if imethod.return_type.has_flag(.optional) {
|
||||
ret_typ = ret_typ.clear_flag(.optional)
|
||||
}
|
||||
inferred_type = ret_typ
|
||||
}
|
||||
}
|
||||
for i, iparam in imethod.params {
|
||||
param := method.params[i] or { ast.Param{} }
|
||||
if iparam.typ.has_flag(.generic)
|
||||
&& c.table.get_type_name(iparam.typ) == gt_name {
|
||||
inferred_type = param.typ
|
||||
}
|
||||
}
|
||||
}
|
||||
if inferred_type == ast.void_type {
|
||||
c.error('could not infer generic type `$gt_name` in interface', pos)
|
||||
return interface_type
|
||||
}
|
||||
inferred_types << inferred_type
|
||||
}
|
||||
// add concrete types to method
|
||||
for imethod in inter_sym.info.methods {
|
||||
if inferred_types !in c.table.fn_generic_types[imethod.name] {
|
||||
c.table.fn_generic_types[imethod.name] << inferred_types
|
||||
}
|
||||
}
|
||||
inter_sym.info.concrete_types = inferred_types
|
||||
return c.table.unwrap_generic_type(interface_type, generic_names, inter_sym.info.concrete_types)
|
||||
}
|
||||
}
|
||||
return interface_type
|
||||
}
|
|
@ -0,0 +1,357 @@
|
|||
module checker
|
||||
|
||||
import v.ast
|
||||
import v.pref
|
||||
import v.util
|
||||
import strings
|
||||
|
||||
pub fn (mut c Checker) match_expr(mut node ast.MatchExpr) ast.Type {
|
||||
node.is_expr = c.expected_type != ast.void_type
|
||||
node.expected_type = c.expected_type
|
||||
cond_type := c.expr(node.cond)
|
||||
// we setting this here rather than at the end of the method
|
||||
// since it is used in c.match_exprs() it saves checking twice
|
||||
node.cond_type = c.table.mktyp(cond_type)
|
||||
c.ensure_type_exists(node.cond_type, node.pos) or { return ast.void_type }
|
||||
c.check_expr_opt_call(node.cond, cond_type)
|
||||
cond_type_sym := c.table.get_type_symbol(cond_type)
|
||||
node.is_sum_type = cond_type_sym.kind in [.interface_, .sum_type]
|
||||
c.match_exprs(mut node, cond_type_sym)
|
||||
c.expected_type = cond_type
|
||||
mut first_iteration := true
|
||||
mut ret_type := ast.void_type
|
||||
mut nbranches_with_return := 0
|
||||
mut nbranches_without_return := 0
|
||||
for branch in node.branches {
|
||||
if node.is_expr {
|
||||
c.stmts_ending_with_expression(branch.stmts)
|
||||
} else {
|
||||
c.stmts(branch.stmts)
|
||||
}
|
||||
if node.is_expr {
|
||||
if branch.stmts.len > 0 {
|
||||
// ignore last statement - workaround
|
||||
// currently the last statement in a match branch does not have an
|
||||
// expected value set, so e.g. IfExpr.is_expr is not set.
|
||||
// probably any mismatch will be caught by not producing a value instead
|
||||
for st in branch.stmts[0..branch.stmts.len - 1] {
|
||||
// must not contain C statements
|
||||
st.check_c_expr() or {
|
||||
c.error('`match` expression branch has $err.msg', st.pos)
|
||||
}
|
||||
}
|
||||
} else if ret_type != ast.void_type {
|
||||
c.error('`match` expression requires an expression as the last statement of every branch',
|
||||
branch.branch_pos)
|
||||
}
|
||||
}
|
||||
// If the last statement is an expression, return its type
|
||||
if branch.stmts.len > 0 {
|
||||
mut stmt := branch.stmts[branch.stmts.len - 1]
|
||||
match mut stmt {
|
||||
ast.ExprStmt {
|
||||
if node.is_expr {
|
||||
c.expected_type = node.expected_type
|
||||
}
|
||||
expr_type := c.expr(stmt.expr)
|
||||
if first_iteration {
|
||||
if node.is_expr && (node.expected_type.has_flag(.optional)
|
||||
|| c.table.type_kind(node.expected_type) == .sum_type) {
|
||||
ret_type = node.expected_type
|
||||
} else {
|
||||
ret_type = expr_type
|
||||
}
|
||||
stmt.typ = expr_type
|
||||
} else if node.is_expr && ret_type.idx() != expr_type.idx() {
|
||||
if !c.check_types(ret_type, expr_type)
|
||||
&& !c.check_types(expr_type, ret_type) {
|
||||
ret_sym := c.table.get_type_symbol(ret_type)
|
||||
is_noreturn := is_noreturn_callexpr(stmt.expr)
|
||||
if !(node.is_expr && ret_sym.kind == .sum_type) && !is_noreturn {
|
||||
c.error('return type mismatch, it should be `$ret_sym.name`',
|
||||
stmt.expr.position())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if node.is_expr && ret_type != ast.void_type {
|
||||
c.error('`match` expression requires an expression as the last statement of every branch',
|
||||
stmt.pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
first_iteration = false
|
||||
if has_return := c.has_return(branch.stmts) {
|
||||
if has_return {
|
||||
nbranches_with_return++
|
||||
} else {
|
||||
nbranches_without_return++
|
||||
}
|
||||
}
|
||||
}
|
||||
if nbranches_with_return > 0 {
|
||||
if nbranches_with_return == node.branches.len {
|
||||
// an exhaustive match, and all branches returned
|
||||
c.returns = true
|
||||
}
|
||||
if nbranches_without_return > 0 {
|
||||
// some of the branches did not return
|
||||
c.returns = false
|
||||
}
|
||||
}
|
||||
// if ret_type != ast.void_type {
|
||||
// node.is_expr = c.expected_type != ast.void_type
|
||||
// node.expected_type = c.expected_type
|
||||
// }
|
||||
node.return_type = ret_type
|
||||
cond_var := c.get_base_name(&node.cond)
|
||||
if cond_var != '' {
|
||||
mut cond_is_auto_heap := false
|
||||
for branch in node.branches {
|
||||
if v := branch.scope.find_var(cond_var) {
|
||||
if v.is_auto_heap {
|
||||
cond_is_auto_heap = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if cond_is_auto_heap {
|
||||
for branch in node.branches {
|
||||
mut v := branch.scope.find_var(cond_var) or { continue }
|
||||
v.is_auto_heap = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret_type
|
||||
}
|
||||
|
||||
fn (mut c Checker) match_exprs(mut node ast.MatchExpr, cond_type_sym ast.TypeSymbol) {
|
||||
// branch_exprs is a histogram of how many times
|
||||
// an expr was used in the match
|
||||
mut branch_exprs := map[string]int{}
|
||||
for branch_i, _ in node.branches {
|
||||
mut branch := node.branches[branch_i]
|
||||
mut expr_types := []ast.TypeNode{}
|
||||
for k, expr in branch.exprs {
|
||||
mut key := ''
|
||||
if expr is ast.RangeExpr {
|
||||
mut low := i64(0)
|
||||
mut high := i64(0)
|
||||
c.expected_type = node.expected_type
|
||||
low_expr := expr.low
|
||||
high_expr := expr.high
|
||||
if low_expr is ast.IntegerLiteral {
|
||||
if high_expr is ast.IntegerLiteral
|
||||
&& (cond_type_sym.is_int() || cond_type_sym.info is ast.Enum) {
|
||||
low = low_expr.val.i64()
|
||||
high = high_expr.val.i64()
|
||||
if low > high {
|
||||
c.error('start value is higher than end value', branch.pos)
|
||||
}
|
||||
} else {
|
||||
c.error('mismatched range types', low_expr.pos)
|
||||
}
|
||||
} else if low_expr is ast.CharLiteral {
|
||||
if high_expr is ast.CharLiteral && cond_type_sym.kind in [.byte, .char, .rune] {
|
||||
low = low_expr.val[0]
|
||||
high = high_expr.val[0]
|
||||
if low > high {
|
||||
c.error('start value is higher than end value', branch.pos)
|
||||
}
|
||||
} else {
|
||||
c.error('mismatched range types', low_expr.pos)
|
||||
}
|
||||
} else {
|
||||
typ := c.table.type_to_str(c.expr(expr.low))
|
||||
c.error('cannot use type `$typ` in match range', branch.pos)
|
||||
}
|
||||
high_low_cutoff := 1000
|
||||
if high - low > high_low_cutoff {
|
||||
c.warn('more than $high_low_cutoff possibilities ($low ... $high) in match range',
|
||||
branch.pos)
|
||||
}
|
||||
for i in low .. high + 1 {
|
||||
key = i.str()
|
||||
val := if key in branch_exprs { branch_exprs[key] } else { 0 }
|
||||
if val == 1 {
|
||||
c.error('match case `$key` is handled more than once', branch.pos)
|
||||
}
|
||||
branch_exprs[key] = val + 1
|
||||
}
|
||||
continue
|
||||
}
|
||||
match expr {
|
||||
ast.TypeNode {
|
||||
key = c.table.type_to_str(expr.typ)
|
||||
expr_types << expr
|
||||
}
|
||||
ast.EnumVal {
|
||||
key = expr.val
|
||||
}
|
||||
else {
|
||||
key = expr.str()
|
||||
}
|
||||
}
|
||||
val := if key in branch_exprs { branch_exprs[key] } else { 0 }
|
||||
if val == 1 {
|
||||
c.error('match case `$key` is handled more than once', branch.pos)
|
||||
}
|
||||
c.expected_type = node.cond_type
|
||||
expr_type := c.expr(expr)
|
||||
if expr_type.idx() == 0 {
|
||||
// parser failed, stop checking
|
||||
return
|
||||
}
|
||||
expr_type_sym := c.table.get_type_symbol(expr_type)
|
||||
if cond_type_sym.kind == .interface_ {
|
||||
// TODO
|
||||
// This generates a memory issue with TCC
|
||||
// Needs to be checked later when TCC errors are fixed
|
||||
// Current solution is to move expr.position() to its own statement
|
||||
// c.type_implements(expr_type, c.expected_type, expr.position())
|
||||
expr_pos := expr.position()
|
||||
if c.type_implements(expr_type, c.expected_type, expr_pos) {
|
||||
if !expr_type.is_ptr() && !expr_type.is_pointer() && !c.inside_unsafe {
|
||||
if expr_type_sym.kind != .interface_ {
|
||||
c.mark_as_referenced(mut &branch.exprs[k], true)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if mut cond_type_sym.info is ast.SumType {
|
||||
if expr_type !in cond_type_sym.info.variants {
|
||||
expr_str := c.table.type_to_str(expr_type)
|
||||
expect_str := c.table.type_to_str(node.cond_type)
|
||||
c.error('`$expect_str` has no variant `$expr_str`', expr.position())
|
||||
}
|
||||
} else if cond_type_sym.info is ast.Alias && expr_type_sym.info is ast.Struct {
|
||||
expr_str := c.table.type_to_str(expr_type)
|
||||
expect_str := c.table.type_to_str(node.cond_type)
|
||||
c.error('cannot match alias type `$expect_str` with `$expr_str`', expr.position())
|
||||
} else if !c.check_types(expr_type, node.cond_type) {
|
||||
expr_str := c.table.type_to_str(expr_type)
|
||||
expect_str := c.table.type_to_str(node.cond_type)
|
||||
c.error('cannot match `$expect_str` with `$expr_str`', expr.position())
|
||||
}
|
||||
branch_exprs[key] = val + 1
|
||||
}
|
||||
// when match is type matching, then register smart cast for every branch
|
||||
if expr_types.len > 0 {
|
||||
if cond_type_sym.kind in [.sum_type, .interface_] {
|
||||
mut expr_type := ast.Type(0)
|
||||
if expr_types.len > 1 {
|
||||
mut agg_name := strings.new_builder(20)
|
||||
mut agg_cname := strings.new_builder(20)
|
||||
agg_name.write_string('(')
|
||||
for i, expr in expr_types {
|
||||
if i > 0 {
|
||||
agg_name.write_string(' | ')
|
||||
agg_cname.write_string('___')
|
||||
}
|
||||
type_str := c.table.type_to_str(expr.typ)
|
||||
name := if c.is_builtin_mod { type_str } else { '${c.mod}.$type_str' }
|
||||
agg_name.write_string(name)
|
||||
agg_cname.write_string(util.no_dots(name))
|
||||
}
|
||||
agg_name.write_string(')')
|
||||
name := agg_name.str()
|
||||
existing_idx := c.table.type_idxs[name]
|
||||
if existing_idx > 0 {
|
||||
expr_type = existing_idx
|
||||
} else {
|
||||
expr_type = c.table.register_type_symbol(ast.TypeSymbol{
|
||||
name: name
|
||||
cname: agg_cname.str()
|
||||
kind: .aggregate
|
||||
mod: c.mod
|
||||
info: ast.Aggregate{
|
||||
types: expr_types.map(it.typ)
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
expr_type = expr_types[0].typ
|
||||
}
|
||||
|
||||
c.smartcast(node.cond, node.cond_type, expr_type, mut branch.scope)
|
||||
}
|
||||
}
|
||||
}
|
||||
// check that expressions are exhaustive
|
||||
// this is achieved either by putting an else
|
||||
// or, when the match is on a sum type or an enum
|
||||
// by listing all variants or values
|
||||
mut is_exhaustive := true
|
||||
mut unhandled := []string{}
|
||||
if node.cond_type == ast.bool_type {
|
||||
variants := ['true', 'false']
|
||||
for v in variants {
|
||||
if v !in branch_exprs {
|
||||
is_exhaustive = false
|
||||
unhandled << '`$v`'
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match mut cond_type_sym.info {
|
||||
ast.SumType {
|
||||
for v in cond_type_sym.info.variants {
|
||||
v_str := c.table.type_to_str(v)
|
||||
if v_str !in branch_exprs {
|
||||
is_exhaustive = false
|
||||
unhandled << '`$v_str`'
|
||||
}
|
||||
}
|
||||
}
|
||||
//
|
||||
ast.Enum {
|
||||
for v in cond_type_sym.info.vals {
|
||||
if v !in branch_exprs {
|
||||
is_exhaustive = false
|
||||
unhandled << '`.$v`'
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
is_exhaustive = false
|
||||
}
|
||||
}
|
||||
}
|
||||
mut else_branch := node.branches[node.branches.len - 1]
|
||||
mut has_else := else_branch.is_else
|
||||
if !has_else {
|
||||
for i, branch in node.branches {
|
||||
if branch.is_else && i != node.branches.len - 1 {
|
||||
c.error('`else` must be the last branch of `match`', branch.pos)
|
||||
else_branch = branch
|
||||
has_else = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if is_exhaustive {
|
||||
if has_else && !c.pref.translated {
|
||||
c.error('match expression is exhaustive, `else` is unnecessary', else_branch.pos)
|
||||
}
|
||||
return
|
||||
}
|
||||
if has_else {
|
||||
return
|
||||
}
|
||||
mut err_details := 'match must be exhaustive'
|
||||
if unhandled.len > 0 {
|
||||
err_details += ' (add match branches for: '
|
||||
if unhandled.len < c.match_exhaustive_cutoff_limit {
|
||||
err_details += unhandled.join(', ')
|
||||
} else {
|
||||
remaining := unhandled.len - c.match_exhaustive_cutoff_limit
|
||||
err_details += unhandled[0..c.match_exhaustive_cutoff_limit].join(', ')
|
||||
if remaining > 0 {
|
||||
err_details += ', and $remaining others ...'
|
||||
}
|
||||
}
|
||||
err_details += ' or `else {}` at the end)'
|
||||
} else {
|
||||
err_details += ' (add `else {}` at the end)'
|
||||
}
|
||||
c.error(err_details, node.pos)
|
||||
}
|
Loading…
Reference in New Issue