module checker import v.ast import v.util import v.token import strings fn (mut c Checker) match_expr(mut node ast.MatchExpr) ast.Type { if !node.is_comptime { node.is_expr = c.expected_type != ast.void_type } node.expected_type = c.expected_type if mut node.cond is ast.ParExpr && !c.pref.translated && !c.file.is_translated { c.warn('unnecessary `()` in `match` condition, use `match expr {` instead of `match (expr) {`.', node.cond.pos) } expr_required := c.expected_type != ast.void_type || (node.is_comptime && node.is_expr && c.fn_level > 0) if node.is_expr || expr_required { c.expected_expr_type = c.expected_type defer(fn) { c.expected_expr_type = ast.void_type } } mut cond_type := ast.void_type if node.is_comptime { cond_expr := node.cond if cond_expr is ast.AtExpr { mut checked_cond := node.cond cond_type = c.expr(mut checked_cond) node.cond = checked_cond } else { // for field.name and generic type `T` is_selector_cond := node.cond is ast.SelectorExpr if is_selector_cond { mut selector_cond := node.cond c.expr(mut selector_cond) node.cond = selector_cond } cond_type = c.get_expr_type(node.cond) } } else { cond_type = c.expr(mut 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 = ast.mktyp(cond_type) if (node.cond is ast.Ident && node.cond.is_mut) || (node.cond is ast.SelectorExpr && node.cond.is_mut) { if node.is_comptime { c.error('`\$match` condition `${node.cond}` can not be mutable', node.cond.pos()) } else { c.fail_if_immutable(mut node.cond) } } if !c.ensure_type_exists(node.cond_type, node.pos) { return ast.void_type } if node.cond_type == 0 { return ast.void_type } c.check_expr_option_or_result_call(node.cond, node.cond_type) cond_type_sym := c.table.sym(node.cond_type) cond_final_sym := c.table.final_sym(node.cond_type) cond_is_option := node.cond_type.has_flag(.option) node.is_sum_type = cond_final_sym.kind in [.interface, .sum_type] c.match_exprs(mut node, cond_type_sym, cond_final_sym) c.expected_type = node.cond_type mut ret_type_needs_inference := true mut infer_cast_type := ast.void_type mut need_explicit_cast := false mut ret_type := ast.void_type mut nbranches_with_return := 0 mut nbranches_without_return := 0 mut must_be_option := false comptime_branch_context_str := if node.is_comptime { c.gen_branch_context_string() } else { '' } mut comptime_match_branch_result := false mut comptime_match_found_branch := false mut comptime_match_cond_value := '' if node.is_comptime { if node.cond in [ast.ComptimeType, ast.TypeNode] || (node.cond is ast.Ident && (c.is_generic_ident(node.cond.name))) || (node.cond is ast.SelectorExpr && node.cond.gkind_field in [.typ, .unaliased_typ]) { // must be a type `$match` c.inside_x_matches_type = true } else { // a value `$match`, eval the `node.cond` first c.expr(mut node.cond) if !c.type_resolver.is_generic_param_var(node.cond) { match mut node.cond { ast.StringLiteral, ast.AtExpr { comptime_match_cond_value = node.cond.val } ast.IntegerLiteral { comptime_match_cond_value = node.cond.val.str() } ast.BoolLiteral { comptime_match_cond_value = node.cond.val.str() } ast.Ident { mut cond_expr := c.find_definition(node.cond) or { c.error(err.msg(), node.cond.pos) return ast.void_type } match mut cond_expr { ast.StringLiteral { comptime_match_cond_value = cond_expr.val } ast.IntegerLiteral { comptime_match_cond_value = cond_expr.val.str() } ast.BoolLiteral { comptime_match_cond_value = cond_expr.val.str() } else { c.error('`${node.cond}` is not a string/int/bool literal.', node.cond.pos) return ast.void_type } } } ast.SelectorExpr { if c.comptime.inside_comptime_for && node.cond.field_name in ['name', 'typ'] { // hack: `typ` is just for bypass the error test, because we don't know it is a type match or a value match righ now comptime_match_cond_value = c.comptime.comptime_for_field_value.name } else if mut node.cond.expr is ast.Ident && node.cond.gkind_field in [.typ, .unaliased_typ] { left_type := c.get_expr_type(node.cond.expr) comptime_match_cond_value = c.table.type_to_str(left_type) } else { c.error('`${node.cond}` is not `\$for` field.name.', node.cond.pos) return ast.void_type } } else { c.error('`\$match` cond only support string/int/bool/ident.', node.cond.pos()) return ast.void_type } } } } } for i, mut branch in node.branches { if node.is_comptime { // `idx_str` is composed of two parts: // The first part represents the current context of the branch statement, `comptime_branch_context_str`, formatted like `T=int,X=string,method.name=json` // The second part is the branch's id. // This format must match what is in `cgen`. if branch.id == 0 { // this is a new branch, alloc a new id for it c.cur_ct_id++ branch.id = c.cur_ct_id } mut idx_str := comptime_branch_context_str + '|id=${branch.id}|' if c.comptime.inside_comptime_for && c.comptime.comptime_for_field_var != '' { idx_str += '|field_type=${c.comptime.comptime_for_field_type}|' } mut c_str := '' if !branch.is_else { if c.inside_x_matches_type { // type $match for expr in branch.exprs { if mut node.cond is ast.ComptimeType { // $match $int { a {} branch_type := c.get_expr_type(expr) comptime_match_branch_result = c.type_resolver.is_comptime_type(branch_type, node.cond) c_str = '${expr} == ${c.table.type_to_str(branch_type)}' } else { is_function := c.table.final_sym(node.cond_type).kind == .function if !is_function { // $match a { $int {} comptime_match_branch_result = c.check_compatible_types(node.cond_type, '${node.cond}', expr) c_str = '${c.table.type_to_str(node.cond_type)} == ${expr}' } else { // $match T { FnType {} } branch_type := c.get_expr_type(expr) comptime_match_branch_result = c.table.type_to_str(node.cond_type) == c.table.type_to_str(branch_type) c_str = '${comptime_match_branch_result} == true' } } if comptime_match_branch_result { break } } } else { // value $match for mut expr in branch.exprs { match mut expr { ast.Ident { mut branch_expr := c.find_definition(expr) or { c.error(err.msg(), expr.pos) return ast.void_type } match mut branch_expr { ast.StringLiteral { comptime_match_branch_result = branch_expr.val == comptime_match_cond_value } ast.IntegerLiteral { comptime_match_branch_result = branch_expr.val.str() == comptime_match_cond_value } ast.BoolLiteral { comptime_match_branch_result = branch_expr.val.str() == comptime_match_cond_value } else { c.error('`${expr}` is not a string/int/bool literal.', expr.pos) return ast.void_type } } c_str = '${node.cond} == ${expr.name}' } ast.SelectorExpr {} ast.StringLiteral { c_str = '${node.cond} == ${expr}' comptime_match_branch_result = comptime_match_cond_value == expr.val } ast.IntegerLiteral { c_str = '${node.cond} == ${expr.val}' comptime_match_branch_result = comptime_match_cond_value == expr.val.str() } ast.BoolLiteral { c_str = '${node.cond} == ${expr.val}' comptime_match_branch_result = comptime_match_cond_value == expr.val.str() } else { c.error('`\$match` branch only support string/int/bool types', node.cond.pos()) return ast.void_type } } if comptime_match_branch_result { break } } } if comptime_match_branch_result { comptime_match_found_branch = true } // set `comptime_is_true` which can be used by `cgen` c.table.comptime_is_true[idx_str] = ast.ComptTimeCondResult{ val: comptime_match_branch_result c_str: c_str } } else { comptime_match_branch_result = !comptime_match_found_branch c.table.comptime_is_true[idx_str] = ast.ComptTimeCondResult{ val: comptime_match_branch_result c_str: '' } } } for mut expr in branch.exprs { // (expr) => expr expr = expr.remove_par() } if !c.pref.translated && !c.file.is_translated { mut did_warn_self_comparison_branch := false // check for always true/false match branch for mut expr in branch.exprs { mut check_expr := ast.InfixExpr{ op: .eq left: node.cond right: expr } t_expr := c.checker_transformer.expr(mut check_expr) if t_expr is ast.BoolLiteral { if t_expr.val { c.note('match is always true', expr.pos()) if c.is_always_true_self_comparison(ast.Expr(ast.InfixExpr{ op: .eq left: node.cond right: expr left_type: node.cond_type right_type: node.cond_type })) && i < node.branches.len - 1 && !did_warn_self_comparison_branch { c.warn('self-comparison match branch is always true; following branches may be unreachable', expr.pos()) did_warn_self_comparison_branch = true } } else { c.note('match is always false', expr.pos()) } } } } if !node.is_comptime || (node.is_comptime && comptime_match_branch_result) { if node.is_expr { c.stmts_ending_with_expression(mut branch.stmts, c.expected_or_type) } else { c.stmts(mut branch.stmts) } } c.smartcast_mut_pos = token.Pos{} c.smartcast_cond_pos = token.Pos{} if node.is_expr { if branch.stmts.len == 0 && ret_type != ast.void_type { c.error('`match` expression requires an expression as the last statement of every branch', branch.branch_pos) } } if !branch.is_else && cond_is_option && branch.exprs.any(it !is ast.None) { c.error('`match` expression with Option type only checks against `none`, to match its value you must unwrap it first `var?`', branch.pos) } if cond_type_sym.kind == .none { c.error('`none` cannot be a match condition', node.pos) } if branch.stmts.len > 0 && node.is_expr && (!node.is_comptime || (node.is_comptime && comptime_match_branch_result)) { mut stmt := branch.stmts.last() if mut stmt is ast.ExprStmt { c.expected_type = if c.expected_expr_type != ast.void_type { c.expected_expr_type } else { node.expected_type } branch.is_comptime_err = stmt.expr is ast.ComptimeCall && stmt.expr.kind in [.compile_error, .compile_warn] expr_type := if branch.is_comptime_err { c.expected_type } else { c.unwrap_generic(if stmt.expr is ast.CallExpr { stmt.typ } else { c.expr(mut stmt.expr) }) } unwrapped_expected_type := c.unwrap_generic(node.expected_type) must_be_option = must_be_option || expr_type == ast.none_type stmt.typ = expr_type if ret_type_needs_inference && !is_noreturn_callexpr(stmt.expr) { if unwrapped_expected_type.has_option_or_result() || c.table.type_kind(unwrapped_expected_type) in [.sum_type, .interface, .multi_return] { c.check_match_branch_last_stmt(mut stmt, unwrapped_expected_type, expr_type) ret_type = node.expected_type } else { ret_type = expr_type if expr_type.is_ptr() { if stmt.expr is ast.Ident && stmt.expr.obj is ast.Var && c.table.is_interface_var(stmt.expr.obj) { ret_type = expr_type.deref() } else if mut stmt.expr is ast.PrefixExpr && stmt.expr.right is ast.Ident { ident := stmt.expr.right as ast.Ident if ident.obj is ast.Var && c.table.is_interface_var(ident.obj) { ret_type = expr_type.deref() } } } c.expected_expr_type = expr_type } infer_cast_type = stmt.typ if mut stmt.expr is ast.CastExpr { need_explicit_cast = true infer_cast_type = stmt.expr.typ } ret_type_needs_inference = false } else if !ret_type_needs_inference { if ret_type.idx() != expr_type.idx() { if unwrapped_expected_type.has_option_or_result() && c.table.sym(stmt.typ).kind == .struct && !c.check_types(expr_type, c.unwrap_generic(ret_type)) && c.type_implements(stmt.typ, ast.error_type, node.pos) { stmt.expr = ast.CastExpr{ expr: stmt.expr typname: 'IError' typ: ast.error_type expr_type: stmt.typ pos: node.pos } stmt.typ = ast.error_type } else { c.check_match_branch_last_stmt(mut stmt, c.unwrap_generic(ret_type), expr_type) if ret_type.is_number() && expr_type.is_number() && !c.inside_return { ret_type = c.promote_num(ret_type, expr_type) } } } if must_be_option && ret_type == ast.none_type && expr_type != ret_type { ret_type = expr_type.set_flag(.option) } if stmt.typ != ast.error_type && !is_noreturn_callexpr(stmt.expr) { ret_sym := c.table.sym(ret_type) stmt_sym := c.table.sym(stmt.typ) if ret_sym.kind !in [.sum_type, .interface] && stmt_sym.kind in [.sum_type, .interface] { c.error('return type mismatch, it should be `${ret_sym.name}`, but it is instead `${c.table.type_to_str(expr_type)}`', stmt.pos) } if ret_type.nr_muls() != stmt.typ.nr_muls() && stmt.typ.idx() !in [ast.voidptr_type_idx, ast.nil_type_idx] { type_name := '&'.repeat(ret_type.nr_muls()) + ret_sym.name c.error('return type mismatch, it should be `${type_name}`, but it is instead `${c.table.type_to_str(expr_type)}`', stmt.pos) } } if !node.is_sum_type { if mut stmt.expr is ast.CastExpr { expr_typ_sym := c.table.sym(stmt.expr.typ) if need_explicit_cast { if infer_cast_type != stmt.expr.typ && expr_typ_sym.kind !in [.interface, .sum_type] { c.error('the type of the last expression in the first match branch was an explicit `${c.table.type_to_str(infer_cast_type)}`, not `${c.table.type_to_str(stmt.expr.typ)}`', stmt.pos) } } else { if infer_cast_type != stmt.expr.typ && expr_typ_sym.kind !in [.interface, .sum_type] && c.promote_num(stmt.expr.typ, ast.int_type) != ast.int_type { c.error('the type of the last expression of the first match branch was `${c.table.type_to_str(infer_cast_type)}`, which is not compatible with `${c.table.type_to_str(stmt.expr.typ)}`', stmt.pos) } } } else { if mut stmt.expr is ast.IntegerLiteral { cast_type_sym := c.table.sym(infer_cast_type) num := stmt.expr.val.i64() mut needs_explicit_cast := false match cast_type_sym.kind { .u8 { if !(num >= min_u8 && num <= max_u8) { needs_explicit_cast = true } } .u16 { if !(num >= min_u16 && num <= max_u16) { needs_explicit_cast = true } } .u32 { if !(num >= min_u32 && num <= max_u32) { needs_explicit_cast = true } } .u64 { if !(num >= min_u64 && num <= max_u64) { needs_explicit_cast = true } } .i8 { if !(num >= min_i32 && num <= max_i32) { needs_explicit_cast = true } } .i16 { if !(num >= min_i16 && num <= max_i16) { needs_explicit_cast = true } } .i32 { if !(num >= min_i32 && num <= max_i32) { needs_explicit_cast = true } } .int { $if new_int ? && x64 { if !(num >= min_i64 && num <= max_i64) { needs_explicit_cast = true } } $else { if !(num >= min_i32 && num <= max_i32) { needs_explicit_cast = true } } } .i64 { if !(num >= min_i64 && num <= max_i64) { needs_explicit_cast = true } } .int_literal { needs_explicit_cast = false } else {} } if needs_explicit_cast { c.error('${num} does not fit the range of `${c.table.type_to_str(infer_cast_type)}`', stmt.pos) } } } } } } else if stmt !in [ast.Return, ast.BranchStmt] { if ret_type != ast.void_type { c.error('`match` expression requires an expression as the last statement of every branch', stmt.pos) } } else if c.inside_return && mut stmt is ast.Return && ret_type == ast.void_type { ret_type = if stmt.types.len > 0 { stmt.types[0] } else { c.expected_type } } } if !node.is_comptime || (node.is_comptime && comptime_match_branch_result) { // ret_type_needs_inference is set to false in the ExprStmt branch above // when a non-noreturn expression provides the type } if node.is_comptime { // branches may not have been processed by c.stmts() if c.has_top_return(branch.stmts) { nbranches_with_return++ } else { nbranches_without_return++ } } else { 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.none_type { c.error('invalid match expression, must supply at least one value other than `none`', node.pos) } node.return_type = if must_be_option { ret_type.set_flag(.option) } else { 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 node.return_type } fn (mut c Checker) check_match_branch_last_stmt(mut last_stmt ast.ExprStmt, ret_type ast.Type, expr_type ast.Type) { if !c.check_types(ret_type, expr_type) && !c.check_types(expr_type, ret_type) { ret_sym := c.table.sym(ret_type) expr_sym := c.table.sym(expr_type) if ret_sym.kind == .interface { if c.type_implements(expr_type, ret_type, last_stmt.pos) { if !expr_type.is_any_kind_of_pointer() && expr_sym.kind != .interface && !c.inside_unsafe { c.mark_as_referenced(mut &last_stmt.expr, true) } } return } is_noreturn := is_noreturn_callexpr(last_stmt.expr) if !(ret_sym.kind == .sum_type && (ret_type.has_flag(.generic) || c.table.is_sumtype_or_in_variant(ret_type, expr_type))) && !is_noreturn { if expr_sym.kind == .multi_return && ret_sym.kind == .multi_return { ret_types := ret_sym.mr_info().types expr_types := expr_sym.mr_info().types.map(ast.mktyp(it)) if expr_types == ret_types { return } } if expr_type != ast.none_type && ret_type != ast.none_type { c.error('return type mismatch, it should be `${ret_sym.name}`, but it is instead `${c.table.type_to_str(expr_type)}`', last_stmt.pos) } } } else if expr_type == ast.void_type && ret_type.idx() == ast.void_type_idx && ret_type.has_option_or_result() { c.error('`${last_stmt.expr}` used as value', last_stmt.pos) } } fn char_literal_number_value(value string) ?i64 { if value.len == 2 && value[0] == `\\` { return match value[1] { `a` { 7 } `b` { 8 } `t` { 9 } `n` { 10 } `v` { 11 } `f` { 12 } `r` { 13 } `e` { 27 } `$` { 36 } `"` { 34 } `'` { 39 } `?` { 63 } `@` { 64 } `\\` { 92 } `\`` { 96 } `{` { 123 } `}` { 125 } else { none } } } runes := value.runes() if runes.len == 1 { return runes[0] } return none } fn (mut c Checker) get_comptime_number_value(mut expr ast.Expr) ?i64 { if mut expr is ast.ParExpr { return c.get_comptime_number_value(mut expr.expr) } if mut expr is ast.PrefixExpr && expr.op == .minus { return -c.get_comptime_number_value(mut expr.right)? } if mut expr is ast.CharLiteral { return char_literal_number_value(expr.val) } if mut expr is ast.IntegerLiteral { return expr.val.i64() } if mut expr is ast.CastExpr { return c.get_comptime_number_value(mut expr.expr) } if mut expr is ast.Ident { if mut obj := c.table.global_scope.find_const(expr.full_name()) { if obj.typ == 0 { obj.typ = c.expr(mut obj.expr) } return c.get_comptime_number_value(mut obj.expr) } } return none } fn (mut c Checker) get_match_case_int_key(mut expr ast.Expr, cond_sym ast.TypeSymbol) ?string { if !cond_sym.is_int() { return none } // Only resolve literal values for dedup, not const idents. // Different const names with the same value should be allowed. if value := c.get_match_case_literal_value(mut expr) { return value.str() } return none } // Like get_comptime_number_value but does NOT resolve const idents. // This ensures different named consts with the same value are not flagged as duplicates. fn (mut c Checker) get_match_case_literal_value(mut expr ast.Expr) ?i64 { if mut expr is ast.ParExpr { return c.get_match_case_literal_value(mut expr.expr) } if mut expr is ast.PrefixExpr && expr.op == .minus { return -c.get_match_case_literal_value(mut expr.right)? } if mut expr is ast.CharLiteral { return char_literal_number_value(expr.val) } if mut expr is ast.IntegerLiteral { return expr.val.i64() } if mut expr is ast.CastExpr { return c.get_match_case_literal_value(mut expr.expr) } return none } fn (mut c Checker) match_exprs(mut node ast.MatchExpr, cond_type_sym ast.TypeSymbol, cond_final_sym ast.TypeSymbol) { c.expected_type = node.expected_type if node.cond_type.idx() == 0 { return } cond_sym := c.table.sym(node.cond_type) cond_match_type := c.table.final_type(node.cond_type) is_alias_to_matchable_type := cond_type_sym.kind == .alias && cond_final_sym.kind in [.interface, .sum_type] cond_match_sym := if is_alias_to_matchable_type { cond_final_sym } else { cond_type_sym } mut enum_ref_checked := false mut is_comptime_value_match := false // branch_exprs is a histogram of how many times // an expr was used in the match mut branch_exprs := map[string]int{} mut branch_expr_types := []ast.Type{} is_multi_allowed_enum_match := cond_type_sym.info is ast.Enum && cond_type_sym.info.is_multi_allowed mut branch_enum_values := map[i64]bool{} for branch_i, _ in node.branches { mut branch := node.branches[branch_i] mut expr_types := []ast.TypeNode{} for k, mut expr in branch.exprs { mut key := '' mut resolved_type_node := ast.no_type // TODO: investigate why enums are different here: if expr !is ast.EnumVal && !(node.is_comptime && expr is ast.ComptimeType) { // ensure that the sub expressions of the branch are actually checked, before anything else: _ := c.expr(mut expr) } if expr is ast.TypeNode && cond_sym.kind == .struct { c.error('struct instances cannot be matched by type name, they can only be matched to other instances of the same struct type', branch.pos) } mut is_comptime := false if c.comptime.inside_comptime_for { // it is a compile-time field.typ checking if mut node.cond is ast.SelectorExpr { is_comptime = node.cond.expr_type == c.field_data_type && node.cond.field_name == 'typ' } } if mut expr is ast.TypeNode && cond_sym.is_primitive() && !is_comptime && !node.is_comptime { c.error('matching by type can only be done for sum types, generics, interfaces, `${node.cond}` is none of those', branch.pos) } if mut expr is ast.RangeExpr { // Allow for `match enum_value { 4..5 { } }`, even though usually int and enum values, // are considered incompatible outside unsafe{}, and are not allowed to be compared directly if cond_sym.kind != .enum && !c.check_types(expr.typ, node.cond_type) { mcstype := c.table.type_to_str(node.cond_type) brstype := c.table.type_to_str(expr.typ) c.add_error_detail('') c.add_error_detail('match condition type: ${mcstype}') c.add_error_detail(' range type: ${brstype}') c.error('the range type and the match condition type should match', expr.pos) } mut low_value_higher_than_high_value := false mut low := i64(0) mut high := i64(0) mut both_low_and_high_are_known := false if low_value := c.get_comptime_number_value(mut expr.low) { low = low_value if high_value := c.get_comptime_number_value(mut expr.high) { high = high_value both_low_and_high_are_known = true if low_value > high_value { low_value_higher_than_high_value = true } } else { if expr.high !is ast.EnumVal { c.error('match branch range expressions need the end value to be known at compile time (only enums, const or literals are supported)', expr.high.pos()) } } } else { if expr.low !is ast.EnumVal { c.error('match branch range expressions need the start value to be known at compile time (only enums, const or literals are supported)', expr.low.pos()) } } if low_value_higher_than_high_value { c.error('the start value `${low}` should be lower than the end value `${high}`', branch.pos) } if both_low_and_high_are_known { high_low_cutoff := 1000 if high - low > high_low_cutoff { c.note('more than ${high_low_cutoff} possibilities (${low} ... ${high}) in match range may affect compile time', 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 if is_multi_allowed_enum_match { branch_enum_values[i] = true } } } continue } is_type_node := expr is ast.TypeNode || expr is ast.ComptimeType match mut expr { ast.TypeNode { resolved_type_node = c.recheck_concrete_type(expr.typ) key = c.table.type_to_str(resolved_type_node) expr_types << ast.TypeNode{ ...expr typ: resolved_type_node } } ast.EnumVal { key = expr.val if is_multi_allowed_enum_match { if enum_val := c.table.find_enum_field_val(cond_type_sym.name, expr.val) { branch_enum_values[enum_val] = true } } if !enum_ref_checked { enum_ref_checked = true if node.cond_type.is_ptr() { c.error('missing `*` dereferencing `${node.cond}` in match statement', node.cond.pos()) } } } else { key = c.get_match_case_int_key(mut expr, cond_final_sym) or { (*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 if is_type_node { c.inside_x_matches_type = true } else { if node.is_comptime { is_comptime_value_match = true } } if node.is_comptime { expr_pos := expr.pos() if is_type_node { if is_comptime_value_match { // type branch in a value match c.error('can not matching a type in a value `\$match`', expr_pos) return } } else if c.inside_x_matches_type { // value branch in a type match if expr in [ast.IntegerLiteral, ast.BoolLiteral, ast.StringLiteral] { c.error('can not matching a value in a type `\$match`', expr_pos) return } } if !is_type_node { // value check should match cond's type if expr is ast.IntegerLiteral { if mut node.cond is ast.ComptimeType { if node.cond.kind != .int { c.error('can not matching a int value(`${ast.Expr(expr)}`) in a non int type `\$match`, `${node.cond}` type is `${node.cond.kind}`', expr_pos) return } } else if node.cond_type !in ast.integer_type_idxs { c.error('can not matching a int value(`${ast.Expr(expr)}`) in a non int type `\$match`, `${node.cond}` type is `${c.table.type_to_str(node.cond_type)}`', expr_pos) return } } else if expr is ast.BoolLiteral && node.cond_type != ast.bool_type { c.error('can not matching a bool value(`${ast.Expr(expr)}`) in a non bool type `\$match`, `${node.cond}` type is `${c.table.type_to_str(node.cond_type)}`', expr_pos) return } else if expr is ast.StringLiteral { if mut node.cond is ast.ComptimeType { if node.cond.kind != .string { c.error('can not matching a string value(`${ast.Expr(expr)}`) in a non string type `\$match`, `${node.cond}` type is `${node.cond.kind}`', expr_pos) return } } else if node.cond_type != ast.string_type { c.error('can not matching a string value(`${ast.Expr(expr)}`) in a non string type `\$match`, `${node.cond}` type is `${c.table.type_to_str(node.cond_type)}`', expr_pos) return } } } } else { expr_type := if expr is ast.TypeNode { resolved_type_node } else { c.expr(mut expr) } if expr_type.idx() == 0 { // parser failed, stop checking return } expr_type_sym := c.table.sym(expr_type) is_exact_alias_type_match := is_alias_to_matchable_type && expr_type in [node.cond_type, cond_match_type] if is_exact_alias_type_match { // Allow matching an alias to its underlying sumtype/interface type. } else if cond_match_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.pos() to its own statement // c.type_implements(expr_type, c.expected_type, expr.pos()) expr_pos := expr.pos() if c.type_implements(expr_type, c.expected_type, expr_pos) { if !expr_type.is_any_kind_of_pointer() && !c.inside_unsafe { if expr_type_sym.kind != .interface { c.mark_as_referenced(mut &branch.exprs[k], true) } } } } else if cond_match_sym.info is ast.SumType { if !c.table.sumtype_has_variant_recursive(cond_match_type, expr_type, true) { expr_str := c.table.type_to_str(expr_type) expect_str := c.table.type_to_str(node.cond_type) sumtype_variant_names := c.table.sumtype_matchable_variants(cond_match_type).map(c.table.type_to_str_using_aliases(it, {})) suggestion := util.new_suggestion(expr_str, sumtype_variant_names) c.error(suggestion.say('`${expect_str}` has no variant `${expr_str}`'), expr.pos()) } } 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.pos()) } else if !c.check_types(expr_type, node.cond_type) && !is_comptime { 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.pos()) } if is_type_node { branch_expr_types << if is_alias_to_matchable_type && expr_type == node.cond_type { cond_match_type } else { expr_type } } } branch_exprs[key] = val + 1 } // when match is type matching, then register smart cast for every branch if expr_types.len > 0 { if cond_match_sym.kind in [.sum_type, .interface] { mut expr_type := ast.no_type 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_sym(ast.TypeSymbol{ name: name cname: agg_cname.str() kind: .aggregate mod: c.mod info: ast.Aggregate{ sum_type: node.cond_type types: expr_types.map(it.typ) } }) } } else { expr_type = expr_types[0].typ } allow_mut_selector_smartcast := node.cond is ast.SelectorExpr c.smartcast(mut node.cond, node.cond_type, expr_type, mut branch.scope, false, false, allow_mut_selector_smartcast, true) } } } // 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 cond_match_sym.info { ast.SumType { for v in c.table.sumtype_missing_variants(cond_match_type, branch_expr_types) { is_exhaustive = false unhandled << '`${c.table.type_to_str(v)}`' } } // ast.Enum { for v in cond_match_sym.info.vals { mut is_handled := v in branch_exprs if !is_handled && is_multi_allowed_enum_match { if enum_val := c.table.find_enum_field_val(cond_match_sym.name, v) { is_handled = enum_val in branch_enum_values } } if !is_handled { is_exhaustive = false unhandled << '`.${v}`' } } if cond_match_sym.info.is_flag { is_exhaustive = false } } else { is_exhaustive = false } } } if node.branches.len == 0 { c.error('`match` must have at least two branches including `else`, or an exhaustive set of branches', node.pos) return } mut else_branch := node.branches.last() mut has_else, mut has_non_else := else_branch.is_else, !else_branch.is_else for branch in node.branches[..node.branches.len - 1] { if branch.is_else { if has_else { c.error('`match` can have only one `else` branch', branch.pos) } c.error('`else` must be the last branch of `match`', branch.pos) else_branch = branch has_else = true } else { has_non_else = true } } if !has_non_else { c.error('`match` must have at least one non `else` branch', else_branch.pos) } if is_exhaustive { if has_else && !c.pref.translated && !c.file.is_translated { c.error('match expression is exhaustive, `else` is unnecessary', else_branch.pos) } return } if has_else || node.is_comptime { 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) }