checker: split up checker.v: fn.v, if.v, interface.v, match.v

pull/12793/head
Alexander Medvednikov 2021-12-11 10:47:57 +03:00
parent ea1f398f90
commit ee6c0a0691
6 changed files with 2493 additions and 2470 deletions

File diff suppressed because it is too large Load Diff

View File

@ -568,3 +568,235 @@ fn (mut c Checker) comptime_if_branch(cond ast.Expr, pos token.Position) bool {
} }
return false 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
}

1372
vlib/v/checker/fn.v 100644

File diff suppressed because it is too large Load Diff

298
vlib/v/checker/if.v 100644
View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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)
}