module checker import strings import v.ast import v.util import v.token import os const print_everything_fns = ['println', 'print', 'eprintln', 'eprint', 'panic'] fn first_attr_by_name(attrs []ast.Attr, name string) (ast.Attr, bool) { for attr in attrs { if attr.name == name { return attr, true } } return ast.Attr{}, false } fn comptime_define_attr_idx(attrs []ast.Attr) int { for idx in 0 .. attrs.len { if attrs[idx].kind == .comptime_define { return idx } } return ast.invalid_type_idx } @[inline] fn (c &Checker) implicit_mutability_enabled() bool { return c.pref.disable_explicit_mutability && (!os.dir(c.file.path).contains('vlib') || c.file.path.ends_with('.vv')) } @[inline] fn (c &Checker) implicit_mut_call_arg(param ast.Param, arg ast.CallArg) ast.CallArg { if !c.implicit_mutability_enabled() || arg.is_mut || !param.is_mut || param.typ.share() != .mut_t { return arg } return ast.CallArg{ ...arg is_mut: true } } fn (mut c Checker) check_os_raw_io_call(node &ast.CallExpr, func &ast.Fn, concrete_types []ast.Type, arg_offset int) { if func.mod != 'os' || !func.is_method { return } match func.name { 'write_struct', 'write_struct_at', 'write_raw', 'write_raw_at', 'read_struct', 'read_struct_at' { if node.args.len > arg_offset { c.ensure_os_raw_io_type(node.args[arg_offset].typ, func.name, node.args[arg_offset].pos) } } 'read_raw', 'read_raw_at' { if concrete_types.len > 0 { c.ensure_os_raw_io_type(concrete_types.last(), func.name, node.pos) } } else {} } } fn (mut c Checker) refresh_generic_fn_scope_vars(node &ast.FnDecl) { generic_names := c.effective_fn_generic_names(node) if c.table.cur_concrete_types.len == 0 || generic_names.len != c.table.cur_concrete_types.len { return } if node.is_method { receiver_type := c.recheck_concrete_type(node.receiver.typ) if mut receiver_var := c.fn_scope.find_var(node.receiver.name) { if receiver_var.generic_typ == 0 && (node.receiver.typ.has_flag(.generic) || c.type_has_unresolved_generic_parts(node.receiver.typ)) { receiver_var.generic_typ = node.receiver.typ } receiver_var.typ = receiver_type receiver_var.orig_type = ast.no_type receiver_var.smartcasts = [] receiver_var.is_unwrapped = false } } for param in node.params { param_source_type := if param.is_mut && param.orig_typ != 0 && (param.orig_typ.has_flag(.generic) || c.type_has_unresolved_generic_parts(param.orig_typ)) { param.orig_typ } else { param.typ } param_type := c.recheck_concrete_type(param_source_type) if mut param_var := c.fn_scope.find_var(param.name) { if param_var.generic_typ == 0 && (param_source_type.has_flag(.generic) || c.type_has_unresolved_generic_parts(param_source_type)) { param_var.generic_typ = param_source_type } param_var.typ = param_type param_var.orig_type = ast.no_type param_var.smartcasts = [] param_var.is_unwrapped = false } } } fn (c &Checker) struct_embeds_type(got ast.Type, expected ast.Type) bool { got_sym := c.table.final_sym(got) if got_sym.info !is ast.Struct { return false } got_info := got_sym.info as ast.Struct expected_unaliased := c.table.unaliased_type(expected) for embed in got_info.embeds { embed_unaliased := c.table.unaliased_type(embed) if embed_unaliased == expected_unaliased || c.struct_embeds_type(embed_unaliased, expected_unaliased) { return true } } return false } fn (c &Checker) embeds_expected_call_arg_type(got ast.Type, expected ast.Type) bool { if got == 0 || expected == 0 { return false } got_base := if got.is_ptr() { got.deref() } else { got } expected_base := if expected.is_ptr() { expected.deref() } else { expected } if got_base == expected_base { return false } got_sym := c.table.final_sym(got_base) if got_sym.info !is ast.Struct { return false } return c.struct_embeds_type(got_base, expected_base) } fn (mut c Checker) effective_fn_generic_names(node &ast.FnDecl) []string { if node.generic_names.len > 0 { return node.generic_names.clone() } if !node.is_method { return []string{} } if !node.receiver.typ.has_flag(.generic) && !c.type_has_unresolved_generic_parts(node.receiver.typ) { return []string{} } rec_sym := c.table.sym(c.unwrap_generic(node.receiver.typ)) match rec_sym.info { ast.Struct, ast.Interface, ast.SumType { if rec_sym.info.generic_types.len > 0 { return rec_sym.info.generic_types.map(c.table.sym(it).name) } } else {} } return c.table.generic_type_names(node.receiver.typ) } fn (mut c Checker) generic_anon_fn_can_use_current_context(generic_names []string) bool { if generic_names.len == 0 || c.table.cur_fn == unsafe { nil } { return false } current_generic_names := c.effective_fn_generic_names(c.table.cur_fn) if current_generic_names.len == 0 { return false } return generic_names.all(it in current_generic_names) } fn (mut c Checker) receiver_requires_generic_names(node &ast.FnDecl) bool { if !node.is_method { return false } return node.receiver.typ.has_flag(.generic) } fn (mut c Checker) check_receiver_decl_generic_type_names(node &ast.FnDecl) { if !node.is_method { return } receiver_sym := c.table.final_sym(node.receiver.typ) match receiver_sym.info { ast.Struct { if receiver_sym.info.generic_types.len > 0 && !node.receiver.typ.has_flag(.generic) && receiver_sym.info.concrete_types.len == 0 { pure_sym_name := receiver_sym.embed_name() c.error('generic struct `${pure_sym_name}` in fn declaration must specify the generic type names, e.g. ${pure_sym_name}[T]', node.receiver.type_pos) } } ast.Interface { if receiver_sym.info.generic_types.len > 0 && !node.receiver.typ.has_flag(.generic) && receiver_sym.info.concrete_types.len == 0 { pure_sym_name := receiver_sym.embed_name() c.error('generic interface `${pure_sym_name}` in fn declaration must specify the generic type names, e.g. ${pure_sym_name}[T]', node.receiver.type_pos) } } ast.SumType { if receiver_sym.info.generic_types.len > 0 && !node.receiver.typ.has_flag(.generic) && receiver_sym.info.concrete_types.len == 0 { pure_sym_name := receiver_sym.embed_name() c.error('generic sumtype `${pure_sym_name}` in fn declaration must specify the generic type names, e.g. ${pure_sym_name}[T]', node.receiver.type_pos) } } else {} } } fn (mut c Checker) check_receiver_decl_generic_name_mentions(node &ast.FnDecl) { if !node.is_method || !node.receiver.typ.has_flag(.generic) { return } generic_names := c.table.generic_type_names(node.receiver.typ) for name in generic_names { if name !in node.generic_names { fn_generic_names := node.generic_names.join(', ') c.error('generic type name `${name}` is not mentioned in fn `${node.name}[${fn_generic_names}]`', node.receiver.type_pos) } } } fn (mut c Checker) fn_decl(mut node ast.FnDecl) { // handle vls go to definition for method receiver types if c.pref.is_vls { // Hover over fn keyword or function name on the declaration line — show full declaration. on_fn_name := c.vls_is_the_node(node.name_pos) on_fn_keyword := c.pref.linfo.line_nr == node.name_pos.line_nr && c.pref.linfo.col >= int(node.pos.col) - 1 && c.pref.linfo.col < node.name_pos.col && node.name_pos.file_idx >= 0 && os.real_path(c.pref.linfo.path) == os.real_path(c.table.filelist[node.name_pos.file_idx]) if c.pref.linfo.method == .completion && (on_fn_name || on_fn_keyword) { mut params := []string{cap: node.params.len} for param in node.params { if param.is_hidden { continue } params << '${param.name} ${c.table.type_to_str(param.typ)}' } ret_str := if node.return_type != ast.no_type && node.return_type != ast.void_type { ' ' + c.table.type_to_str(node.return_type) } else { '' } declaration := 'fn ${node.short_name}(${params.join(', ')})${ret_str}' mut doc := '' mod := node.name.all_before_last('.') if info := c.table.vls_info['fn_${mod}[]${node.short_name}'] { doc = info.doc } c.vls_write_details([ Detail{ kind: .function label: node.short_name declaration: declaration documentation: doc }, ]) exit(0) } if node.is_method && node.receiver.type_pos.line_nr > 0 { if c.vls_is_the_node(node.receiver.type_pos) { typ_str := c.table.type_to_str(node.receiver.typ) if np := c.name_pos_gotodef(typ_str) { if np.file_idx != -1 { println('${c.table.filelist[np.file_idx]}:${np.line_nr + 1}:${np.col}') } exit(0) } } } if node.return_type_pos.line_nr > 0 { if c.vls_is_the_node(node.return_type_pos) { typ_str := c.table.type_to_str(node.return_type) if np := c.name_pos_gotodef(typ_str) { if np.file_idx != -1 { println('${c.table.filelist[np.file_idx]}:${np.line_nr + 1}:${np.col}') } exit(0) } } } } $if trace_post_process_generic_fns_types ? { if node.generic_names.len > 0 { eprintln('>>> post processing node.name: ${node.name:-30} | ${node.generic_names} <=> ${c.table.cur_concrete_types}') } } if node.language in [.c, .js] && node.generic_names.len > 0 { lang := if node.language == .c { 'C' } else { 'JS' } c.error('${lang} functions cannot be declared as generic', node.pos) } // record the veb route methods (public non-generic methods): if node.generic_names.len > 0 && node.is_pub { typ_veb_result := c.table.find_type('veb.Result') if node.return_type == typ_veb_result { rec_sym := c.table.sym(node.receiver.typ) if rec_sym.kind == .struct { if _ := c.table.find_field_with_embeds(rec_sym, 'Context') { // there is no point in the message here, for methods // that are not public; since they will not be available as routes anyway c.note('generic method routes of veb will be skipped', node.pos) } } } } mut need_generic_names := false if node.generic_names.len == 0 { if node.return_type.has_flag(.generic) { need_generic_names = true } else if c.receiver_requires_generic_names(node) { need_generic_names = true } else { for param in node.params { if param.typ.has_flag(.generic) { need_generic_names = true break } } } if need_generic_names { if node.is_method { c.add_error_detail('use `fn (r SomeType[T]) foo[T]() {`, not just `fn (r SomeType[T]) foo() {`') c.error('generic method declaration must specify generic type names', node.pos) } else { c.add_error_detail('use `fn foo[T](x T) {`, not just `fn foo(x T) {`') c.error('generic function declaration must specify generic type names', node.pos) } } } c.check_receiver_decl_generic_type_names(node) c.check_receiver_decl_generic_name_mentions(node) effective_generic_names := c.effective_fn_generic_names(node) if effective_generic_names.len > 0 && c.table.cur_concrete_types.len == 0 { // Just remember the generic function for now. // It will be processed later in c.post_process_generic_fns, // after all other normal functions are processed. // This is done so that all generic function calls can // have a chance to populate c.table.fn_generic_types with // the correct concrete types. fkey := node.fkey() if !c.generic_fns[fkey] { c.need_recheck_generic_fns = true c.generic_fns[fkey] = true c.file.generic_fns << node } return } node.ninstances++ // save all the state that fn_decl or inner statements/expressions // could potentially modify, since functions can be nested, due to // anonymous function support, and ensure that it is restored, when // fn_decl returns: prev_fn_scope := c.fn_scope prev_in_for_count := c.in_for_count prev_inside_defer := c.inside_defer prev_inside_unsafe := c.inside_unsafe prev_inside_anon_fn := c.inside_anon_fn prev_returns := c.returns prev_stmt_level := c.stmt_level prev_assert_autocasts := c.assert_autocasts.clone() c.fn_level++ c.in_for_count = 0 c.inside_defer = false c.inside_unsafe = node.is_unsafe c.returns = false c.assert_autocasts = map[string]AssertAutocast{} defer { c.stmt_level = prev_stmt_level c.fn_level-- c.returns = prev_returns c.inside_anon_fn = prev_inside_anon_fn c.inside_unsafe = prev_inside_unsafe c.inside_defer = prev_inside_defer c.in_for_count = prev_in_for_count c.fn_scope = prev_fn_scope c.assert_autocasts = prev_assert_autocasts.clone() } // Check generics fn/method without generic type parameters if node.language == .v && !c.is_builtin_mod && !node.is_anon && !node.get_name().contains('veb_tmpl_') { c.check_valid_snake_case(node.get_name(), 'function name', node.pos) if !node.is_method && node.mod == 'main' && node.short_name in c.table.builtin_pub_fns { c.error('cannot redefine builtin public function `${node.short_name}`', node.pos) } c.check_module_name_conflict(node.short_name, node.pos) } if node.kind == .main_main { c.main_fn_decl_node = *node } if node.language == .v && node.attrs.len > 0 { required_args_attr := ['export', '_linker_section'] for attr_name in required_args_attr { attr, has_attr := first_attr_by_name(node.attrs, attr_name) if has_attr { if attr.arg == '' { c.error('missing argument for @[${attr_name}] attribute', attr.pos) } else if attr_name == 'export' { // export fn name fn_name := attr.arg if !fn_name.is_identifier() { c.error('export name `${fn_name}` should be a valid identifier', node.pos) } if fn_name in c.table.export_names.values() { c.error('duplicate export name `${fn_name}`', node.pos) } else { mut node_name := node.name if node.is_method { node_name = c.table.type_to_str(node.receiver.typ) + '.' + node_name } c.table.export_names[node_name] = fn_name } } } } } if node.return_type == ast.no_type { c.error('invalid return type in fn `${node.name}`', node.pos) return } c.fn_return_type = node.return_type return_type_unaliased := c.table.unaliased_type(node.return_type) if node.return_type.has_flag(.option) && return_type_unaliased.has_flag(.result) { c.error('the fn returns ${c.error_type_name(node.return_type)}, but ${c.error_type_name(node.return_type.clear_flag(.option))} is a Result alias, you can not mix them', node.return_type_pos) } if node.return_type.has_flag(.result) && return_type_unaliased.has_flag(.option) { c.error('the fn returns ${c.error_type_name(node.return_type)}, but ${c.error_type_name(node.return_type.clear_flag(.result))} is an Option alias, you can not mix them', node.return_type_pos) } if node.return_type != ast.void_type { if node.language == .v && node.return_type.clear_option_and_result() == ast.any_type { c.error('cannot use type `any` here', node.return_type_pos) } ct_attr_idx := comptime_define_attr_idx(node.attrs) if ct_attr_idx != ast.invalid_type_idx { sexpr := node.attrs[ct_attr_idx].ct_expr.str() c.error('only functions that do NOT return values can have `@[if ${sexpr}]` tags', node.pos) } if node.generic_names.len > 0 { gs := c.table.sym(node.return_type) mut has_missing_generic_return_type := false if gs.info is ast.Struct { if gs.info.is_generic && !node.return_type.has_flag(.generic) { has_missing_generic_return_type = true c.error('return generic struct `${gs.name}` in fn declaration must specify the generic type names, e.g. ${gs.name}[T]', node.return_type_pos) } } if gs.kind == .struct && !has_missing_generic_return_type && c.needs_unwrap_generic_type(node.return_type) { // resolve generic Array[T], Map[T] generics, avoid recursive generic resolving type if c.ensure_generic_type_specify_type_names(node.return_type, node.return_type_pos, false, false) { c.table.unwrap_generic_type_ex(node.return_type, c.table.cur_fn.generic_names, c.table.cur_concrete_types, true) } } } return_sym := c.table.sym(node.return_type) if return_sym.info is ast.Alias { parent_sym := c.table.sym(return_sym.info.parent_type) if parent_sym.info is ast.ArrayFixed { c.table.find_or_register_array_fixed(parent_sym.info.elem_type, parent_sym.info.size, parent_sym.info.size_expr, true) } if return_sym.name == 'byte' { c.error('byte is deprecated, use u8 instead', node.return_type_pos) } } if return_sym.info is ast.ArrayFixed && c.array_fixed_has_unresolved_size(return_sym.info) { c.unresolved_fixed_sizes << node } final_return_sym := c.table.final_sym(node.return_type) if final_return_sym.info is ast.MultiReturn { for multi_type in final_return_sym.info.types { if multi_type == ast.error_type { c.error('type `IError` cannot be used in multi-return, return an Option instead', node.return_type_pos) } else if multi_type.has_flag(.result) { c.error('result cannot be used in multi-return, return a Result instead', node.return_type_pos) } } } // Ensure each generic type of the parameter was declared in the function's definition if node.return_type.has_flag(.generic) { ret_sym := c.table.sym(node.return_type) // Skip check for forward-declared types (placeholder) where the // generic param names haven't been resolved yet if ret_sym.kind != .placeholder { generic_names := c.table.generic_type_names(node.return_type) for name in generic_names { if name !in node.generic_names { // When a generic struct is used as return type and the struct's // generic param names differ from the fn's (e.g. struct uses T // but fn uses B), skip if the return type sym is the base generic // struct (not yet instantiated with concrete types) if ret_sym.info is ast.Struct && ret_sym.info.is_generic && ret_sym.generic_types.len == 0 { continue } fn_generic_names := node.generic_names.join(', ') c.error('generic type name `${name}` is not mentioned in fn `${node.name}[${fn_generic_names}]`', node.return_type_pos) } } } } } else { for mut a in node.attrs { if a.kind == .comptime_define { node.should_be_skipped = c.evaluate_once_comptime_if_attribute(mut a) } } } if node.is_method { if node.receiver.name in c.global_names { c.error('cannot use global variable name `${node.receiver.name}` as receiver', node.receiver_pos) } if node.receiver.typ.has_flag(.option) { c.error('option types cannot have methods', node.receiver_pos) } mut sym := c.table.sym(node.receiver.typ) if sym.kind == .array && !c.is_builtin_mod && node.kind == .map { // TODO: `node.map in array_builtin_methods` c.error('method overrides built-in array method', node.pos) } else if sym.kind == .sum_type && node.kind == .type_name { c.error('method overrides built-in sum type method', node.pos) } else if sym.kind == .sum_type && node.kind == .type_idx { c.error('method overrides built-in sum type method', node.pos) } else if sym.kind == .multi_return { c.error('cannot define method on multi-value', node.method_type_pos) } if sym.name.len == 1 { // One letter types are reserved for generics. c.error('unknown type `${sym.name}`', node.receiver_pos) return } // make sure interface does not implement its own interface methods if mut sym.info is ast.Interface && sym.has_method(node.name) { // if the method is in info.methods then it is an interface method if sym.info.has_method(node.name) { c.error('interface `${sym.name}` cannot implement its own interface method `${node.name}`', node.pos) } } if mut sym.info is ast.Struct { if field := c.table.find_field(sym, node.name) { field_sym := c.table.sym(field.typ) if field_sym.kind == .function { c.error('type `${sym.name}` has both field and method named `${node.name}`', node.pos) } } if node.kind == .free { if node.return_type != ast.void_type { c.error('`.free()` methods should not have a return type', node.return_type_pos) } if !node.receiver.typ.is_ptr() { tname := sym.name.after_char(`.`) c.error('`.free()` methods should be defined on either a `(mut x &${tname})`, or a `(x &${tname})` receiver', node.receiver_pos) } if node.params.len != 1 { c.error('`.free()` methods should have 0 arguments', node.pos) } } } // needed for proper error reporting during veb route checking if node.method_idx < sym.methods.len { sym.methods[node.method_idx].source_fn = voidptr(node) } else { c.error('method index: ${node.method_idx} >= sym.methods.len: ${sym.methods.len}', node.pos) } } if node.language == .v { // Make sure all types are valid for mut param in node.params { param.typ = c.preferred_c_symbol_type(param.typ) if mut scoped_param := node.scope.find_var(param.name) { scoped_param.typ = param.typ } // handle vls go to definition for parameter types if c.pref.is_vls && c.pref.linfo.method == .definition { if c.vls_is_the_node(param.type_pos) { typ_str := c.table.type_to_str(param.typ) if np := c.name_pos_gotodef(typ_str) { if np.file_idx != -1 { println('${c.table.filelist[np.file_idx]}:${np.line_nr + 1}:${np.col}') } exit(0) } } } if !c.ensure_type_exists(param.typ, param.type_pos) { return } if reserved_type_names_chk.matches(param.name) { c.error('invalid use of reserved type `${param.name}` as a parameter name', param.pos) } if param.typ.has_flag(.result) { c.error('result type arguments are not supported', param.type_pos) } arg_typ_sym := c.table.sym(param.typ) if arg_typ_sym.language == .v && param.typ == ast.any_type && c.file.mod.name != 'builtin' { c.note('the `any` type is deprecated and will be removed soon - either use an empty interface, or a sum type', param.pos) } // resolve unresolved fixed array size e.g. [mod.const]array_type if arg_typ_sym.info is ast.ArrayFixed && c.array_fixed_has_unresolved_size(arg_typ_sym.info) { mut size_expr := unsafe { arg_typ_sym.info.size_expr } param.typ = c.eval_array_fixed_sizes(mut size_expr, 0, arg_typ_sym.info.elem_type) mut v := node.scope.find_var(param.name) or { continue } v.typ = param.typ } else if arg_typ_sym.info is ast.Struct { if !param.typ.is_ptr() && arg_typ_sym.info.is_heap { // set auto_heap to promote value parameter mut v := node.scope.find_var(param.name) or { continue } v.is_auto_heap = true } if arg_typ_sym.info.generic_types.len > 0 && !param.typ.has_flag(.generic) && arg_typ_sym.info.concrete_types.len == 0 { pure_sym_name := arg_typ_sym.embed_name() c.error('generic struct `${pure_sym_name}` in fn declaration must specify the generic type names, e.g. ${pure_sym_name}[T]', param.type_pos) } if param.is_mut && arg_typ_sym.info.attrs.any(it.name == 'params') { c.error('declaring a mutable parameter that accepts a struct with the `@[params]` attribute is not allowed', param.type_pos) } } else if arg_typ_sym.info is ast.Interface { if arg_typ_sym.info.generic_types.len > 0 && !param.typ.has_flag(.generic) && arg_typ_sym.info.concrete_types.len == 0 { pure_sym_name := arg_typ_sym.embed_name() c.error('generic interface `${pure_sym_name}` in fn declaration must specify the generic type names, e.g. ${pure_sym_name}[T]', param.type_pos) } } else if arg_typ_sym.info is ast.SumType { if arg_typ_sym.info.generic_types.len > 0 && !param.typ.has_flag(.generic) && arg_typ_sym.info.concrete_types.len == 0 { pure_sym_name := arg_typ_sym.embed_name() c.error('generic sumtype `${pure_sym_name}` in fn declaration must specify the generic type names, e.g. ${pure_sym_name}[T]', param.type_pos) } } else if arg_typ_sym.info is ast.FnType { if arg_typ_sym.info.func.generic_names.len > 0 && !param.typ.has_flag(.generic) { pure_sym_name := arg_typ_sym.embed_name() c.error('generic function `${pure_sym_name}` in fn declaration must specify the generic type names, e.g. ${pure_sym_name}[T]', param.type_pos) } } // Ensure each generic type of the parameter was declared in the function's definition if param.typ.has_flag(.generic) { generic_names := c.table.generic_type_names(param.typ) for name in generic_names { if name !in node.generic_names { fn_generic_names := node.generic_names.join(', ') c.error('generic type name `${name}` is not mentioned in fn `${node.name}[${fn_generic_names}]`', param.type_pos) } } } if c.pref.skip_unused { if param.typ.has_flag(.generic) { c.table.used_features.comptime_syms[c.unwrap_generic(param.typ)] = true c.table.used_features.comptime_syms[param.typ] = true } if node.return_type.has_flag(.generic) { c.table.used_features.comptime_syms[c.unwrap_generic(node.return_type)] = true c.table.used_features.comptime_syms[node.return_type] = true } if node.receiver.typ.has_flag(.generic) { c.table.used_features.comptime_syms[node.receiver.typ] = true c.table.used_features.comptime_syms[c.unwrap_generic(node.receiver.typ)] = true } } if param.name == node.mod && param.name != 'main' { c.error('duplicate of a module name `${param.name}`', param.pos) } // Check if parameter name is already registered as imported module symbol if c.check_import_sym_conflict(param.name) { c.error('duplicate of an import symbol `${param.name}`', param.pos) } if arg_typ_sym.kind == .alias && arg_typ_sym.name == 'byte' { c.error('byte is deprecated, use u8 instead', param.type_pos) } } if !node.is_method { // Check if function name is already registered as imported module symbol if c.check_import_sym_conflict(node.short_name) { c.error('duplicate of an import symbol `${node.short_name}`', node.pos) } if node.params.len == 0 && node.name.after_char(`.`) == 'init' { if node.is_pub { c.error('fn `init` must not be public', node.pos) } if node.return_type != ast.void_type { c.error('fn `init` cannot have a return type', node.pos) } } if !c.is_builtin_mod && node.mod == 'main' && node.name.after_char(`.`) in reserved_type_names { c.error('top level declaration cannot shadow builtin type', node.pos) } if node.is_static_type_method { if sym := c.table.find_sym(node.name.all_before('__static__')) { if sym.kind == .placeholder { c.error('unknown type `${sym.name}`', node.static_type_pos) } } } } } if node.return_type != ast.no_type { node.return_type = c.preferred_c_symbol_type(node.return_type) if !c.ensure_type_exists(node.return_type, node.return_type_pos) { return } if node.language == .v && node.is_method && node.kind == .str { if node.return_type != ast.string_type { c.error('.str() methods should return `string`', node.pos) } if node.params.len != 1 { c.error('.str() methods should have 0 arguments', node.pos) } } if node.language == .v && node.is_method && node.name in ['+', '-', '*', '%', '/', '<', '=='] { if node.params.len != 2 { c.error('operator methods should have exactly 1 argument', node.pos) } else { receiver_type := node.receiver.typ receiver_sym := c.table.sym(receiver_type) param_type := node.params[1].typ param_sym := c.table.sym(param_type) if param_sym.kind == .string && receiver_sym.kind == .string { // bypass check for strings // TODO: there must be a better way to handle that } else if param_sym.kind !in [.struct, .alias] || receiver_sym.kind !in [.struct, .alias] { c.error('operator methods are only allowed for struct and type alias', node.pos) } else { parent_sym := c.table.final_sym(node.receiver.typ) if node.rec_mut { c.error('receiver cannot be `mut` for operator overloading', node.receiver_pos) } else if node.params[1].is_mut { c.error('argument cannot be `mut` for operator overloading', node.pos) } else if !c.check_same_type_ignoring_pointers(node.receiver.typ, node.params[1].typ) { c.error('expected `${receiver_sym.name}` not `${param_sym.name}` - both operands must be the same type for operator overloading', node.params[1].type_pos) } else if node.return_type != ast.bool_type && node.name in ['<', '=='] { c.error('operator comparison methods should return `bool`', node.pos) } else if parent_sym.is_primitive() { if node.return_type.has_option_or_result() { c.error('return type cannot be Option or Result', node.return_type_pos) } else if node.name in ['+', '-', '*', '**', '%', '/'] && node.return_type != receiver_type { srtype := c.table.type_to_str(receiver_type) c.error('operator `${node.name}` methods on primitive aliases should return `${srtype}`', node.return_type_pos) } // aliases of primitive types are explicitly allowed } else if receiver_type != param_type { srtype := c.table.type_to_str(receiver_type) sptype := c.table.type_to_str(param_type) c.error('the receiver type `${srtype}` should be the same type as the operand `${sptype}`', node.pos) } else if node.return_type.has_option_or_result() { c.error('return type cannot be Option or Result', node.return_type_pos) } } } } if node.language == .v && node.is_method && node.name == '[]' { if node.params.len != 2 { c.error('index operator methods should have exactly 1 argument', node.pos) } else { receiver_type := node.receiver.typ receiver_sym := c.table.sym(receiver_type) index_sym := c.table.sym(node.params[1].typ) if index_sym.kind == .placeholder { c.error('unknown type `${index_sym.name}`', node.params[1].type_pos) } if receiver_sym.kind !in [.struct, .alias] { c.error('index operator methods are only allowed for struct and type alias', node.pos) } else if node.rec_mut { c.error('receiver cannot be `mut` for `[]`, use `[]=` for writable indexing', node.receiver_pos) } else if node.params[1].is_mut { c.error('argument cannot be `mut` for operator overloading', node.pos) } else if node.return_type == ast.void_type { c.error('index operator methods should return a value', node.return_type_pos) } } } if node.language == .v && node.is_method && node.name == '[]=' { if node.params.len != 3 { c.error('index assignment operator methods should have exactly 2 arguments', node.pos) } else { receiver_sym := c.table.sym(node.receiver.typ) index_sym := c.table.sym(node.params[1].typ) value_sym := c.table.sym(node.params[2].typ) if index_sym.kind == .placeholder { c.error('unknown type `${index_sym.name}`', node.params[1].type_pos) } if value_sym.kind == .placeholder { c.error('unknown type `${value_sym.name}`', node.params[2].type_pos) } if receiver_sym.kind !in [.struct, .alias] { c.error('index assignment operator methods are only allowed for struct and type alias', node.pos) } else if !node.rec_mut { c.error('receiver must be `mut` for `[]=` operator overloading', node.receiver_pos) } else if node.params[1].is_mut || node.params[2].is_mut { c.error('arguments cannot be `mut` for operator overloading', node.pos) } else if node.return_type != ast.void_type { c.error('index assignment operator methods cannot return a value', node.return_type_pos) } } } } // TODO: c.pref.is_vet if c.file.is_test && (!node.is_method && (node.short_name.starts_with('test_') || node.short_name.starts_with('testsuite_') || node.short_name in ['before_each', 'after_each'])) { if !c.pref.is_test { // simple heuristic for st in node.stmts { if st is ast.AssertStmt { c.warn('tests will not be run, because filename does not end with `_test.v`', node.pos) break } } } if node.params.len != 0 { c.error('test functions should take 0 parameters', node.pos) } if node.return_type != ast.void_type_idx && node.return_type.clear_flag(.option) != ast.void_type_idx && node.return_type.clear_flag(.result) != ast.void_type_idx { c.error('test functions should either return nothing at all, or be marked to return `?` or `!`', node.pos) } } c.expected_type = ast.void_type saved_generic_names := node.generic_names mut needs_generic_names_restore := false saved_return_type := node.return_type if c.table.cur_concrete_types.len > 0 && effective_generic_names.len == c.table.cur_concrete_types.len && node.generic_names != effective_generic_names { unsafe { mut p := &[]string(&node.generic_names) *p = effective_generic_names.clone() } needs_generic_names_restore = true } c.table.cur_fn = unsafe { node } if c.table.cur_concrete_types.len > 0 { resolved_return_type := c.recheck_concrete_type(node.return_type) if resolved_return_type != ast.void_type && resolved_return_type != 0 { node.return_type = resolved_return_type } } // Add return if `fn(...) ? {...}` have no return at end if node.return_type != ast.void_type && node.return_type.has_flag(.option) && (node.stmts.len == 0 || node.stmts.last() !is ast.Return) { sym := c.table.sym(node.return_type) if sym.kind == .void { return_pos := if node.stmts.len == 0 { node.pos } else { node.stmts.last().pos } node.stmts << ast.Return{ scope: node.scope pos: return_pos // node.pos } } } // same for result `fn (...) ! { ... }` if node.return_type != ast.void_type && node.return_type.has_flag(.result) && (node.stmts.len == 0 || node.stmts.last() !is ast.Return) { sym := c.table.sym(node.return_type) if sym.kind == .void { node.stmts << ast.Return{ scope: node.scope pos: node.pos } } } c.fn_scope = node.scope c.refresh_generic_fn_scope_vars(node) // Register implicit context var typ_veb_result := c.table.get_veb_result_type_idx() // c.table.find_type('veb.Result') if node.is_method && node.return_type == typ_veb_result { is_veb_app_method := !c.has_veb_context(node.receiver.typ) for param in node.params[1..] { if is_veb_app_method && c.has_veb_context(param.typ) && !param.is_mut { c.error('veb app method `${node.name}` must declare context parameter `${param.name}` as mutable, e.g. `mut ${param.name} ${c.table.type_to_str(param.typ)}`', param.pos) break } } // Find a custom user Context type first mut ctx_idx := c.table.find_type('main.Context') if ctx_idx < 1 { // If it doesn't exist, use veb.Context ctx_idx = c.table.find_type('veb.Context') } typ_veb_context := ctx_idx.set_nr_muls(1) // No Context type param? Add it if !node.params.any(c.has_veb_context(it.typ)) && node.params.len >= 1 { params := node.params.clone() ctx_param := ast.Param{ name: 'ctx' typ: typ_veb_context is_mut: true } node.params = [node.params[0], ctx_param] node.params << params[1..] // println('new params ${node.name}') // println(node.params) // We've added ctx to the FnDecl node. // Now update the existing method, already registered in Table. mut rec_sym := c.table.sym(node.receiver.typ) if mut m := c.table.find_method(rec_sym, node.name) { p := m.params.clone() m.params = [m.params[0], ctx_param] m.params << p[1..] rec_sym.update_method(m) } } // sym := c.table.sym(typ_veb_context) // println('reging ${typ_veb_context} ${sym}') // println(c.fn_scope) // println(node.params) // Finally add ctx to the scope c.fn_scope.register(ast.Var{ name: 'ctx' typ: typ_veb_context pos: node.pos is_used: true is_mut: true is_stack_obj: false // true }) if is_veb_app_method { for param in node.params[1..] { if c.has_veb_context(param.typ) || c.supports_veb_string_bound_param(param.typ) { continue } c.error('veb app method `${node.name}` parameter `${param.name}` has unsupported type `${c.table.type_to_str(param.typ)}`; parameters after the context are populated from strings and must be `string`, integer, or `bool`', param.pos) } } } c.stmts(mut node.stmts) node_has_top_return := c.has_top_return(node.stmts) node.has_return = c.returns || node_has_top_return c.check_noreturn_fn_decl(mut node) if node.language == .v && !node.no_body && node.return_type != ast.void_type && !node.has_return && !node.is_noreturn { if c.inside_anon_fn { c.error('missing return at the end of an anonymous function', node.pos) } else if !node.attrs.contains('_naked') { c.error('missing return at end of function `${node.name}`', node.pos) } } if !node.has_return { // for `node.has_return`, checking in `return.v` `return_stmt` old_inside_defer := c.inside_defer c.inside_defer = true for i := c.table.cur_fn.defer_stmts.len - 1; i >= 0; i-- { c.stmts(mut c.table.cur_fn.defer_stmts[i].stmts) } c.inside_defer = old_inside_defer } node.source_file = c.file if node.name in c.table.fns { if node.name != 'main.main' { mut dep_names := []string{} for stmt in node.stmts { dnames := c.table.dependent_names_in_stmt(stmt) for dname in dnames { if dname in dep_names { continue } dep_names << dname } } if dep_names.len > 0 { unsafe { c.table.fns[node.name].dep_names = dep_names } } } unsafe { c.table.fns[node.name].source_fn = voidptr(node) } } if node.is_expand_simple_interpolation { match true { !node.is_method { c.error('@[expand_simple_interpolation] is supported only on methods', node.pos) } node.params.len != 2 { c.error('methods tagged with @[expand_simple_interpolation], should have exactly 1 argument', node.pos) } !node.params[1].typ.is_string() { c.error('methods tagged with @[expand_simple_interpolation], should accept a single string', node.pos) } else {} } } if needs_generic_names_restore { unsafe { mut p := &[]string(&node.generic_names) *p = saved_generic_names } } node.return_type = saved_return_type } // check_same_type_ignoring_pointers util function to check if the Types are the same, including all // corner cases. // FIXME: if the optimization is done after the checker, we can safely remove this util function fn (c &Checker) check_same_type_ignoring_pointers(type_a ast.Type, type_b ast.Type) bool { // FIXME: if possible pass the ast.Node and check the property `is_auto_rec` if type_a != type_b { // before failing we must be sure that the parser didn't optimize the function clean_type_a := type_a.set_nr_muls(0) clean_type_b := type_b.set_nr_muls(0) return clean_type_a == clean_type_b } return true } fn (mut c Checker) expected_callback_fn() ?ast.Fn { if c.expected_type in [0, ast.void_type] { return none } expected_type := c.unwrap_generic(c.recheck_concrete_type(c.expected_type)) expected_sym := c.table.final_sym(expected_type) if expected_sym.kind != .function || expected_sym.info !is ast.FnType { return none } return (expected_sym.info as ast.FnType).func } fn (mut c Checker) omitted_callback_param(expected_param ast.Param, idx int, pos token.Pos, prefix string) ast.Param { param_type := c.recheck_concrete_type(expected_param.typ) return ast.Param{ pos: pos name: '${prefix}${idx}' is_mut: expected_param.is_mut is_shared: expected_param.is_shared is_atomic: expected_param.is_atomic typ: param_type orig_typ: if expected_param.orig_typ == 0 { param_type } else { expected_param.orig_typ } type_pos: pos on_newline: expected_param.on_newline } } fn (mut c Checker) append_omitted_callback_params(mut params []ast.Param, expected_params []ast.Param, pos token.Pos, prefix string, scope &ast.Scope) { if params.len >= expected_params.len { return } for idx in params.len .. expected_params.len { param := c.omitted_callback_param(expected_params[idx], idx, pos, prefix) params << param if scope != unsafe { nil } { unsafe { mut scope_ := &ast.Scope(scope) scope_.register(ast.Var{ name: param.name typ: param.typ generic_typ: if param.typ.has_flag(.generic) { param.typ } else { ast.Type(0) } is_mut: c.implicit_mutability_enabled() || param.is_mut is_auto_deref: param.is_mut pos: param.pos is_used: true is_arg: true is_stack_obj: !param.typ.has_flag(.shared_f) && (param.is_mut || param.typ.is_ptr()) }) } } } } fn (mut c Checker) expand_anon_fn_callback_signature(mut node ast.AnonFn) { expected_fn := c.expected_callback_fn() or { return } if node.decl.params.len >= expected_fn.params.len || node.decl.is_variadic || expected_fn.is_variadic { return } mut params := node.decl.params.clone() c.append_omitted_callback_params(mut params, expected_fn.params, node.decl.pos, '__v_anon_unused_param_', node.decl.scope) node.decl.params = params mut func := ast.Fn{ params: params is_variadic: node.decl.is_variadic return_type: node.decl.return_type is_method: false } name := c.table.get_anon_fn_name(c.file.unique_prefix, func, node.decl.pos) func.name = name node.decl = ast.FnDecl{ ...node.decl name: name params: params } idx := c.table.find_or_register_fn_type(func, true, false) node.typ = if node.decl.generic_names.len > 0 { ast.new_type(idx).set_flag(.generic) } else { ast.new_type(idx) } } fn (mut c Checker) anon_fn(mut node ast.AnonFn) ast.Type { keep_fn := c.table.cur_fn keep_inside_anon := c.inside_anon_fn keep_anon_fn := c.cur_anon_fn keep_anon_fn_generic_names := c.anon_fn_generic_names.clone() keep_anon_fn_concrete_types := c.anon_fn_concrete_types.clone() c.table.used_features.anon_fn = true defer { c.table.cur_fn = keep_fn c.inside_anon_fn = keep_inside_anon c.cur_anon_fn = keep_anon_fn c.anon_fn_generic_names = keep_anon_fn_generic_names c.anon_fn_concrete_types = keep_anon_fn_concrete_types } if node.decl.no_body { c.error('anonymous function must declare a body', node.decl.pos) } c.expand_anon_fn_callback_signature(mut node) for param in node.decl.params { if param.name == '' { c.error('use `_` to name an unused parameter', param.pos) } } mut can_use_outer_generic_context := node.decl.generic_names.len > 0 c.table.cur_fn = unsafe { &node.decl } c.inside_anon_fn = true c.cur_anon_fn = unsafe { node } mut has_generic := false for mut var in node.inherited_vars { parent_var := node.decl.scope.parent.find_var(var.name) or { panic('unexpected checker error: cannot find parent of inherited variable `${var.name}`') } captures_auto_deref_by_value := parent_var.is_auto_deref && !var.is_mut mut declared_parent_typ := parent_var.typ if keep_fn != unsafe { nil } { if keep_fn.is_method && keep_fn.receiver.name == var.name { declared_parent_typ = keep_fn.receiver.typ } else { for param in keep_fn.params { if param.name == var.name { declared_parent_typ = param.typ break } } } } if var.is_mut && !parent_var.is_mut { c.error('original `${parent_var.name}` is immutable, declare it with `mut` to make it mutable', var.pos) } ptyp := c.visible_var_type_for_read(parent_var) node.has_ct_var = node.has_ct_var || var.name in [c.comptime.comptime_for_field_var, c.comptime.comptime_for_method_var] if declared_parent_typ != ast.no_type { parent_var_sym := c.table.final_sym(declared_parent_typ) if parent_var_sym.info is ast.FnType { ret_typ := parent_var_sym.info.func.return_type if c.type_has_unresolved_generic_parts(ret_typ) { generic_names := c.table.generic_type_names(ret_typ) curr_list := node.decl.generic_names.join(', ') for name in generic_names { if name !in node.decl.generic_names { can_use_outer_generic_context = false c.error('Add the generic type `${name}` to the anon fn generic list type, that is currently `[${curr_list}]`', var.pos) } } } } } if parent_var.expr is ast.IfGuardExpr { sym := c.table.sym(parent_var.expr.expr_type) if sym.info is ast.MultiReturn { for i, v in parent_var.expr.vars { if v.name == var.name { var.typ = sym.info.types[i] break } } } else { var.typ = parent_var.expr.expr_type.clear_option_and_result() } } else { var.typ = ptyp } if captures_auto_deref_by_value && var.typ.is_ptr() { var.typ = var.typ.deref() } if c.is_nocopy_struct(var.typ) { c.error('cannot capture @[nocopy] struct by value: use a reference instead', var.pos) } if c.type_has_unresolved_generic_parts(declared_parent_typ) { has_generic = true } node.decl.scope.update_var_type(var.name, var.typ) if captures_auto_deref_by_value { if mut captured_var := node.decl.scope.find_var(var.name) { captured_var.is_auto_deref = false captured_var.typ = var.typ if captured_var.orig_type.is_ptr() { captured_var.orig_type = captured_var.orig_type.deref() } if captured_var.smartcasts.len > 0 { captured_var.smartcasts = captured_var.smartcasts.map(if it.is_ptr() { it.deref() } else { it }) } } } } c.anon_fn_generic_names = []string{} c.anon_fn_concrete_types = []ast.Type{} if can_use_outer_generic_context && keep_fn != unsafe { nil } && keep_fn.generic_names.len == c.table.cur_concrete_types.len { for generic_name in node.decl.generic_names { generic_idx := keep_fn.generic_names.index(generic_name) if generic_idx >= 0 { c.anon_fn_generic_names << generic_name c.anon_fn_concrete_types << c.table.cur_concrete_types[generic_idx] } } for i, generic_name in keep_fn.generic_names { if generic_name !in c.anon_fn_generic_names { c.anon_fn_generic_names << generic_name c.anon_fn_concrete_types << c.table.cur_concrete_types[i] } } } if has_generic && node.decl.generic_names.len == 0 { c.error('generic closure fn must specify type parameter, e.g. fn [foo] [T]()', node.decl.pos) } // Refresh param scope vars before checking stmts, so that generic params // resolve to the current concrete types (not stale types from a previous // generic instantiation pass). if c.table.cur_concrete_types.len > 0 && node.decl.generic_names.len > 0 && node.decl.generic_names.len == c.table.cur_concrete_types.len { for param in node.decl.params { param_type := if resolved := c.table.convert_generic_type(param.typ, node.decl.generic_names, c.table.cur_concrete_types) { c.unwrap_generic(resolved) } else { c.table.unwrap_generic_type_ex(param.typ, node.decl.generic_names, c.table.cur_concrete_types, true) } if mut param_var := node.decl.scope.find_var(param.name) { if param_var.generic_typ == 0 && (param.typ.has_flag(.generic) || c.type_has_unresolved_generic_parts(param.typ)) { param_var.generic_typ = param.typ } param_var.typ = param_type } } } c.stmts(mut node.decl.stmts) c.fn_decl(mut node.decl) return node.typ } fn (mut c Checker) resolve_self_method_call(mut node ast.CallExpr) bool { if node.is_method || node.name == '' { return false } if c.table.cur_fn == unsafe { nil } || !c.table.cur_fn.is_method { return false } if node.name.contains('.') && !node.name.starts_with('${c.mod}.') { return false } call_name := node.name.all_after_last('.') current_method_name := c.table.cur_fn.name.all_after_last('.') if call_name != current_method_name { return false } receiver_name := c.table.cur_fn.receiver.name node.left = ast.Ident{ pos: node.pos scope: node.scope mod: c.mod name: receiver_name } node.name = call_name node.left_type = c.expr(mut node.left) if node.left_type == ast.void_type { return false } node.is_method = true return true } fn (mut c Checker) call_expr(mut node ast.CallExpr) ast.Type { // Check whether the inner function definition is before the call if node.scope != unsafe { nil } { if var := node.scope.find_var(node.name) { if var.expr is ast.AnonFn && var.pos.pos > node.pos.pos { c.error('unknown function: ${node.name}', node.pos) } } } // If the left expr has an or_block, it needs to be checked for legal or_block statement. mut left_type := ast.void_type match mut node.left { ast.SelectorExpr { if node.name == '' && node.left.or_block.kind != .absent { left_type = c.selector_expr(mut node.left) } else { left_type = c.expr(mut node.left) } } else { left_type = c.expr(mut node.left) } } if node.name == '' { left_type = c.check_expr_option_or_result_call(node.left, left_type) } else { c.check_expr_option_or_result_call(node.left, left_type) } // TODO: merge logic from method_call and fn_call // First check everything that applies to both fns and methods old_inside_fn_arg := c.inside_fn_arg c.inside_fn_arg = true mut continue_check := true node.left_type = left_type // Now call `method_call` or `fn_call` for specific checks. mut typ := ast.void_type if node.is_method { typ = c.method_call(mut node, mut continue_check) } else { typ = c.fn_call(mut node, mut continue_check) } if c.pref.is_vls { c.autocomplete_for_fn_call_expr(node) } if !continue_check { return ast.void_type } c.inside_fn_arg = old_inside_fn_arg arg0 := if node.args.len > 0 { node.args[0] } else { ast.CallArg{} } // autofree: mark args that have to be freed (after saving them in tmp exprs) free_tmp_arg_vars := c.pref.autofree && !c.is_builtin_mod && node.args.len > 0 && !c.inside_const && !arg0.typ.has_flag(.option) && !arg0.typ.has_flag(.result) && !(arg0.expr is ast.CallExpr && (arg0.expr.return_type.has_flag(.option) || arg0.expr.return_type.has_flag(.result))) if free_tmp_arg_vars { for i, arg in node.args { if arg.typ != ast.string_type { continue } if arg.expr in [ast.Ident, ast.StringLiteral, ast.SelectorExpr, ast.ComptimeSelector] || autofree_expr_has_or_block_in_chain(arg.expr) { // Simple expressions like variables, string literals, selector expressions // (`x.field`) can't result in allocations and don't need to be assigned to // temporary vars. // Only expressions like `str + 'b'` need to be freed. // Expressions with result/option propagation in the call chain (e.g. // `get_str()!.to_upper()`) cannot be pre-generated safely by // autofree_call_pregen, because the or_block unwrapping internally calls // go_before_last_stmt() which garbles the output buffer. continue } if arg.expr is ast.CallExpr && arg.expr.name in ['json.encode', 'json.encode_pretty'] { continue } node.args[i].is_tmp_autofree = true } // TODO: copy pasta from above if node.receiver_type == ast.string_type && node.left !in [ast.Ident, ast.StringLiteral, ast.SelectorExpr] { node.free_receiver = true } } if node.nr_ret_values == -1 && node.return_type != 0 { if node.return_type == ast.void_type { node.nr_ret_values = 0 } else { ret_sym := c.table.sym(node.return_type) if ret_sym.info is ast.MultiReturn { node.nr_ret_values = ret_sym.info.types.len } else { node.nr_ret_values = 1 } } } old_expected_or_type := c.expected_or_type c.expected_or_type = node.return_type.clear_flag(.result) c.stmts_ending_with_expression(mut node.or_block.stmts, c.expected_or_type) if node.or_block.kind == .block && !node.or_block.err_used { if err_var := node.or_block.scope.find_var('err') { node.or_block.err_used = err_var.is_used } } if node.or_block.kind == .block { mut return_context_type := ast.no_type if c.inside_return && c.table.cur_fn != unsafe { nil } && node.or_block.stmts.len > 0 { last_stmt := node.or_block.stmts.last() if last_stmt is ast.ExprStmt { if last_stmt.typ == ast.none_type && c.table.cur_fn.return_type.has_flag(.option) { return_context_type = c.table.cur_fn.return_type } else if last_stmt.typ == ast.error_type && c.table.cur_fn.return_type.has_flag(.result) { return_context_type = c.table.cur_fn.return_type } } } if return_context_type != ast.no_type { typ = return_context_type } else { old_inside_or_block_value := c.inside_or_block_value c.inside_or_block_value = true last_cur_or_expr := c.cur_or_expr c.cur_or_expr = &node.or_block c.check_or_expr(node.or_block, typ, c.expected_or_type, node) c.cur_or_expr = last_cur_or_expr c.inside_or_block_value = old_inside_or_block_value } } c.expected_or_type = old_expected_or_type c.markused_call_expr(left_type, mut node) if !c.inside_const && c.table.cur_fn != unsafe { nil } && !c.table.cur_fn.is_main && !c.table.cur_fn.is_test { // TODO: use just `if node.or_block.kind == .propagate_result && !c.table.cur_fn.return_type.has_flag(.result) {` after the deprecation for ?!Type if node.or_block.kind == .propagate_result && !c.table.cur_fn.return_type.has_flag(.result) && !c.table.cur_fn.return_type.has_flag(.option) { c.add_instruction_for_result_type() c.error('to propagate the Result call, `${c.table.cur_fn.name}` must return a Result', node.or_block.pos) } if node.or_block.kind == .propagate_option && !c.table.cur_fn.return_type.has_flag(.option) { c.add_instruction_for_option_type() c.error('to propagate the Option call, `${c.table.cur_fn.name}` must return an Option', node.or_block.pos) } } return typ } fn (mut c Checker) builtin_args(mut node ast.CallExpr, fn_name string, func &ast.Fn) { c.inside_interface_deref = true c.expected_type = ast.string_type if !(node.language != .js && node.args[0].expr is ast.CallExpr) { node.args[0].typ = c.expr(mut node.args[0].expr) } arg := node.args[0] c.check_expr_option_or_result_call(arg.expr, arg.typ) if arg.typ.is_void() { c.error('`${fn_name}` can not print void expressions', node.pos) } else if arg.typ == ast.char_type && arg.typ.nr_muls() == 0 { c.error('`${fn_name}` cannot print type `char` directly, print its address or cast it to an integer instead', node.pos) } else if arg.expr is ast.ArrayDecompose { c.error('`${fn_name}` cannot print variadic values', node.pos) } else if c.fail_if_private_implicit_str(arg.typ, node.pos, 'print') { return } c.fail_if_unreadable(arg.expr, arg.typ, 'argument to print') c.inside_interface_deref = false node.return_type = ast.void_type c.set_node_expected_arg_types(mut node, func) /* // TODO: optimize `struct T{} fn (t &T) str() string {return 'abc'} mut a := []&T{} a << &T{} println(a[0])` // It currently generates: // `println(T_str_no_ptr(*(*(T**)array_get(a, 0))));` // ... which works, but could be just: // `println(T_str(*(T**)array_get(a, 0)));` prexpr := node.args[0].expr prtyp := node.args[0].typ prtyp_sym := c.table.sym(prtyp) prtyp_is_ptr := prtyp.is_ptr() prhas_str, prexpects_ptr, prnr_args := prtyp_sym.str_method_info() eprintln('>>> println hack typ: ${prtyp} | sym.name: ${prtyp_sym.name} | is_ptr: ${prtyp_is_ptr} | has_str: ${prhas_str} | expects_ptr: ${prexpects_ptr} | nr_args: ${prnr_args} | expr: ${prexpr.str()} ') */ } fn (mut c Checker) try_resolve_c_type_cast_call(mut node ast.CallExpr) ?ast.Type { if node.language != .c || !node.name.starts_with('C.') || node.args.len != 1 { return none } c_name := node.name.all_after('C.') if c_name.len == 0 || !c_name[0].is_capital() { return none } to_type := c.table.find_type(node.name) if to_type == 0 { return none } to_sym := c.table.sym(to_type) if to_sym.kind == .placeholder { return none } mut cast_expr := ast.CastExpr{ typ: to_type typname: to_sym.name expr: node.args[0].expr pos: node.pos } typ := c.cast_expr(mut cast_expr) node.is_c_type_cast = true node.args[0].expr = cast_expr.expr node.args[0].typ = cast_expr.expr_type node.return_type = typ return typ } fn (mut c Checker) needs_unwrap_generic_type(typ ast.Type) bool { if typ == 0 { return false } if c.type_has_unresolved_generic_parts(typ) { return true } if !typ.has_flag(.generic) { return false } sym := c.table.sym(typ) match sym.info { ast.Struct, ast.Interface, ast.SumType { return true } ast.Array { return c.needs_unwrap_generic_type(sym.info.elem_type) } ast.ArrayFixed { return c.needs_unwrap_generic_type(sym.info.elem_type) } ast.Map { if c.needs_unwrap_generic_type(sym.info.key_type) { return true } if c.needs_unwrap_generic_type(sym.info.value_type) { return true } } ast.Chan { return c.needs_unwrap_generic_type(sym.info.elem_type) } ast.Thread { return c.needs_unwrap_generic_type(sym.info.return_type) } else { return false } } return false } fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast.Type { is_va_arg := node.kind == .va_arg is_json_decode := node.kind == .json_decode is_json_encode := node.kind == .json_encode mut fn_name := node.name if node.is_static_method { // resolve static call T.name() if c.table.cur_fn != unsafe { nil } { node.left_type, fn_name = c.table.convert_generic_static_type_name(fn_name, c.table.cur_fn.generic_names, c.table.cur_concrete_types) c.table.used_features.comptime_calls[fn_name] = true } } if !c.file.is_test && node.kind == .main { c.error('the `main` function cannot be called in the program', node.pos) } mut has_generic := false // foo[T]() instead of foo[int]() mut concrete_types := []ast.Type{} node.concrete_types = node.raw_concrete_types for concrete_type in node.concrete_types { if concrete_type.has_flag(.generic) || (c.type_has_unresolved_generic_parts(concrete_type) && c.table.sym(concrete_type).kind != .placeholder) { has_generic = true concrete_types << c.unwrap_generic(concrete_type) } else { concrete_types << concrete_type } } if c.table.cur_fn != unsafe { nil } && c.table.cur_concrete_types.len == 0 && has_generic { c.error('generic fn using generic types cannot be called outside of generic fn', node.pos) } fkey := node.fkey() fn_name_has_dot := fn_name.contains('.') if concrete_types.len > 0 { mut no_exists := true if fn_name_has_dot { no_exists = c.table.register_fn_concrete_types(fkey, concrete_types) } else { no_exists = c.table.register_fn_concrete_types(c.mod + '.' + fkey, concrete_types) // if the generic fn does not exist in the current fn calling module, continue // to look in builtin module if !no_exists { no_exists = c.table.register_fn_concrete_types(fkey, concrete_types) } } if no_exists { c.need_recheck_generic_fns = true } full_fkey := if fn_name_has_dot { fkey } else { c.mod + '.' + fkey } c.generic_call_positions[c.build_generic_call_key(full_fkey, concrete_types)] = node.pos } mut args_len := node.args.len if node.kind == .jsawait { if node.args.len > 1 { c.error('JS.await expects 1 argument, a promise value (e.g `JS.await(fs.read())`', node.pos) return ast.void_type } typ := c.expr(mut node.args[0].expr) tsym := c.table.sym(typ) if !tsym.name.starts_with('Promise[') { c.error('JS.await: first argument must be a promise, got `${tsym.name}`', node.pos) return ast.void_type } if c.table.cur_fn != unsafe { nil } { c.table.cur_fn.has_await = true } match tsym.info { ast.Struct { mut ret_type := tsym.info.concrete_types[0] ret_type = ret_type.set_flag(.option) node.return_type = ret_type return ret_type } else { c.error('JS.await: Promise must be a struct type', node.pos) return ast.void_type } } panic('unreachable') } else if args_len > 0 && node.args[0].typ.has_flag(.shared_f) && node.kind == .json_encode { c.error('json.encode cannot handle shared data', node.pos) return ast.void_type } else if args_len > 0 && (is_va_arg || is_json_decode) { if args_len != 2 { if is_json_decode { c.error("json.decode expects 2 arguments, a type and a string (e.g `json.decode(T, '')`)", node.pos) } else { c.error('C.va_arg expects 2 arguments, a type and va_list (e.g `C.va_arg(int, va)`)', node.pos) } return ast.void_type } mut expr := node.args[0].expr if mut expr is ast.TypeNode { expr.typ = c.expr(mut expr) mut unwrapped_typ := c.unwrap_generic(expr.typ) if c.needs_unwrap_generic_type(expr.typ) { unwrapped_typ = c.table.unwrap_generic_type(expr.typ, c.table.cur_fn.generic_names, c.table.cur_concrete_types) } sym := c.table.sym(unwrapped_typ) if c.table.known_type(sym.name) && sym.kind != .placeholder { mut kind := sym.kind if sym.info is ast.Alias { kind = c.table.sym(sym.info.parent_type).kind } else if sym.kind == .generic_inst && sym.info is ast.GenericInst { parent_sym := c.table.sym(ast.new_type(sym.info.parent_idx)) kind = parent_sym.kind } if is_json_decode && c.table.fully_unaliased_type(unwrapped_typ).is_ptr() { c.error('${fn_name}: cannot decode into a pointer type', expr.pos) } else if is_json_decode && kind !in [.struct, .sum_type, .map, .array] { c.error('${fn_name}: expected sum type, struct, map or array, found ${kind}', expr.pos) } } else { c.error('${fn_name}: unknown type `${sym.name}`', node.pos) } } else { typ := expr.type_name() c.error('${fn_name}: first argument needs to be a type, got `${typ}`', node.pos) return ast.void_type } c.expected_type = ast.string_type node.args[1].typ = c.expr(mut node.args[1].expr) if is_json_decode && node.args[1].typ != ast.string_type { c.error('json.decode: second argument needs to be a string', node.pos) } typ := expr as ast.TypeNode node.return_type = if is_json_decode { typ.typ.set_flag(.result) } else { typ.typ } if typ.typ.has_flag(.generic) { c.table.used_features.comptime_syms[c.unwrap_generic(typ.typ)] = true } return node.return_type } else if node.kind == .addr { if !c.inside_unsafe { c.error('`__addr` must be called from an unsafe block', node.pos) } if args_len != 1 { c.error('`__addr` requires 1 argument', node.pos) return ast.void_type } typ := c.expr(mut node.args[0].expr) node.args[0].typ = typ node.return_type = typ.ref() return node.return_type } // look for function in format `mod.fn` or `fn` (builtin) mut func := ast.Fn{} mut found := false mut found_in_args := false defer { if found { c.check_must_use_call_result(node, func, 'function') } } // anon fn direct call if node.left is ast.AnonFn { // it was set to anon for checker errors, clear for gen node.name = '' left := node.left as ast.AnonFn if left.typ != ast.no_type { anon_fn_sym := c.table.sym(left.typ) func = (anon_fn_sym.info as ast.FnType).func found = true } } // try prefix with current module as it would have never gotten prefixed if !found && node.mod != 'builtin' && !fn_name_has_dot { name_prefixed := '${node.mod}.${fn_name}' if f := c.table.find_fn(name_prefixed) { node.name = name_prefixed found = true func = f unsafe { c.table.fns[name_prefixed].usages++ } c.mark_fn_decl_as_referenced(f.fkey()) } } if !found && node.left is ast.IndexExpr { left := node.left as ast.IndexExpr sym := c.table.final_sym(left.left_type) if sym.info is ast.Array { elem_sym := c.table.sym(sym.info.elem_type) if elem_sym.info is ast.FnType { func = elem_sym.info.func found = true node.is_fn_var = true node.fn_var_type = sym.info.elem_type } else { c.error('cannot call the element of the array, it is not a function', node.pos) } } else if sym.info is ast.Map { value_sym := c.table.sym(sym.info.value_type) if value_sym.info is ast.FnType { func = value_sym.info.func found = true node.is_fn_var = true node.fn_var_type = sym.info.value_type } else { c.error('cannot call the value of the map, it is not a function', node.pos) } } else if sym.info is ast.ArrayFixed { elem_sym := c.table.sym(sym.info.elem_type) if elem_sym.info is ast.FnType { func = elem_sym.info.func found = true node.is_fn_var = true node.fn_var_type = sym.info.elem_type } else { c.error('cannot call the element of the array, it is not a function', node.pos) } } } if !found && node.left is ast.CallExpr { left := node.left as ast.CallExpr if left.return_type != 0 { sym := c.table.sym(left.return_type) if sym.info is ast.FnType { node.return_type = sym.info.func.return_type found = true func = sym.info.func } } } if !found && node.name == '' && node.left_type != 0 { left_sym := c.table.final_sym(c.unwrap_generic(node.left_type)) if left_sym.info is ast.FnType { func = left_sym.info.func found = true node.is_fn_var = true node.fn_var_type = node.left_type } else if left_sym.info is ast.GenericInst { parent_sym := c.table.sym(ast.new_type(left_sym.info.parent_idx)) if parent_sym.info is ast.FnType { func = parent_sym.info.func found = true node.is_fn_var = true node.fn_var_type = node.left_type } } } // already prefixed (mod.fn) or C/builtin/main if !found { if f := c.table.find_fn(fn_name) { found = true func = f unsafe { c.table.fns[fn_name].usages++ } c.mark_fn_decl_as_referenced(f.fkey()) } } // static method resolution if !found && node.is_static_method { if index := fn_name.index('__static__') { owner_name := fn_name#[..index] // already imported symbol (static Foo.new() in another module) for import_sym in c.file.imports.filter(it.syms.any(it.name == owner_name)) { qualified_name := '${import_sym.mod}.${fn_name}' if f := c.table.find_fn(qualified_name) { found = true func = f node.name = qualified_name unsafe { c.table.fns[qualified_name].usages++ } c.mark_fn_decl_as_referenced(f.fkey()) if !c.table.register_fn_concrete_types(f.name, concrete_types) { c.need_recheck_generic_fns = true } break } } if !found { // aliased static method on current mod full_type_name := if !fn_name_has_dot { c.mod + '.' + owner_name } else { owner_name } typ := c.table.find_type(full_type_name) if typ != 0 { final_sym := c.table.final_sym(typ) // try to find the unaliased static method name orig_name := final_sym.name + fn_name#[index..] if f := c.table.find_fn(orig_name) { found = true func = f unsafe { c.table.fns[orig_name].usages++ } c.mark_fn_decl_as_referenced(f.fkey()) node.name = orig_name node.left_type = typ } } } } // Enum.from_string, `mod.Enum.from_string('item')`, `Enum.from_string('item')` if !found && fn_name.ends_with('__static__from_string') { enum_name := fn_name.all_before('__static__') mut full_enum_name := if !enum_name.contains('.') { c.mod + '.' + enum_name } else { enum_name } mut idx := c.table.type_idxs[full_enum_name] if idx > 0 { // is from another mod. if enum_name.contains('.') { if !c.check_type_and_visibility(full_enum_name, idx, .enum, node.pos) { return ast.void_type } } else { if !c.check_type_sym_kind(full_enum_name, idx, .enum, node.pos) { return ast.void_type } } } else if !enum_name.contains('.') { // find from another mods. for import_sym in c.file.imports { full_enum_name = '${import_sym.mod}.${enum_name}' idx = c.table.type_idxs[full_enum_name] if idx < 1 { continue } if !c.check_type_and_visibility(full_enum_name, idx, .enum, node.pos) { return ast.void_type } break } } if idx == 0 { c.error('unknown enum `${enum_name}`', node.pos) continue_check = false return ast.void_type } ret_typ := ast.idx_to_type(idx).set_flag(.option) if args_len != 1 { c.error('expected 1 argument, but got ${args_len}', node.pos) } else { node.args[0].typ = c.expr(mut node.args[0].expr) if node.args[0].typ != ast.string_type { styp := c.table.type_to_str(node.args[0].typ) c.error('expected `string` argument, but got `${styp}`', node.pos) } } node.return_type = ret_typ return ret_typ } } if !found && c.pref.is_vsh { // TODO: test this hack more extensively os_name := 'os.${fn_name}' if f := c.table.find_fn(os_name) { if f.generic_names.len == node.concrete_types.len { node_alias_name := fkey mut existing := c.table.fn_generic_types[os_name] or { [] } existing << c.table.fn_generic_types[node_alias_name] existing << node.concrete_types c.table.fn_generic_types[os_name] = existing } node.name = os_name found = true func = f unsafe { c.table.fns[os_name].usages++ } c.mark_fn_decl_as_referenced(f.fkey()) } } // check for arg (var) of fn type if !found { mut typ := ast.no_type if mut obj := node.scope.find(node.name) { match mut obj { ast.GlobalField { typ = obj.typ node.is_fn_var = true node.fn_var_type = typ } ast.Var { if obj.smartcasts.len != 0 { typ = c.exposed_smartcast_type(obj.orig_type, obj.smartcasts.last(), obj.is_mut) } else { if obj.typ == 0 { if mut obj.expr is ast.IfGuardExpr { typ = c.expr(mut obj.expr.expr).clear_option_and_result() } else { typ = c.expr(mut obj.expr) } } else { typ = obj.typ } } node.is_fn_var = true node.fn_var_type = typ if typ.nr_muls() > 0 { c.error('function pointer must be undereferenced first', node.pos) } } else {} } } // XTODO document if typ != 0 { if node.concrete_types.len == 0 && typ.has_flag(.generic) && c.table.cur_fn != unsafe { nil } && c.table.cur_fn.is_method && node.name == c.table.cur_fn.receiver.name && c.table.cur_fn.receiver.typ.has_flag(.generic) && c.table.cur_fn.generic_names.len == c.table.cur_concrete_types.len { if fn_typ := c.table.convert_generic_type(typ, c.table.cur_fn.generic_names, c.table.cur_concrete_types) { typ = fn_typ node.fn_var_type = fn_typ } } generic_vts := c.table.final_sym(typ) if generic_vts.info is ast.FnType { func = generic_vts.info.func found = true found_in_args = true } else if generic_vts.info is ast.GenericInst { parent_sym := c.table.sym(ast.new_type(generic_vts.info.parent_idx)) if parent_sym.info is ast.FnType { func = parent_sym.info.func found = true found_in_args = true } } else { vts := c.table.sym(c.unwrap_generic(typ)) if vts.info is ast.FnType { func = vts.info.func found = true found_in_args = true } else if vts.info is ast.GenericInst { parent_sym := c.table.sym(ast.new_type(vts.info.parent_idx)) if parent_sym.info is ast.FnType { func = parent_sym.info.func found = true found_in_args = true } } } } } // global fn? if !found { if obj := c.file.global_scope.find(fn_name) { if obj.typ != 0 { sym := c.table.sym(obj.typ) if sym.info is ast.FnType { func = sym.info.func found = true } } } } // a same module constant? if !found { // allow for `const abc = myfunc`, then calling `abc()` qualified_const_name := if fn_name_has_dot { fn_name } else { '${c.mod}.${fn_name}' } if mut obj := c.table.global_scope.find_const(qualified_const_name) { if obj.typ == 0 { obj.typ = c.expr(mut obj.expr) } if obj.typ != 0 { sym := c.table.sym(obj.typ) if sym.info is ast.FnType { // at this point, the const metadata should be already known, // and we are sure that it is just a function unsafe { c.table.fns[qualified_const_name].usages++ } unsafe { c.table.fns[func.name].usages++ } found = true func = sym.info.func node.is_fn_a_const = true node.fn_var_type = obj.typ node.const_name = qualified_const_name c.mark_const_decl_as_referenced(qualified_const_name) } } } } if !found { if typ := c.try_resolve_c_type_cast_call(mut node) { return typ } if c.resolve_self_method_call(mut node) { return c.method_call(mut node, mut continue_check) } continue_check = false if dot_index := fn_name.index('.') { if !fn_name[0].is_capital() { mod_name := fn_name#[..dot_index] mut mod_func_names := []string{} for ctfnk, ctfnv in c.table.fns { if ctfnv.is_pub && ctfnk.starts_with(mod_name) { mod_func_names << ctfnk } } suggestion := util.new_suggestion(fn_name, mod_func_names) c.error(suggestion.say('unknown function: ${fn_name} '), node.pos) return ast.void_type } } name := node.get_name() if c.pref.experimental && name.starts_with('C.') { println('unknown function ${name}, ' + 'searching for the C definition in one of the #includes') mut includes := []string{cap: 5} for stmt in c.file.stmts { if stmt is ast.HashStmt { if stmt.kind == 'include' { includes << '#include ${stmt.main}' } } } mut tmp_c_file_with_includes := os.create('tmp.c') or { panic(err) } tmp_c_file_with_includes.write_string(includes.join('\n')) or { panic(err) } tmp_c_file_with_includes.close() os.execute('${os.quoted_path(@VEXE)} translate fndef ${name[2..]} tmp.c') x := os.read_file('__cdefs_autogen.v') or { for mut arg in node.args { c.expr(mut arg.expr) } return ast.void_type } if x.contains('fn ${name}') { println( 'function definition for ${name} has been generated in __cdefs_autogen.v. ' + 'Please re-run the compilation with `v .` or `v run .`') os.rm('tmp.c') or {} exit(0) } else { println('Failed to generate function definition. Please report it via github.com/vlang/v/issues') } os.rm('tmp.c') or {} } for mut arg in node.args { c.expr(mut arg.expr) } if name.starts_with('C.') { c.error('unknown C function: `${name}`. `C.` calls are external C calls; declare the function with `fn ${name}(...)` and include/link the C header/library that provides it.', node.pos) return ast.void_type } c.error('unknown function: ${node.get_name()}', node.pos) return ast.void_type } node.is_file_translated = func.is_file_translated node.is_noreturn = func.is_noreturn node.is_expand_simple_interpolation = func.is_expand_simple_interpolation node.is_ctor_new = func.is_ctor_new if node.is_fn_var && node.concrete_types.len > 0 && func.generic_names.len == 0 { if node.raw_concrete_types.len == 0 { node.raw_concrete_types = node.concrete_types.clone() } node.concrete_types = [] } if !found_in_args { if node.scope.known_var(fn_name) { c.error('ambiguous call to: `${fn_name}`, may refer to fn `${fn_name}` or variable `${fn_name}`', node.pos) } } if !func.is_pub && func.language == .v && func.name != '' && func.mod.len > 0 && func.mod != c.mod && !c.pref.is_test { c.error('function `${func.name}` is private', node.pos) } if c.table.cur_fn != unsafe { nil } && !c.table.cur_fn.is_deprecated && func.is_deprecated { c.deprecate('function', func.name, func.attrs, node.pos) } if func.is_unsafe && !c.inside_unsafe && (func.language != .c || (func.name[2] in [`m`, `s`] && func.mod == 'builtin')) { // builtin C.m*, C.s* only - temp if !c.pref.translated && !c.file.is_translated { c.warn('function `${func.name}` must be called from an `unsafe` block', node.pos) } } node.is_keep_alive = func.is_keep_alive if func.language == .v && func.no_body && !c.pref.translated && !c.file.is_translated && !func.is_unsafe && !func.is_file_translated && func.mod != 'builtin' { c.error('cannot call a function that does not have a body', node.pos) } if node.concrete_types.len > 0 && func.generic_names.len > 0 && node.concrete_types.len != func.generic_names.len { plural := if func.generic_names.len == 1 { '' } else { 's' } c.error('expected ${func.generic_names.len} generic parameter${plural}, got ${node.concrete_types.len}', node.concrete_list_pos) } for concrete_type in node.concrete_types { c.ensure_type_exists(concrete_type, node.concrete_list_pos) } if func.generic_names.len > 0 && args_len == 0 && node.concrete_types.len == 0 { c.error('no argument generic function must add concrete types, e.g. foo[int]()', node.pos) return func.return_type } if func.return_type == ast.void_type && func.is_conditional && func.ctdefine_idx != ast.invalid_type_idx { node.should_be_skipped = c.evaluate_once_comptime_if_attribute(mut func.attrs[func.ctdefine_idx]) } if node.kind == .free && func.mod == 'builtin' && args_len == 1 && c.table.cur_fn != unsafe { nil } && c.table.cur_fn.is_method && c.table.cur_fn.short_name != 'free' && !c.is_builtin_mod && !c.inside_recheck { if node.args[0].expr is ast.Ident { if node.args[0].expr.name == c.table.cur_fn.receiver.name { receiver_sym := c.table.sym(c.table.cur_fn.receiver.typ) if !receiver_sym.is_heap() { c.warn('calling builtin `free()` on a method receiver will cause a' + ' runtime crash when the receiver is stack-allocated; free individual fields instead', node.pos) } } } } // dont check number of args for JS functions since arguments are not required if node.language != .js { for i, mut call_arg in node.args { is_call_expr := call_arg.expr is ast.CallExpr if is_call_expr { mut arg_expr := call_arg.expr node.args[i].typ = c.expr(mut arg_expr) call_arg.expr = arg_expr } else if mut call_arg.expr is ast.LambdaExpr { if node.concrete_types.len > 0 { call_arg.expr.call_ctx = unsafe { node } } } } c.check_expected_arg_count(mut node, func) or { node.return_type = func.return_type return func.return_type } args_len = node.args.len } // println / eprintln / panic can print anything if args_len > 0 && fn_name in print_everything_fns && func.mod == 'builtin' { node.args[0].ct_expr = c.comptime.is_comptime(node.args[0].expr) c.builtin_args(mut node, fn_name, func) c.markused_print_call(mut node) return func.return_type } // `return error(err)` -> `return err` if args_len == 1 && node.kind == .error { mut arg := node.args[0] node.args[0].typ = c.expr(mut arg.expr) node.args[0].ct_expr = c.comptime.is_comptime(node.args[0].expr) if node.args[0].typ == ast.error_type { c.warn('`error(${arg})` can be shortened to just `${arg}`', node.pos) } } c.set_node_expected_arg_types(mut node, func) if !c.is_js_backend && args_len > 0 && func.params.len == 0 { c.error('too many arguments in call to `${func.name}` (non-js backend: ${c.pref.backend})', node.pos) } mut has_decompose := false mut has_unresolved_generic_param := false mut nr_multi_values := 0 variadic_start := variadic_call_arg_start_idx(func, false) has_typed_variadic := func.is_variadic && !func.is_c_variadic for i, mut call_arg in node.args { mut variadic_arg_handled := false if func.params.len == 0 { continue } if !c.inside_recheck { call_arg.ct_expr = c.comptime.is_comptime(call_arg.expr) } if !func.is_variadic && has_decompose { c.error('cannot have parameter after array decompose', node.pos) } param_i := i + nr_multi_values mut param := call_arg_param_for_fn(func, param_i, false) if node.is_fn_var && param.typ.has_flag(.generic) && c.table.cur_fn != unsafe { nil } && c.table.cur_fn.generic_names.len > 0 && c.table.cur_fn.generic_names.len == c.table.cur_concrete_types.len { mut unwrapped := param unwrapped.typ = c.table.unwrap_generic_param_type(param, c.table.cur_fn.generic_names, c.table.cur_concrete_types) param = unwrapped } param.typ = c.resolve_call_arg_param_type(call_arg, param, func.generic_names, concrete_types) // registers if the arg must be passed by ref to disable auto deref args call_arg.should_be_ptr = param.typ.is_ptr() && !param.is_mut if func.is_variadic && call_arg.expr is ast.ArrayDecompose { if param_i > variadic_start { c.error('too many arguments in call to `${func.name}`', node.pos) } } has_decompose = call_arg.expr is ast.ArrayDecompose if !func.is_variadic && call_arg.expr is ast.ArrayDecompose { array_decompose_expr := call_arg.expr as ast.ArrayDecompose if array_decompose_expr.expr is ast.ArrayInit { extra_params := func.params.len - i array_init := array_decompose_expr.expr as ast.ArrayInit if array_init.exprs.len < extra_params { elem_word := if array_init.exprs.len == 1 { 'element' } else { 'elements' } verb_word := if extra_params == 1 { 'is' } else { 'are' } c.error('array decompose has ${array_init.exprs.len} ${elem_word} but ${extra_params} ${verb_word} needed for `${func.name}`', call_arg.pos) } } } already_checked := node.language != .js && call_arg.expr is ast.CallExpr if has_typed_variadic && param_i >= variadic_start { param_sym := c.table.sym(param.typ) mut expected_type := param.typ if param_sym.info is ast.Array { expected_type = param_sym.info.elem_type c.expected_type = expected_type } typ := if already_checked && mut call_arg.expr is ast.CallExpr { node.args[i].typ } else { c.expr(mut call_arg.expr) } if i == args_len - 1 { variadic_arg_handled = c.check_variadic_arg(call_arg.expr, typ, expected_type, param.typ, i + 1, func.name, func.is_method, func.is_variadic, args_len == 1 && i == 0, func.generic_names.len > 0, node.pos, call_arg.pos) } } else { c.expected_type = param.typ } e_sym := c.table.sym(c.expected_type) if call_arg.expr is ast.MapInit && e_sym.kind == .struct { c.error('cannot initialize a struct with a map', call_arg.pos) continue } else if call_arg.expr is ast.StructInit && e_sym.kind == .map && !call_arg.expr.typ.has_flag(.generic) { c.error('cannot initialize a map with a struct', call_arg.pos) continue } mut arg_typ := c.check_expr_option_or_result_call(call_arg.expr, if already_checked { node.args[i].typ } else { c.expr(mut call_arg.expr) }) arg_typ = c.maybe_wrap_index_expr_smartcast(mut call_arg.expr, arg_typ) is_struct_init_arg := call_arg.expr is ast.StructInit if is_struct_init_arg { mut arg_expr := call_arg.expr arg_typ = c.expr(mut arg_expr) } node.args[i].expr = call_arg.expr node.args[i].typ = arg_typ call_arg.typ = arg_typ if c.comptime.comptime_for_field_var != '' { if mut call_arg.expr is ast.Ident && call_arg.expr.name == c.comptime.comptime_for_field_var && call_arg.expr.obj is ast.Var { node.args[i].typ = call_arg.expr.obj.typ } } // sumtype coercion param_type_sym := c.table.sym(param.typ) if param_type_sym.kind == .placeholder { base_type := c.table.find_type(param_type_sym.ngname) if base_type != 0 { base_sym := c.table.sym(base_type) if base_sym.kind == .sum_type && base_sym.info is ast.SumType { base_info := base_sym.info as ast.SumType arg_typ_sym := c.table.sym(arg_typ) for variant in base_info.variants { variant_sym := c.table.sym(variant) variant_base_name := variant_sym.ngname if variant_base_name == arg_typ_sym.ngname { node.args[i].expr = ast.CastExpr{ expr: call_arg.expr typ: param.typ typname: c.table.type_to_str(param.typ) pos: call_arg.expr.pos() } node.args[i].typ = param.typ arg_typ = param.typ break } } } } } if param_type_sym.kind == .sum_type && !c.table.sumtype_has_variant(param.typ, arg_typ, false) && c.table.sumtype_has_variant_recursive(param.typ, arg_typ, false) { call_arg.expr = ast.CastExpr{ expr: call_arg.expr typ: param.typ typname: c.table.type_to_str(param.typ) expr_type: arg_typ pos: call_arg.expr.pos() } call_arg.typ = param.typ node.args[i].expr = call_arg.expr node.args[i].typ = param.typ arg_typ = param.typ } mut arg_typ_sym := c.table.sym(arg_typ) if param.typ.has_flag(.generic) { if arg_typ_sym.kind == .none && !param.typ.has_flag(.option) { c.error('cannot use `none` as generic argument', call_arg.pos) } has_unresolved_generic_param = c.check_unresolved_generic_param(node, call_arg) || has_unresolved_generic_param } param_typ_sym := c.table.sym(param.typ) if has_typed_variadic && arg_typ.has_flag(.variadic) && args_len - 1 > i { c.error('when forwarding a variadic variable, it must be the final argument', call_arg.pos) } arg_share := param.typ.share() if arg_share == .shared_t && (c.locked_names.len > 0 || c.rlocked_names.len > 0) { c.error('function with `shared` arguments cannot be called inside `lock`/`rlock` block', call_arg.pos) } call_arg = c.implicit_mut_call_arg(param, call_arg) node.args[i] = call_arg if call_arg.is_mut { to_lock, pos := c.fail_if_immutable(mut call_arg.expr) call_arg_expr_pos := call_arg.expr.pos() if !call_arg.expr.is_lvalue() { if call_arg.expr is ast.StructInit { c.error('cannot pass a struct initialization as `mut`, you may want to use a variable `mut var := ${ast.Expr(call_arg.expr)}`', call_arg_expr_pos) } else { c.error('cannot pass expression as `mut`', call_arg_expr_pos) } } if !param.is_mut { tok := call_arg.share.str() c.error('`${node.name}` parameter `${param.name}` is not `${tok}`, `${tok}` is not needed`', call_arg.expr.pos()) } else { if param.typ.share() != call_arg.share { c.error('wrong shared type `${call_arg.share.str()}`, expected: `${param.typ.share().str()}`', call_arg.expr.pos()) } if to_lock != '' && !param.typ.has_flag(.shared_f) { c.error('${to_lock} is `shared` and must be `lock`ed to be passed as `mut`', pos) } } } else { if param.is_mut { tok := param.specifier() param_sym := c.table.sym(param.typ) if !(param_sym.info is ast.Struct && param_sym.info.attrs.any(it.name == 'params')) { c.error('function `${node.name}` parameter `${param.name}` is `${tok}`, so use `${tok} ${call_arg.expr}` instead', call_arg.expr.pos()) } } else { c.fail_if_unreadable(call_arg.expr, arg_typ, 'argument') } } array_param_typ := if func.generic_names.len > 0 && concrete_types.len > 0 { c.table.convert_generic_param_type(param, func.generic_names, concrete_types) or { param.typ } } else { param.typ } arg_typ = c.lower_fixed_array_call_arg_to_array(mut call_arg, array_param_typ, node.language) node.args[i] = call_arg node.args[i].typ = arg_typ arg_typ_sym = c.table.sym(arg_typ) mut final_param_sym := unsafe { param_typ_sym } mut final_param_typ := param.typ if has_typed_variadic && param_typ_sym.info is ast.Array { final_param_typ = param_typ_sym.info.elem_type final_param_sym = c.table.sym(final_param_typ) } // Note: Casting to voidptr is used as an escape mechanism, so: // 1. allow passing *explicit* voidptr (native or through cast) to functions // expecting voidptr or ...voidptr // ... but 2. disallow passing non-pointers - that is very rarely what the user wanted, // it can lead to codegen errors (except for 'magic' functions like `json.encode` that, // the compiler has special codegen support for), so it should be opt in, that is it // should require an explicit voidptr(x) cast (and probably unsafe{} ?) . // V variadic ...voidptr calls are boxed in cgen, so rvalues are safe there. if call_arg.typ != param.typ && (param.typ == ast.voidptr_type || final_param_sym.idx == ast.voidptr_type_idx || param.typ == ast.nil_type || final_param_sym.idx == ast.nil_type_idx) && !call_arg.typ.is_any_kind_of_pointer() && func.language == .v && !call_arg.expr.is_lvalue() && !c.pref.translated && !c.file.is_translated && !(has_typed_variadic && final_param_sym.idx == ast.voidptr_type_idx) && !func.is_c_variadic && func.name !in ['json.encode', 'json.encode_pretty'] { c.error('expression cannot be passed as `voidptr`', call_arg.expr.pos()) } // Handle expected interface mut is_generic_interface := false if final_param_sym.kind == .generic_inst { gi := final_param_sym.info if gi is ast.GenericInst { is_generic_interface = c.table.type_symbols[gi.parent_idx].kind == .interface } } if final_param_sym.kind == .interface || is_generic_interface { // For generic interface parameters, resolve the generic type to its concrete // instantiation before checking implementation. mut resolved_param_typ := final_param_typ if final_param_typ.has_flag(.generic) && func.generic_names.len > 0 && node.concrete_types.len == func.generic_names.len { if t := c.table.convert_generic_type(final_param_typ, func.generic_names, node.concrete_types) { resolved_param_typ = t } } if c.type_implements_with_mut_receiver(arg_typ, resolved_param_typ, call_arg.expr.pos(), param.is_mut) { if !arg_typ.is_any_kind_of_pointer() && !c.inside_unsafe && arg_typ_sym.kind != .interface { c.mark_as_referenced(mut &call_arg.expr, true) } } // For non-generic interfaces, check pointer compatibility. // For generic_inst interfaces, the param.typ may still have unresolved // generic flags, so skip the ptr check — type_implements already validated. if final_param_sym.kind == .interface && arg_typ !in [ast.voidptr_type, ast.nil_type] && !c.check_multiple_ptr_match(arg_typ, param.typ, param, call_arg) { got_typ_str, expected_typ_str := c.get_string_names_of(arg_typ, param.typ) c.error('cannot use `${got_typ_str}` as `${expected_typ_str}` in argument ${i + 1} to `${fn_name}`', call_arg.pos) } if call_arg.expr is ast.ArrayDecompose && arg_typ.idx() != final_param_typ.idx() && c.table.cur_concrete_types.len == 0 { expected_type_str := c.table.type_to_str(param.typ) got_type_str := c.table.type_to_str(arg_typ) c.error('cannot use `${got_type_str}` as `${expected_type_str}` in argument ${i + 1} to `${fn_name}`', call_arg.pos) } continue } if !c.inside_unsafe && !param.is_mut && node.language == .v && c.is_nocopy_struct(final_param_typ) { c.error('cannot pass @[nocopy] struct by value: use a reference instead', call_arg.pos) } if param.typ.is_ptr() && !param.is_mut && !call_arg.typ.is_any_kind_of_pointer() && call_arg.expr.is_literal() && func.language == .v && !c.pref.translated { c.error('literal argument cannot be passed as reference parameter `${c.table.type_to_str(param.typ)}`', call_arg.pos) } if !variadic_arg_handled { c.check_expected_call_arg(arg_typ, c.unwrap_generic(param.typ), node.language, call_arg) or { if param.typ.has_flag(.generic) { continue } // When decomposing a generic variadic arg into a non-variadic function, // each generic instantiation re-checks all branches (e.g. match arms), // so the decomposed type may not match the target param type for // unreachable branches. Skip the error in this case. if has_decompose && call_arg.expr is ast.ArrayDecompose && c.table.cur_concrete_types.len > 0 { continue } if param_typ_sym.info is ast.Array && arg_typ_sym.info is ast.Array { param_elem_type := c.table.unaliased_type(param_typ_sym.info.elem_type) arg_elem_type := c.table.unaliased_type(arg_typ_sym.info.elem_type) param_nr_muls := param.typ.nr_muls() arg_nr_muls := if call_arg.is_mut { arg_typ.nr_muls() + 1 } else { arg_typ.nr_muls() } if param.typ.has_flag(.option) == arg_typ.has_flag(.option) && param.typ.has_flag(.result) == arg_typ.has_flag(.result) && param_nr_muls == arg_nr_muls && param_typ_sym.info.nr_dims == arg_typ_sym.info.nr_dims && param_elem_type == arg_elem_type { continue } } else if arg_typ_sym.info is ast.MultiReturn { arg_typs := arg_typ_sym.info.types if !(has_typed_variadic && param_i >= variadic_start) { if arg_typ_sym.info.types.len > func.params.len { c.error('trying to pass ${arg_typ_sym.info.types.len} argument(s), but function expects ${func.params.len} argument(s)', node.pos) continue_check = false return ast.void_type } } if !func.is_variadic && func.params.len < (param_i + arg_typ_sym.info.types.len) { c.fn_call_error_have_want( nr_params: func.params.len nr_args: param_i + arg_typ_sym.info.types.len params: func.params args: node.args pos: node.pos ) return ast.void_type } out: for n in 0 .. arg_typ_sym.info.types.len { curr_arg := arg_typs[n] multi_param := call_arg_param_for_fn(func, n + param_i, false) c.check_expected_call_arg(curr_arg, c.unwrap_generic(multi_param.typ), node.language, call_arg) or { c.error('${err.msg()} in argument ${param_i + n + 1} to `${fn_name}` from ${c.table.type_to_str(arg_typ)}', call_arg.pos) continue out } } nr_multi_values += arg_typ_sym.info.types.len - 1 continue } else if param_typ_sym.info is ast.Struct && arg_typ_sym.info is ast.Struct && param_typ_sym.info.is_anon { if c.is_anon_struct_compatible(param_typ_sym.info, arg_typ_sym.info) { continue } } else if c.embeds_expected_call_arg_type(arg_typ, final_param_typ) { continue } if c.pref.translated || c.file.is_translated { // in case of variadic make sure to use array elem type for checks // check_expected_call_arg already does this before checks also. param_type := if param.typ.has_flag(.variadic) { param_typ_sym.array_info().elem_type } else { param.typ } // TODO: duplicated logic in check_types() (check_types.v) // Allow enums to be used as ints and vice versa in translated code if param_type.idx() in ast.integer_type_idxs && arg_typ_sym.kind == .enum { continue } if arg_typ.idx() in ast.integer_type_idxs && param_typ_sym.kind == .enum { continue } if (arg_typ == ast.bool_type && param_type.is_int()) || (arg_typ.is_int() && param_type == ast.bool_type) { continue } // In C unsafe number casts are used all the time (e.g. `char*` where // `int*` is expected etc), so just allow them all. mut param_is_number := c.table.unaliased_type(param_type).is_number() if param_type.is_ptr() { param_is_number = param_type.deref().is_number() } mut typ_is_number := c.table.unaliased_type(arg_typ).is_number() if arg_typ.is_ptr() { typ_is_number = arg_typ.deref().is_number() } if param_is_number && typ_is_number { continue } // Allow voidptrs for everything if param_type == ast.voidptr_type_idx || param_type == ast.nil_type_idx || arg_typ == ast.voidptr_type_idx || arg_typ == ast.nil_type_idx { continue } if param_type.is_any_kind_of_pointer() && arg_typ.is_any_kind_of_pointer() { continue } unaliased_param_sym := c.table.sym(c.table.unaliased_type(param_type)) unaliased_arg_sym := c.table.sym(c.table.unaliased_type(arg_typ)) // Allow `[32]i8` as `&i8` etc arg_is_array_like := unaliased_arg_sym.kind in [.array_fixed, .array] param_is_array_like := unaliased_param_sym.kind in [.array_fixed, .array] if arg_is_array_like && (param_is_number || c.table.unaliased_type(param_type).is_any_kind_of_pointer()) { continue } if param_is_array_like && (typ_is_number || c.table.unaliased_type(arg_typ).is_any_kind_of_pointer()) { continue } // Allow `[N]anyptr` as `[N]anyptr` if unaliased_arg_sym.info is ast.Array && unaliased_param_sym.info is ast.Array { if unaliased_arg_sym.info.elem_type.is_any_kind_of_pointer() && unaliased_param_sym.info.elem_type.is_any_kind_of_pointer() { continue } } else if unaliased_arg_sym.info is ast.ArrayFixed && unaliased_param_sym.info is ast.ArrayFixed { if unaliased_arg_sym.info.elem_type.is_any_kind_of_pointer() && unaliased_param_sym.info.elem_type.is_any_kind_of_pointer() { continue } } // Allow `int` as `&i8` if param_type.is_any_kind_of_pointer() && typ_is_number { continue } // Allow `&i8` as `int` if arg_typ.is_any_kind_of_pointer() && param_is_number { continue } } // if first_sym.name == 'VContext' && f.params[0].name == 'ctx' { // TODO use int comparison for perf //} /* if param_typ_sym.info is ast.Struct && param_typ_sym.name == 'VContext' { c.note('ok', call_arg.pos) continue } */ c.error('${err.msg()} in argument ${i + nr_multi_values + 1} to `${fn_name}`', call_arg.pos) } } if final_param_sym.kind == .struct && arg_typ !in [ast.voidptr_type, ast.nil_type] && !c.check_multiple_ptr_match(arg_typ, param.typ, param, call_arg) { got_typ_str, expected_typ_str := c.get_string_names_of(arg_typ, param.typ) c.error('cannot use `${got_typ_str}` as `${expected_typ_str}` in argument ${i + nr_multi_values + 1} to `${fn_name}`', call_arg.pos) } // Warn about automatic (de)referencing, which will be removed soon. if func.language != .c && !c.inside_unsafe && !(call_arg.is_mut && param.is_mut) { if arg_typ.nr_muls() != param.typ.nr_muls() && param.typ !in [ast.byteptr_type, ast.charptr_type, ast.voidptr_type, ast.nil_type] && arg_typ != ast.voidptr_type && !(!call_arg.is_mut && !param.is_mut) //&& !(!call_arg.is_mut && !param.is_mut) { c.warn('automatic referencing/dereferencing is deprecated and will be removed soon (got: ${arg_typ.nr_muls()} references, expected: ${param.typ.nr_muls()} references)', call_arg.pos) } // A special case of the check to not allow voidptr params like in the recently reported raylib // bug with fn... // fn f(p &Foo) => f(foo) -- do not allow this, force f(&foo) // if !c.is_builtin_mod else if param.typ == ast.voidptr_type && func.language == .v && arg_typ !in [ast.voidptr_type, ast.nil_type] && arg_typ.nr_muls() == 0 && func.name !in ['isnil', 'ptr_str'] && !func.name.starts_with('json.') && arg_typ_sym.kind !in [.float_literal, .int_literal, .charptr, .function] && !c.is_js_backend { c.warn('automatic ${arg_typ_sym.name} referencing/dereferencing into voidptr is deprecated and will be removed soon; use `foo(&x)` instead of `foo(x)`', call_arg.pos) } } } if has_unresolved_generic_param { node.return_type = func.return_type return func.return_type } if is_json_encode { // json.encode param is set voidptr, we should bound the proper type here node.expected_arg_types = [node.args[0].typ] } if func.generic_names.len != node.concrete_types.len { // no type arguments given in call, attempt implicit instantiation c.infer_fn_generic_types(func, mut node) concrete_types = node.concrete_types.map(c.unwrap_generic(it)) need_recheck, _ := c.type_resolver.resolve_fn_generic_args(c.table.cur_fn, func, mut node) if need_recheck { c.need_recheck_generic_fns = true } } if func.generic_names.len > 0 { for i, mut call_arg in node.args { param := call_arg_param_for_fn(func, i, false) if param.typ.has_flag(.generic) { if unwrap_typ := c.table.convert_generic_param_type(param, func.generic_names, concrete_types) { c.expected_type = unwrap_typ } } else { c.expected_type = param.typ } already_checked := node.language != .js && call_arg.expr is ast.CallExpr mut typ := c.check_expr_option_or_result_call(call_arg.expr, if already_checked && mut call_arg.expr is ast.CallExpr { call_arg.expr.return_type } else { c.expr(mut call_arg.expr) }) if param.typ.has_flag(.generic) && func.generic_names.len == node.concrete_types.len { if unwrap_typ := c.table.convert_generic_param_type(param, func.generic_names, concrete_types) { call_arg.typ = typ typ = c.lower_fixed_array_call_arg_to_array(mut call_arg, unwrap_typ, node.language) node.args[i].expr = call_arg.expr node.args[i].typ = typ utyp := c.unwrap_generic(typ) unwrap_sym := c.table.sym(unwrap_typ) if unwrap_sym.kind == .interface { if c.type_implements_with_mut_receiver(utyp, unwrap_typ, call_arg.expr.pos(), param.is_mut) { if !utyp.is_any_kind_of_pointer() && !c.inside_unsafe && c.table.sym(utyp).kind != .interface { c.mark_as_referenced(mut &call_arg.expr, true) } } continue } c.check_expected_call_arg(utyp, unwrap_typ, node.language, call_arg) or { if c.type_resolver.type_map.len > 0 { continue } if mut call_arg.expr is ast.LambdaExpr { // Calling fn is generic and lambda arg also is generic c.handle_generic_lambda_arg(node, func.generic_names, mut call_arg.expr) continue } if mut call_arg.expr is ast.AnonFn { c.handle_generic_anon_fn_arg(node, func.generic_names, mut call_arg.expr) } if c.is_optional_array_arg_compatible(utyp, unwrap_typ) { continue } c.error('${err.msg()} in argument ${i + 1} to `${fn_name}`', call_arg.pos) } // When check succeeds (e.g. after lambda re-check with concrete types), // still set call_ctx on lambda args for generic context in cgen: if mut call_arg.expr is ast.LambdaExpr { c.handle_generic_lambda_arg(node, func.generic_names, mut call_arg.expr) } else if mut call_arg.expr is ast.AnonFn { c.handle_generic_anon_fn_arg(node, func.generic_names, mut call_arg.expr) } } } } if c.pref.skip_unused && node.concrete_types.len > 0 { for concrete_type in node.concrete_types { c.table.used_features.comptime_syms[c.unwrap_generic(concrete_type)] = true } } } raw_io_arg_offset := if func.is_method { 1 } else { 0 } c.check_os_raw_io_call(node, func, concrete_types, raw_io_arg_offset) // resolve return generics struct to concrete type if func.generic_names.len > 0 && func.return_type.has_flag(.generic) && c.table.cur_fn != unsafe { nil } && c.needs_unwrap_generic_type(func.return_type) { node.return_type = c.table.unwrap_generic_type(func.return_type, func.generic_names, concrete_types) } else { node.return_type = func.return_type } if func.return_type.has_flag(.generic) { node.return_type_generic = func.return_type } if node.concrete_types.len > 0 && func.return_type != 0 && c.table.cur_fn != unsafe { nil } && c.table.cur_fn.generic_names.len == 0 { if typ := c.table.convert_generic_type(func.return_type, func.generic_names, concrete_types) { node.return_type = typ c.register_trace_call(node, func) if func.return_type.has_flag(.generic) { c.table.used_features.comptime_syms[typ.clear_option_and_result()] = true c.table.used_features.comptime_syms[func.return_type] = true } return typ } } if node.concrete_types.len > 0 && func.generic_names.len == 0 { c.error('a non generic function called like a generic one', node.concrete_list_pos) } // resolve generic fn return type if func.generic_names.len > 0 && node.return_type != ast.void_type { ret_type := c.resolve_fn_return_type(func, node, concrete_types) c.register_trace_call(node, func) node.return_type = ret_type if ret_type.has_flag(.generic) { unwrapped_ret := c.unwrap_generic(ret_type) if c.table.sym(unwrapped_ret).kind == .multi_return { c.table.used_features.comptime_syms[unwrapped_ret] = true } } return ret_type } c.register_trace_call(node, func) return func.return_type } // register_trace_call registers the wrapper funcs for calling funcs for callstack feature fn (mut c Checker) register_trace_call(node &ast.CallExpr, func &ast.Fn) { if !(c.pref.is_callstack || c.pref.is_trace) || c.table.cur_fn == unsafe { nil } || node.language != .v { return } if node.name in ['v.debug.callstack', 'v.debug.add_after_call', 'v.debug.add_before_call', 'v.debug.remove_after_call', 'v.debug.remove_before_call'] { return } if !c.file.imports.any(it.mod == 'v.debug') { return } hash_fn, fn_name := c.table.get_trace_fn_name(c.table.cur_fn, node) calling_fn := if func.is_method { '${c.table.type_to_str(c.unwrap_generic(node.left_type))}_${fn_name}' } else { fn_name } c.table.cur_fn.trace_fns[hash_fn] = ast.FnTrace{ name: calling_fn file: c.file.path line: node.pos.line_nr + 1 return_type: node.return_type func: &ast.Fn{ ...func } is_fn_var: node.is_fn_var } } // cast_fixed_array_ret casts a ArrayFixed type created to return to a non returning one fn (mut c Checker) cast_fixed_array_ret(typ ast.Type, sym ast.TypeSymbol) ast.Type { if sym.info is ast.ArrayFixed && sym.info.is_fn_ret { return c.table.find_or_register_array_fixed(sym.info.elem_type, sym.info.size, sym.info.size_expr, false) } return typ } // cast_to_fixed_array_ret casts a ArrayFixed type created to do not return to a returning one fn (mut c Checker) cast_to_fixed_array_ret(typ ast.Type, sym ast.TypeSymbol) ast.Type { if sym.info is ast.ArrayFixed && !sym.info.is_fn_ret { return c.table.find_or_register_array_fixed(sym.info.elem_type, sym.info.size, sym.info.size_expr, true) } return typ } // checks if a symbol kind is an expected kind fn (mut c Checker) check_type_sym_kind(name string, type_idx int, expected_kind ast.Kind, pos token.Pos) bool { mut sym := c.table.sym_by_idx(type_idx) if sym.kind == .alias { parent_type := (sym.info as ast.Alias).parent_type sym = c.table.sym(parent_type) } if sym.kind != expected_kind { c.error('expected ${expected_kind}, but `${name}` is ${sym.kind}', pos) return false } return true } // checks if a type from another module is as expected and visible(`is_pub`) fn (mut c Checker) check_type_and_visibility(name string, type_idx int, expected_kind ast.Kind, pos token.Pos) bool { mut sym := c.table.sym_by_idx(type_idx) if sym.kind == .alias { parent_type := (sym.info as ast.Alias).parent_type sym = c.table.sym(parent_type) } if sym.kind != expected_kind { c.error('expected ${expected_kind}, but `${name}` is ${sym.kind}', pos) return false } if !sym.is_pub { c.error('module `${sym.mod}` type `${sym.name}` is private', pos) return false } return true } fn (c &Checker) is_valid_os_file_struct_io_type(typ ast.Type) bool { if typ.nr_muls() > 0 || typ.has_option_or_result() { return false } mut current_typ := typ mut sym := c.table.sym(current_typ) for { if sym.info !is ast.Alias { break } alias_info := sym.info as ast.Alias current_typ = alias_info.parent_type if current_typ.nr_muls() > 0 || current_typ.has_option_or_result() { return false } sym = c.table.sym(current_typ) } return sym.kind == .struct } fn (mut c Checker) check_os_file_struct_io_method_call(node &ast.CallExpr, method ast.Fn, concrete_types []ast.Type) { if method.name !in ['read_struct', 'read_struct_at', 'write_struct', 'write_struct_at'] { return } if method.params.len == 0 || concrete_types.len != 1 { return } receiver_sym := c.table.final_sym(method.params[0].typ) if receiver_sym.name != 'os.File' { return } concrete_type := concrete_types[0] if concrete_type.has_flag(.generic) || c.is_valid_os_file_struct_io_type(concrete_type) { return } err_pos := if node.raw_concrete_types.len > 0 { node.concrete_list_pos } else if node.args.len > 0 { node.args[0].pos } else { node.pos } c.error('`${receiver_sym.name}.${method.name}` expects a struct type, but got `${c.table.type_to_str(concrete_type)}`', err_pos) } // is_optional_array_arg_compatible allows the generic recheck fallback for `[]?T -> []T` // without also accepting `[]&T -> []T` or other pointedness mismatches. fn (mut c Checker) is_optional_array_arg_compatible(got ast.Type, expected ast.Type) bool { if expected.has_flag(.variadic) { return false } if c.table.final_sym(got).kind != .array || c.table.final_sym(expected).kind != .array { return false } got_value_type := c.table.value_type(got) expected_value_type := c.table.value_type(expected) if !got_value_type.has_flag(.option) || expected_value_type.has_flag(.option) { return false } if got_value_type.nr_muls() != expected_value_type.nr_muls() { return false } return c.check_types(got_value_type.clear_flag(.option), expected_value_type) } fn (mut c Checker) fixed_array_arg_as_array_type(got ast.Type, expected ast.Type) ast.Type { if expected.has_flag(.variadic) { return ast.no_type } got_sym := c.table.final_sym(c.unwrap_generic(got).clear_option_and_result()) expected_sym := c.table.final_sym(c.unwrap_generic(expected).clear_option_and_result().set_nr_muls(0)) if got_sym.kind != .array_fixed || expected_sym.kind != .array { return ast.no_type } return ast.new_type(c.table.find_or_register_array(got_sym.array_fixed_info().elem_type)) } fn (mut c Checker) lower_fixed_array_call_arg_to_array(mut arg ast.CallArg, expected ast.Type, language ast.Language) ast.Type { array_typ := c.fixed_array_arg_as_array_type(arg.typ, expected) if array_typ == ast.no_type { return arg.typ } expected_array_typ := c.unwrap_generic(expected).clear_option_and_result().set_nr_muls(0) c.check_expected_call_arg(array_typ, expected_array_typ, language, arg) or { return arg.typ } original_expr := arg.expr arg.expr = ast.IndexExpr{ pos: original_expr.pos() left: original_expr left_type: arg.typ index: ast.RangeExpr{ pos: original_expr.pos() } } arg.typ = c.expr(mut arg.expr) return arg.typ } fn (mut c Checker) method_call(mut node ast.CallExpr, mut continue_check &bool) ast.Type { // `(if true { 'foo.bar' } else { 'foo.bar.baz' }).all_after('foo.')` node.concrete_types = node.raw_concrete_types.clone() mut left_expr := node.left left_expr = left_expr.remove_par() if mut left_expr is ast.IfExpr { if left_expr.branches.len > 0 && left_expr.has_else { mut last_stmt := left_expr.branches[0].stmts.last() if mut last_stmt is ast.ExprStmt { c.expected_type = c.expr(mut last_stmt.expr) } } } left_type := node.left_type if left_type == ast.void_type { // c.error('cannot call a method using an invalid expression', node.pos) continue_check = false return ast.void_type } c.markused_method_call(mut node, mut left_expr, left_type) c.expected_type = left_type mut is_generic := left_type.has_flag(.generic) node.left_type = left_type // Set default values for .return_type & .receiver_type too, // or there will be hard tRo diagnose 0 type panics in cgen. node.return_type = left_type node.receiver_type = left_type if is_generic { c.table.used_features.comptime_syms[c.unwrap_generic(left_type)] = true } if c.table.cur_fn != unsafe { nil } && c.table.cur_fn.generic_names.len > 0 { c.table.unwrap_generic_type(left_type, c.table.cur_fn.generic_names, c.table.cur_concrete_types) } unwrapped_left_type := c.unwrap_generic(left_type) left_sym := c.table.sym(unwrapped_left_type) final_left_sym := c.table.final_sym(unwrapped_left_type) mut final_left_kind := final_left_sym.kind if final_left_sym.kind == .generic_inst && final_left_sym.info is ast.GenericInst { final_left_kind = c.table.sym(ast.new_type(final_left_sym.info.parent_idx)).kind } method_name := node.name if left_type.has_flag(.option) { c.error('Option type `${left_sym.name}` cannot be called directly, you should unwrap it first', node.left.pos()) return ast.void_type } else if left_type.has_flag(.result) { c.error('Result type cannot be called directly', node.left.pos()) return ast.void_type } if left_sym.kind in [.sum_type, .interface] { if node.kind == .type_name { return ast.string_type } if node.kind == .type_idx { return ast.int_type } } if left_type == ast.void_type { // No need to print this error, since this means that the variable is unknown, // and there already was an error before. // c.error('`void` type has no methods', node.left.pos()) continue_check = false return ast.void_type } mut use_builtin_array_sort := false if final_left_sym.kind == .array && node.kind in [.sort, .sorted] && node.args.len > 0 { if method := left_sym.find_method(method_name) { use_builtin_array_sort = method.params.len == 1 } } if final_left_sym.kind == .array && array_builtin_methods_chk.matches(method_name) && (!(left_sym.has_method(method_name)) || use_builtin_array_sort) { return c.array_builtin_method_call(mut node, left_type) } else if final_left_sym.kind == .array_fixed && fixed_array_builtin_methods_chk.matches(method_name) && !(left_sym.kind == .alias && left_sym.has_method(method_name)) { return c.fixed_array_builtin_method_call(mut node, left_type) } else if final_left_sym.kind == .map && node.kind in [.clone, .keys, .values, .move, .delete] && !(left_sym.kind == .alias && left_sym.has_method(method_name)) { unaliased_left_type := c.table.unaliased_type(left_type) return c.map_builtin_method_call(mut node, unaliased_left_type) } else if c.is_js_backend && left_sym.name.starts_with('Promise[') && node.kind == .wait { info := left_sym.info as ast.Struct if node.args.len > 0 { c.error('wait() does not have any arguments', node.args[0].pos) } if c.table.cur_fn != unsafe { nil } { c.table.cur_fn.has_await = true } node.return_type = info.concrete_types[0] node.return_type.set_flag(.option) return node.return_type } else if left_sym.info is ast.Thread && node.kind == .wait { if node.args.len > 0 { c.error('wait() does not have any arguments', node.args[0].pos) } node.return_type = left_sym.info.return_type return left_sym.info.return_type } else if left_sym.kind == .char && left_type.nr_muls() == 0 && node.kind == .str { c.error('calling `.str()` on type `char` is not allowed, use its address or cast it to an integer instead', node.left.pos().extend(node.pos)) return ast.void_type } mut unknown_method_msg := '' mut method := ast.Fn{} mut has_method := false mut is_method_from_embed := false mut structured_receiver_concrete_types := []ast.Type{} mut is_structured_receiver_method := false defer { if has_method && node.is_method { c.check_must_use_call_result(node, method, 'method') } } lookup_sym := c.table.sym(left_type) if structured_method := c.table.find_structured_receiver_method_with_types(left_type, method_name) { if m := lookup_sym.find_method(method_name) { method = m has_method = true } else if m := c.table.find_alias_parent_exact_method(left_type, method_name) { method = m has_method = true } else { method = structured_method.method structured_receiver_concrete_types = structured_method.concrete_types.clone() is_structured_receiver_method = true has_method = true } if lookup_sym.kind == .interface && method.from_embedded_type != 0 { is_method_from_embed = true node.from_embed_types = [method.from_embedded_type] } } else if m := left_sym.find_method_with_generic_parent(method_name) { method = m has_method = true if left_sym.kind == .interface && m.from_embedded_type != 0 { is_method_from_embed = true node.from_embed_types = [m.from_embedded_type] } } else if m := c.table.find_method(left_sym, method_name) { method = m has_method = true if left_sym.kind == .interface && m.from_embedded_type != 0 { is_method_from_embed = true node.from_embed_types = [m.from_embedded_type] } } else { if final_left_sym.kind in [.struct, .sum_type, .interface, .array] { mut parent_type := ast.void_type match final_left_sym.info { ast.Struct, ast.SumType, ast.Interface { parent_type = final_left_sym.info.parent_type } ast.Array { typ := c.table.unaliased_type(final_left_sym.info.elem_type) parent_type = ast.idx_to_type(c.table.find_or_register_array(typ)) } else {} } if parent_type != 0 { type_sym := c.table.sym(parent_type) if m := c.table.find_method(type_sym, method_name) { method = m has_method = true is_generic = true if left_sym.kind == .interface && m.from_embedded_type != 0 { is_method_from_embed = true node.from_embed_types = [m.from_embedded_type] } } } } if !has_method { has_method = true mut embed_types := []ast.Type{} method, embed_types = c.table.find_method_from_embeds(final_left_sym, method_name) or { emsg := err.str() if emsg != '' { c.error(emsg, node.pos) } has_method = false ast.Fn{}, []ast.Type{} } if embed_types.len != 0 { is_method_from_embed = true node.from_embed_types = embed_types c.markused_comptime_call(node.left_type.has_flag(.generic), '${int(method.receiver_type)}.${method.name}') } } if final_left_sym.kind == .aggregate { // the error message contains the problematic type unknown_method_msg = err.msg() if unknown_method_msg == 'unknown method' { unknown_method_msg += ' `' + method_name + '`' } } } if !has_method { // TODO: str methods if node.kind == .str { if left_sym.kind == .interface { iname := left_sym.name c.error('interface `${iname}` does not have a .str() method. Use typeof() instead', node.pos) } if c.fail_if_private_implicit_str(left_type, node.pos, 'call auto-generated `.str()` on') { return ast.string_type } node.receiver_type = left_type.clear_ref() node.return_type = ast.string_type if node.args.len > 0 { c.error('.str() method calls should have no arguments', node.pos) } c.fail_if_unreadable(node.left, left_type, 'receiver') if !c.is_builtin_mod { c.table.used_features.auto_str = true c.markused_auto_str_dependencies(left_type) } return ast.string_type } else if node.kind == .free { if !c.is_builtin_mod && !c.inside_unsafe && !method.is_unsafe { c.warn('manual memory management with `free()` is only allowed in unsafe code', node.pos) } if left_sym.kind == .array_fixed { name := left_sym.symbol_name_except_generic().replace_each(['<', '[', '>', ']']) c.error('unknown method or field: ${name}.free()', node.pos) } return ast.void_type } // call struct field fn type // TODO: can we use SelectorExpr for all? this dosent really belong here if field := c.table.find_field_with_embeds(left_sym, method_name) { mut field_typ := field.typ if left_sym.info is ast.Struct && left_sym.info.generic_types.len > 0 && left_sym.info.generic_types.len == left_sym.info.concrete_types.len { generic_names := c.table.get_generic_names(left_sym.info.generic_types) if resolved_typ := c.table.convert_generic_type(field_typ, generic_names, left_sym.info.concrete_types) { field_typ = resolved_typ } } if field_typ.has_flag(.option) { // unwrapped callback (if f.func != none {}) scope_field := node.scope.find_struct_field(node.left.str(), node.left_type, method_name) if scope_field != unsafe { nil } { field_typ = c.exposed_smartcast_type(scope_field.orig_type, scope_field.smartcasts.last(), scope_field.is_mut) node.is_unwrapped_fn_selector = true } else { c.error('Option function field must be unwrapped first', node.pos) } } field_sym := c.table.sym(c.unwrap_generic(field_typ)) if field_sym.kind == .function { node.is_method = false node.is_field = true info := field_sym.info as ast.FnType c.check_expected_arg_count(mut node, info.func) or { node.return_type = info.func.return_type if info.func.return_type.has_flag(.generic) { node.return_type_generic = info.func.return_type } return info.func.return_type } match left_sym.info { ast.Struct, ast.Interface, ast.SumType { if left_sym.info.parent_type != 0 { parent_sym := c.table.sym(left_sym.info.parent_type) if generic_field := c.table.find_field_with_embeds(parent_sym, method_name) { generic_field_sym := c.table.sym(generic_field.typ) if generic_field_sym.info is ast.FnType { generic_ret := generic_field_sym.info.func.return_type if generic_ret.has_flag(.generic) { node.return_type_generic = generic_ret } } } } } else {} } node.return_type = info.func.return_type if info.func.return_type.has_flag(.generic) { node.return_type_generic = info.func.return_type } mut earg_types := []ast.Type{} for i, mut arg in node.args { targ := c.check_expr_option_or_result_call(arg.expr, c.expr(mut arg.expr)) arg.typ = targ param := call_arg_param_for_fn(info.func, i, false) // registers if the arg must be passed by ref to disable auto deref args arg.should_be_ptr = param.typ.is_ptr() && !param.is_mut if c.table.sym(param.typ).kind == .interface { // cannot hide interface expected type to make possible to pass its interface type automatically earg_types << if targ.idx() != param.typ.idx() { param.typ } else { targ } } else { earg_types << param.typ } param_share := param.typ.share() if param_share == .shared_t && (c.locked_names.len > 0 || c.rlocked_names.len > 0) { c.error('method with `shared` arguments cannot be called inside `lock`/`rlock` block', arg.pos) } arg = c.implicit_mut_call_arg(param, arg) node.args[i] = arg if arg.is_mut { to_lock, pos := c.fail_if_immutable(mut arg.expr) if !param.is_mut { tok := arg.share.str() c.error('`${node.name}` parameter ${i + 1} is not `${tok}`, `${tok}` is not needed`', arg.expr.pos()) } else { if param_share != arg.share { c.error('wrong shared type `${arg.share.str()}`, expected: `${param_share.str()}`', arg.expr.pos()) } if to_lock != '' && param_share != .shared_t { c.error('${to_lock} is `shared` and must be `lock`ed to be passed as `mut`', pos) } } } else { if param.is_mut { tok := param.specifier() c.error('method `${node.name}` parameter ${i + 1} is `${tok}`, so use `${tok} ${arg.expr}` instead', arg.expr.pos()) } else { c.fail_if_unreadable(arg.expr, targ, 'argument') } } if i < info.func.params.len { exp_arg_typ := info.func.params[i].typ c.check_expected_call_arg(targ, c.unwrap_generic(exp_arg_typ), node.language, arg) or { if targ != ast.void_type { c.error('${err.msg()} in argument ${i + 1} to `${left_sym.name}.${method_name}`', arg.pos) } } } } node.expected_arg_types = earg_types node.is_method = true _, node.from_embed_types = c.table.find_field_from_embeds(left_sym, method_name) or { return info.func.return_type } return info.func.return_type } } if left_sym.kind in [.struct, .aggregate, .interface, .sum_type] { if c.smartcast_mut_pos != token.Pos{} && !c.implicit_mutability_enabled() { c.note('smartcasting requires either an immutable value, or an explicit mut keyword before the value', c.smartcast_mut_pos) } if c.smartcast_cond_pos != token.Pos{} { c.note('smartcast can only be used on ident, selector or index expressions, e.g. match foo, match foo.bar, match foo[0]', c.smartcast_cond_pos) } } if left_type != ast.void_type { suggestion := util.new_suggestion(method_name, left_sym.methods.map(it.name)) if unknown_method_msg == '' { if c.table.structured_receiver_method_rejects_voidptr(left_type, method_name) { unknown_method_msg = 'method `${left_sym.name}.${method_name}` cannot bind `voidptr` to a generic receiver pattern; cast the receiver to a concrete V type first' } else if field := c.table.find_field(left_sym, method_name) { unknown_method_msg = 'unknown method `${field.name}` did you mean to access the field with the same name instead?' } else { mut sname := left_sym.symbol_name_except_generic() match left_sym.info { ast.Struct, ast.Interface, ast.SumType { if left_sym.info.concrete_types.len > 0 && left_sym.info.parent_type.has_flag(.generic) { sname = c.table.sym(left_sym.info.parent_type).symbol_name_except_generic() } } else {} } if left_sym.generic_types.len > 0 { generic_names := left_sym.generic_types.map(c.table.sym(it).name).join(', ') sname = '${left_sym.ngname}<${generic_names}>' } name := sname.replace_each(['<', '[', '>', ']']) unknown_method_msg = 'unknown method or field: `${name}.${method_name}`' } } c.error(suggestion.say(unknown_method_msg), node.pos) } return ast.void_type } c.mark_fn_decl_as_referenced(method.fkey()) // x is Bar[T], x.foo() -> x.foo[T]() rec_sym := if is_method_from_embed { c.table.final_sym(node.from_embed_types.last()) } else { c.table.final_sym(node.left_type) } rec_is_generic := if is_method_from_embed { node.from_embed_types.last().has_flag(.generic) } else { left_type.has_flag(.generic) } mut rec_concrete_types := []ast.Type{} mut method_generic_names_len := method.generic_names.len mut receiver_generics_in_selected_method := false if !is_structured_receiver_method { match rec_sym.info { ast.Struct, ast.SumType, ast.Interface { receiver_generic_names := rec_sym.info.generic_types.map(c.table.sym(it).name) receiver_generics_in_method := receiver_generic_names.len > 0 && method.generic_names.len >= receiver_generic_names.len && method.generic_names[..receiver_generic_names.len] == receiver_generic_names receiver_generics_in_selected_method = receiver_generics_in_method if rec_sym.info.concrete_types.len > 0 { rec_concrete_types = rec_sym.info.concrete_types.clone() } concrete_types_len := node.concrete_types.len if rec_is_generic && concrete_types_len == 0 && receiver_generics_in_method { node.concrete_types = rec_sym.info.generic_types } else if rec_is_generic && concrete_types_len > 0 && receiver_generics_in_method && method_generic_names_len > concrete_types_len && rec_sym.info.generic_types.len + concrete_types_len == method_generic_names_len { t_concrete_types := node.concrete_types.clone() node.concrete_types = rec_sym.info.generic_types node.concrete_types << t_concrete_types } else if !rec_is_generic && rec_sym.info.concrete_types.len > 0 && receiver_generics_in_method && concrete_types_len > 0 && rec_sym.info.concrete_types.len + concrete_types_len == method_generic_names_len { t_concrete_types := node.concrete_types.clone() node.concrete_types = rec_sym.info.concrete_types node.concrete_types << t_concrete_types } else if !rec_is_generic && receiver_generics_in_method && rec_concrete_types.len > 0 && concrete_types_len == 0 { node.concrete_types = rec_concrete_types } } ast.GenericInst { // Concrete generic instance (e.g. Vec2[f64]): resolve from parent struct parent_sym := c.table.sym(ast.new_type(rec_sym.info.parent_idx)) match parent_sym.info { ast.Struct, ast.SumType, ast.Interface { receiver_generic_names := parent_sym.info.generic_types.map(c.table.sym(it).name) receiver_generics_in_method := receiver_generic_names.len > 0 && method.generic_names.len >= receiver_generic_names.len && method.generic_names[..receiver_generic_names.len] == receiver_generic_names receiver_generics_in_selected_method = receiver_generics_in_method rec_concrete_types = rec_sym.info.concrete_types.clone() concrete_types_len := node.concrete_types.len if !rec_is_generic && receiver_generics_in_method && rec_concrete_types.len > 0 && concrete_types_len == 0 { node.concrete_types = rec_concrete_types } else if !rec_is_generic && receiver_generics_in_method && rec_concrete_types.len > 0 && concrete_types_len > 0 && rec_concrete_types.len + concrete_types_len == method_generic_names_len { t_concrete_types := node.concrete_types.clone() node.concrete_types = rec_concrete_types node.concrete_types << t_concrete_types } } else {} } } ast.FnType { if rec_sym.parent_idx > 0 { parent_sym := c.table.sym(ast.idx_to_type(rec_sym.parent_idx)) if parent_sym.info is ast.FnType { rec_concrete_types = rec_sym.generic_types.clone() if rec_concrete_types.len > 0 && method_generic_names_len == rec_concrete_types.len { node.concrete_types = rec_concrete_types } } } } else {} } } if structured_receiver_concrete_types.len > 0 { rec_concrete_types = structured_receiver_concrete_types.clone() receiver_generics_in_selected_method = true concrete_types_len := node.concrete_types.len if structured_receiver_concrete_types.len == method_generic_names_len { node.concrete_types = structured_receiver_concrete_types.clone() } else if structured_receiver_concrete_types.len < method_generic_names_len { if concrete_types_len == 0 { node.concrete_types = structured_receiver_concrete_types.clone() } else if structured_receiver_concrete_types.len + concrete_types_len == method_generic_names_len { t_concrete_types := node.concrete_types.clone() node.concrete_types = structured_receiver_concrete_types.clone() node.concrete_types << t_concrete_types } } } mut concrete_types := node.concrete_types.map(c.unwrap_generic(it)) if method_generic_names_len > 0 && concrete_types.len == method_generic_names_len && concrete_types.all(!it.has_flag(.generic)) && c.table.register_fn_concrete_types(method.fkey(), concrete_types) { c.need_recheck_generic_fns = true } node.is_noreturn = method.is_noreturn node.is_expand_simple_interpolation = method.is_expand_simple_interpolation node.is_ctor_new = method.is_ctor_new node.return_type = method.return_type if method.return_type.has_flag(.generic) { node.return_type_generic = method.return_type } is_used_outside_receiver_module := left_sym.mod != c.mod && method.mod != c.mod if !method.is_pub && is_used_outside_receiver_module { // If a private method is called outside of the module // its receiver type is defined in, show an error. // println('warn ${method_name} lef.mod=${left_type_sym.mod} c.mod=${c.mod}') c.error('method `${left_sym.name}.${method_name}` is private', node.pos) } rec_share := method.params[0].typ.share() if rec_share == .shared_t && (c.locked_names.len > 0 || c.rlocked_names.len > 0) { c.error('method with `shared` receiver cannot be called inside `lock`/`rlock` block', node.pos) } requires_mut_receiver := method.params[0].is_mut && (!is_used_outside_receiver_module || c.fn_has_visible_mutation_for_param(method, 0)) if requires_mut_receiver { to_lock, pos := c.check_for_mut_receiver(mut node.left) // node.is_mut = true if to_lock != '' && rec_share != .shared_t { c.error('${to_lock} is `shared` and must be `lock`ed to be passed as `mut`', pos) } } else { c.fail_if_unreadable(node.left, left_type, 'receiver') } if left_sym.language != .js && (!left_sym.is_builtin() && method.mod != 'builtin') && method.language == .v && final_left_kind != .interface && method.no_body { c.error('cannot call a method that does not have a body', node.pos) } if node.raw_concrete_types.len > 0 && method_generic_names_len > 0 && node.concrete_types.len != method_generic_names_len { plural := if method_generic_names_len == 1 { '' } else { 's' } c.error('expected ${method_generic_names_len} generic parameter${plural}, got ${node.concrete_types.len}', node.concrete_list_pos) } for concrete_type in node.concrete_types { c.ensure_type_exists(concrete_type, node.concrete_list_pos) } if method.return_type == ast.void_type && method.is_conditional && method.ctdefine_idx != ast.invalid_type_idx { node.should_be_skipped = c.evaluate_once_comptime_if_attribute(mut method.attrs[method.ctdefine_idx]) } c.check_expected_arg_count(mut node, method) or { node.return_type = method.return_type return method.return_type } mut exp_arg_typ := ast.no_type // type of 1st arg for special builtin methods mut has_unresolved_generic_param := false mut param_is_mut := false mut no_type_promotion := false if left_sym.info is ast.Chan { if node.kind == .try_push { exp_arg_typ = left_sym.info.elem_type.ref() } else if node.kind == .try_pop { exp_arg_typ = left_sym.info.elem_type param_is_mut = true no_type_promotion = true } } for i, mut arg in node.args { mut exp_arg_param := call_arg_param_for_fn(method, i, true) variadic_start := variadic_call_arg_start_idx(method, true) has_typed_variadic := method.is_variadic && !method.is_c_variadic if i > 0 || exp_arg_typ == ast.no_type { exp_arg_typ = exp_arg_param.typ if !c.inside_recheck { arg.ct_expr = c.comptime.is_comptime(arg.expr) } // If initialize a generic struct with short syntax, // need to get the parameter information from the original generic method if is_method_from_embed && arg.expr is ast.StructInit { expr := arg.expr as ast.StructInit is_short_syntax := expr.is_short_syntax && expr.typ == ast.void_type if is_short_syntax { embed_type := node.from_embed_types.last() embed_sym := c.table.sym(embed_type) if embed_sym.info is ast.Struct { info := embed_sym.info as ast.Struct if info.concrete_types.len > 0 { parent_type := info.parent_type parent_sym := c.table.sym(parent_type) if parent_sym.info is ast.Struct && parent_sym.info.is_generic { if f := parent_sym.find_method(method_name) { exp_arg_param = call_arg_param_for_fn(f, i, true) exp_arg_typ = exp_arg_param.typ } } } } } } param_is_mut = false no_type_promotion = false } resolved_method_concrete_types := if receiver_generics_in_selected_method && method_generic_names_len == rec_concrete_types.len { rec_concrete_types } else { concrete_types } mut resolved_method_generic_names := method.generic_names.clone() if resolved_method_generic_names.len == 0 { match rec_sym.info { ast.Struct, ast.Interface, ast.SumType { if rec_sym.info.generic_types.len == resolved_method_concrete_types.len { resolved_method_generic_names = rec_sym.info.generic_types.map(c.table.sym(it).name) } } else {} } } exp_arg_typ = if exp_arg_typ == exp_arg_param.typ { c.resolve_call_arg_param_type(arg, exp_arg_param, resolved_method_generic_names, resolved_method_concrete_types) } else { c.resolve_short_syntax_call_arg_type(arg, exp_arg_typ, resolved_method_generic_names, resolved_method_concrete_types) } mut variadic_arg_handled := false if has_typed_variadic && i >= variadic_start { variadic_sym := c.table.sym(exp_arg_typ) if variadic_sym.info is ast.Array { exp_arg_typ = variadic_sym.info.elem_type } } exp_arg_sym := c.table.sym(exp_arg_typ) c.expected_type = exp_arg_typ mut got_arg_typ := c.check_expr_option_or_result_call(arg.expr, c.expr(mut arg.expr)) node.args[i].typ = got_arg_typ arg.typ = got_arg_typ if no_type_promotion { if got_arg_typ != exp_arg_typ { c.error('cannot use `${c.table.sym(got_arg_typ).name}` as argument for `${method.name}` (`${exp_arg_sym.name}` expected)', arg.pos) } } if has_typed_variadic && got_arg_typ.has_flag(.variadic) && node.args.len - 1 > i { c.error('when forwarding a variadic variable, it must be the final argument', arg.pos) } mut final_arg_sym := unsafe { exp_arg_sym } mut final_arg_typ := exp_arg_typ if has_typed_variadic && exp_arg_sym.info is ast.Array { final_arg_typ = exp_arg_sym.info.elem_type final_arg_sym = c.table.sym(final_arg_typ) } param := call_arg_param_for_fn(method, i, true) if method.is_variadic && arg.expr is ast.ArrayDecompose { if i > variadic_start { c.error('too many arguments in call to `${method.name}`', node.pos) } } if has_typed_variadic && i >= variadic_start { param_sym := c.table.sym(param.typ) mut expected_type := param.typ if param_sym.kind == .array { info := param_sym.array_info() expected_type = info.elem_type c.expected_type = expected_type } typ := c.expr(mut arg.expr) if i == node.args.len - 1 { variadic_arg_handled = c.check_variadic_arg(arg.expr, typ, expected_type, param.typ, i + 1, method.name, true, method.is_variadic, node.args.len == 1 && i == 0, method.generic_names.len > 0, node.pos, arg.pos) } } else { c.expected_type = param.typ } param_is_mut = param_is_mut || param.is_mut param_share := param.typ.share() if param_share == .shared_t && (c.locked_names.len > 0 || c.rlocked_names.len > 0) { c.error('method with `shared` arguments cannot be called inside `lock`/`rlock` block', arg.pos) } arg = c.implicit_mut_call_arg(param, arg) node.args[i] = arg if arg.is_mut { to_lock, pos := c.fail_if_immutable(mut arg.expr) if !param_is_mut { tok := arg.share.str() c.error('`${node.name}` parameter `${param.name}` is not `${tok}`, `${tok}` is not needed`', arg.expr.pos()) } else { if param_share != arg.share { c.error('wrong shared type `${arg.share.str()}`, expected: `${param_share.str()}`', arg.expr.pos()) } if to_lock != '' && param_share != .shared_t { c.error('${to_lock} is `shared` and must be `lock`ed to be passed as `mut`', pos) } } } else { if param_is_mut { tok := if param.typ.has_flag(.shared_f) { 'shared' } else { arg.share.str() } c.error('method `${node.name}` parameter `${param.name}` is `${tok}`, so use `${tok} ${arg.expr}` instead', arg.expr.pos()) } else { c.fail_if_unreadable(arg.expr, got_arg_typ, 'argument') } } if concrete_types.len > 0 && method_generic_names_len != rec_concrete_types.len { need_recheck, mut new_concrete_types := c.type_resolver.resolve_fn_generic_args(c.table.cur_fn, method, mut node) concrete_types = new_concrete_types.clone() if need_recheck { c.need_recheck_generic_fns = true } if concrete_types.len == method_generic_names_len && concrete_types.all(!it.has_flag(.generic)) { c.table.register_fn_concrete_types(method.fkey(), concrete_types) } } method_concrete_types := if receiver_generics_in_selected_method && method_generic_names_len == rec_concrete_types.len { rec_concrete_types } else { concrete_types } if exp_arg_typ.has_flag(.generic) { has_unresolved_generic_param = c.check_unresolved_generic_param(node, arg) || has_unresolved_generic_param if exp_utyp := c.table.convert_generic_param_type(param, method.generic_names, method_concrete_types) { exp_arg_typ = exp_utyp } else { continue } if got_arg_typ.has_flag(.generic) { if c.table.cur_fn != unsafe { nil } && c.table.cur_concrete_types.len > 0 { got_arg_typ = c.unwrap_generic(got_arg_typ) } else { if got_utyp := c.table.convert_generic_type(got_arg_typ, method.generic_names, method_concrete_types) { got_arg_typ = got_utyp } else { continue } } } } got_arg_typ = c.lower_fixed_array_call_arg_to_array(mut arg, exp_arg_typ, node.language) node.args[i] = arg node.args[i].typ = got_arg_typ // Handle expected interface if final_arg_sym.kind == .interface { if c.type_implements_with_mut_receiver(got_arg_typ, final_arg_typ, arg.expr.pos(), param.is_mut) { if !got_arg_typ.is_any_kind_of_pointer() && !c.inside_unsafe { got_arg_typ_sym := c.table.sym(got_arg_typ) if got_arg_typ_sym.kind != .interface { c.mark_as_referenced(mut &arg.expr, true) } } } if got_arg_typ !in [ast.voidptr_type, ast.nil_type] && !c.check_multiple_ptr_match(got_arg_typ, param.typ, param, arg) { got_typ_str, expected_typ_str := c.get_string_names_of(got_arg_typ, param.typ) c.error('cannot use `${got_typ_str}` as `${expected_typ_str}` in argument ${i + 1} to `${method_name}`', arg.pos) } continue } if final_arg_sym.kind == .none && param.typ.has_flag(.generic) && !param.typ.has_flag(.option) { c.error('cannot use `none` as generic argument', arg.pos) } if param.typ.is_ptr() && !arg.typ.is_any_kind_of_pointer() && arg.expr.is_literal() && !c.pref.translated { c.error('literal argument cannot be passed as reference parameter `${c.table.type_to_str(param.typ)}`', arg.pos) } if !variadic_arg_handled { c.check_expected_call_arg(c.unwrap_generic(got_arg_typ), exp_arg_typ, node.language, arg) or { // str method, allow type with str method if fn arg is string // Passing an int or a string array produces a c error here // Deleting this condition results in proper V error messages // if arg_typ_sym.kind == .string && typ_sym.has_method('str') { // continue // } param_typ_sym := c.table.sym(exp_arg_typ) arg_typ_sym := c.table.sym(got_arg_typ) if param_typ_sym.info is ast.Array && arg_typ_sym.info is ast.Array { param_elem_type := c.table.unaliased_type(param_typ_sym.info.elem_type) arg_elem_type := c.table.unaliased_type(arg_typ_sym.info.elem_type) if exp_arg_typ.has_flag(.option) == got_arg_typ.has_flag(.option) && exp_arg_typ.has_flag(.result) == got_arg_typ.has_flag(.result) && exp_arg_typ.nr_muls() == got_arg_typ.nr_muls() && param_typ_sym.info.nr_dims == arg_typ_sym.info.nr_dims && param_elem_type == arg_elem_type { continue } } if c.is_optional_array_arg_compatible(got_arg_typ, exp_arg_typ) { continue } receiver_name := if method.receiver_type.has_flag(.generic) && method_concrete_types.len > 0 { c.table.type_to_str(c.table.unwrap_generic_type(method.receiver_type.set_nr_muls(0), method.generic_names, method_concrete_types)) } else { left_sym.name } c.error('${err.msg()} in argument ${i + 1} to `${receiver_name}.${method_name}`', arg.pos) } } if mut arg.expr is ast.LambdaExpr { // Calling fn is generic and lambda arg also is generic c.handle_generic_lambda_arg(node, method.generic_names, mut arg.expr) } else if mut arg.expr is ast.AnonFn { c.handle_generic_anon_fn_arg(node, method.generic_names, mut arg.expr) } param_typ_sym := c.table.sym(exp_arg_typ) if param_typ_sym.kind == .struct && got_arg_typ !in [ast.voidptr_type, ast.nil_type] && !c.check_multiple_ptr_match(got_arg_typ, param.typ, param, arg) { got_typ_str, expected_typ_str := c.get_string_names_of(got_arg_typ, param.typ) c.error('cannot use `${got_typ_str}` as `${expected_typ_str}` in argument ${i + 1} to `${method_name}`', arg.pos) } } if has_unresolved_generic_param { return method.return_type } if method.is_unsafe && !c.inside_unsafe { if !c.pref.translated && !c.file.is_translated { c.warn('method `${left_sym.name}.${method_name}` must be called from an `unsafe` block', node.pos) } } if c.table.cur_fn != unsafe { nil } && !c.table.cur_fn.is_deprecated && method.is_deprecated { c.deprecate('method', '${left_sym.name}.${method.name}', method.attrs, node.pos) } c.set_node_expected_arg_types(mut node, method) if is_method_from_embed { node.receiver_type = node.from_embed_types.last().derive(method.params[0].typ) } else if is_generic { // if receiver is generic, then cgen requires `receiver_type` to be T. // and the concrete type is stored in `receiver_concrete_type` below. node.receiver_type = method.params[0].typ } else { node.receiver_type = method.params[0].typ } if node.receiver_type.has_flag(.shared_f) && !node.left_type.has_flag(.shared_f) { c.error('cannot use shared method `${node.name}` as `${node.left}` is not a shared var', node.left.pos()) } // if receiver_type is T, then `receiver_concrete_type` is concrete type, otherwise it is the same as `receiver_type`. node.receiver_concrete_type = if is_method_from_embed { node.from_embed_types.last().derive(method.params[0].typ) } else if is_structured_receiver_method { left_type.derive(method.params[0].typ).clear_flag(.generic) } else if is_generic { left_type.derive(method.params[0].typ).clear_flag(.generic) } else { method.params[0].typ } if left_sym.kind == .interface && is_method_from_embed && method.return_type.has_flag(.generic) && method_generic_names_len == 0 { method.generic_names = c.table.get_generic_names((rec_sym.info as ast.Interface).generic_types) method_generic_names_len = method.generic_names.len } if method_generic_names_len != node.concrete_types.len { // no type arguments given in call, attempt implicit instantiation c.infer_fn_generic_types(method, mut node) concrete_types = node.concrete_types.map(c.unwrap_generic(it)) } if method_generic_names_len > 0 && node.concrete_types.len > 0 { variadic_start := variadic_call_arg_start_idx(method, true) has_typed_variadic := method.is_variadic && !method.is_c_variadic for i, mut arg in node.args { param := call_arg_param_for_fn(method, i, true) if method_generic_names_len == node.concrete_types.len && param.typ.has_flag(.generic) { if unwrap_typ := c.table.convert_generic_param_type(param, method.generic_names, concrete_types) { mut expected_fn_typ := unwrap_typ if has_typed_variadic && i >= variadic_start { unwrap_sym := c.table.sym(unwrap_typ) if unwrap_sym.info is ast.Array { expected_fn_typ = unwrap_sym.info.elem_type } } if c.table.final_sym(expected_fn_typ).kind == .function && arg.expr !is ast.LambdaExpr && arg.expr !is ast.AnonFn { if mut arg.expr is ast.Ident { if arg.expr.concrete_types.any(it.has_flag(.generic) || (it != 0 && c.table.sym(it).kind == .placeholder)) { arg.expr.concrete_types = []ast.Type{} } } old_expected_type := c.expected_type c.expected_type = expected_fn_typ arg_typ := c.check_expr_option_or_result_call(arg.expr, c.expr(mut arg.expr)) c.expected_type = old_expected_type arg.typ = arg_typ node.args[i].typ = arg_typ node.args[i].expr = arg.expr } } } if mut arg.expr is ast.LambdaExpr { c.handle_generic_lambda_arg(node, method.generic_names, mut arg.expr) } else if mut arg.expr is ast.AnonFn { c.handle_generic_anon_fn_arg(node, method.generic_names, mut arg.expr) } node.args[i] = arg } } if concrete_types.len == method_generic_names_len && concrete_types.all(!it.has_flag(.generic)) { c.table.register_fn_concrete_types(method.fkey(), concrete_types) need_recheck, _ := c.type_resolver.resolve_fn_generic_args(c.table.cur_fn, method, mut node) if need_recheck { c.need_recheck_generic_fns = true } } c.check_os_file_struct_io_method_call(node, method, concrete_types) if node.concrete_types.len > 0 && method_generic_names_len == 0 { c.error('a non generic function called like a generic one', node.concrete_list_pos) } c.check_os_raw_io_call(node, method, concrete_types, 0) // resolve return generics struct to concrete type if method_generic_names_len > 0 && method.return_type.has_flag(.generic) && c.table.cur_fn != unsafe { nil } && c.table.cur_fn.generic_names.len == 0 { node.return_type = c.table.unwrap_generic_type(method.return_type, method.generic_names, concrete_types) } else { node.return_type = method.return_type } // resolve generic fn return type if method_generic_names_len > 0 && method.return_type.has_flag(.generic) { ret_type := c.resolve_fn_return_type(method, node, concrete_types) c.register_trace_call(node, method) node.return_type = ret_type return ret_type } if method_generic_names_len > 0 && receiver_generics_in_selected_method { if !left_type.has_flag(.generic) { if left_sym.info is ast.Struct { if method_generic_names_len == left_sym.info.concrete_types.len { node.concrete_types = left_sym.info.concrete_types } } } } c.register_trace_call(node, method) return node.return_type } fn (c &Checker) generic_lambda_concrete_types(lambda &ast.LambdaExpr, caller_generic_names []string, caller_concrete_types []ast.Type) []ast.Type { if lambda == unsafe { nil } || lambda.func == unsafe { nil } { return []ast.Type{} } return c.generic_callback_concrete_types(lambda.func.decl.generic_names, caller_generic_names, caller_concrete_types) } fn (c &Checker) generic_callback_concrete_types(generic_names []string, caller_generic_names []string, caller_concrete_types []ast.Type) []ast.Type { if generic_names.len == 0 { return []ast.Type{} } if caller_concrete_types.len == generic_names.len && (caller_generic_names.len == 0 || caller_generic_names.len == caller_concrete_types.len) { return caller_concrete_types.clone() } if caller_generic_names.len == 0 || caller_generic_names.len != caller_concrete_types.len { return []ast.Type{} } mut concrete_types := []ast.Type{cap: generic_names.len} for generic_name in generic_names { idx := caller_generic_names.index(generic_name) if idx < 0 || idx >= caller_concrete_types.len { return []ast.Type{} } concrete_types << caller_concrete_types[idx] } return concrete_types } fn (mut c Checker) handle_generic_lambda_arg(node &ast.CallExpr, generic_names []string, mut lambda ast.LambdaExpr) { // Calling fn is generic and lambda arg also is generic if node.concrete_types.len > 0 && lambda.func != unsafe { nil } && lambda.func.decl.generic_names.len > 0 { if generic_names.len == node.concrete_types.len { unsafe { mut p := &[]string(&lambda.func.decl.generic_names) *p = generic_names.clone() } } lambda.call_ctx = unsafe { node } lambda_concrete_types := c.generic_lambda_concrete_types(lambda, generic_names, node.concrete_types) if lambda_concrete_types.len == 0 { return } if c.table.register_fn_concrete_types(lambda.func.decl.fkey(), lambda_concrete_types) { lambda.func.decl.ninstances++ } } } fn (mut c Checker) handle_generic_anon_fn_arg(node &ast.CallExpr, generic_names []string, mut anon ast.AnonFn) { if node.concrete_types.len == 0 || anon.decl.generic_names.len == 0 { return } anon_concrete_types := c.generic_callback_concrete_types(anon.decl.generic_names, generic_names, node.concrete_types) if anon_concrete_types.len == 0 { return } if anon.decl.fkey() !in c.table.fn_generic_types { c.table.register_fn_generic_types(anon.decl.fkey()) } if c.table.register_fn_concrete_types(anon.decl.fkey(), anon_concrete_types) { anon.decl.ninstances++ } } fn (mut c Checker) resolve_call_arg_param_type(arg ast.CallArg, param ast.Param, generic_names []string, concrete_types []ast.Type) ast.Type { if !param.typ.has_flag(.generic) || generic_names.len == 0 || generic_names.len != concrete_types.len { return param.typ } if arg.expr is ast.StructInit { expr := arg.expr as ast.StructInit if expr.is_short_syntax && expr.typ == ast.void_type { return c.table.convert_generic_param_type(param, generic_names, concrete_types) or { param.typ } } } param_sym := c.table.final_sym(param.typ) if param_sym.kind == .function { if typ := c.table.convert_generic_param_type(param, generic_names, concrete_types) { return typ } } return param.typ } fn (mut c Checker) resolve_short_syntax_call_arg_type(arg ast.CallArg, param_typ ast.Type, generic_names []string, concrete_types []ast.Type) ast.Type { if !param_typ.has_flag(.generic) || generic_names.len == 0 || generic_names.len != concrete_types.len { return param_typ } if arg.expr is ast.StructInit { expr := arg.expr as ast.StructInit if expr.is_short_syntax && expr.typ == ast.void_type { return c.table.convert_generic_type(param_typ, generic_names, concrete_types) or { param_typ } } } return param_typ } fn (mut c Checker) check_unresolved_generic_param(node &ast.CallExpr, arg ast.CallArg) bool { if node.raw_concrete_types.len == 0 && arg.expr is ast.ArrayInit && arg.expr.typ == ast.void_type { c.error('cannot use empty array as generic argument', arg.pos) return true } return false } fn (mut c Checker) spawn_expr(mut node ast.SpawnExpr) ast.Type { ret_type := c.call_expr(mut node.call_expr) if node.call_expr.or_block.kind != .absent { c.error('option handling cannot be done in `spawn` call. Do it when calling `.wait()`', node.call_expr.or_block.pos) } c.mark_spawn_expr_ref_args(node.call_expr) // Make sure there are no mutable arguments for arg in node.call_expr.args { if arg.is_mut && !arg.typ.is_ptr() { if arg.typ == 0 { c.error('invalid expr', node.pos) return 0 } if c.table.final_sym(arg.typ).kind == .array { // allow mutable []array to be passed as mut continue } c.error('function in `spawn` statement cannot contain mutable non-reference arguments', arg.expr.pos()) } } if node.call_expr.is_method && node.call_expr.receiver_type.is_ptr() && !node.call_expr.left_type.is_ptr() { c.error('method in `spawn` statement cannot have non-reference mutable receiver', node.call_expr.left.pos()) } if c.is_js_backend { return c.table.find_or_register_promise(c.unwrap_generic(ret_type)) } else { return c.table.find_or_register_thread(c.unwrap_generic(ret_type)) } } fn (mut c Checker) mark_spawn_expr_ref_args(call ast.CallExpr) { if call.is_method && call.receiver_type.is_ptr() { c.mark_spawn_expr_ref_arg(call.left) } for arg in call.args { c.mark_spawn_expr_ref_arg(arg.expr) } } fn (mut c Checker) mark_spawn_expr_ref_arg(expr ast.Expr) { mut clean_expr := expr clean_expr = clean_expr.remove_par() if mut clean_expr is ast.PrefixExpr { if clean_expr.op == .amp { mut referenced_expr := clean_expr.right c.mark_as_referenced(mut &referenced_expr, false) } } } fn (mut c Checker) thread_array_wait_return_type(thread_ret_type ast.Type) ast.Type { payload_type := thread_ret_type.clear_option_and_result() if payload_type == ast.void_type { return thread_ret_type } mut return_type := ast.idx_to_type(c.table.find_or_register_array(payload_type)) if thread_ret_type.has_flag(.option) { return_type = return_type.set_flag(.option) } if thread_ret_type.has_flag(.result) { return_type = return_type.set_flag(.result) } return return_type } fn (mut c Checker) go_expr(mut node ast.GoExpr) ast.Type { // TODO: copypasta from spawn_expr ret_type := c.call_expr(mut node.call_expr) if node.call_expr.or_block.kind != .absent { c.error('option handling cannot be done in `go` call. Do it when calling `.wait()`', node.call_expr.or_block.pos) } // Make sure there are no mutable arguments for arg in node.call_expr.args { if arg.is_mut && !arg.typ.is_ptr() { c.error('function in `go` statement cannot contain mutable non-reference arguments', arg.expr.pos()) } } if node.call_expr.is_method && node.call_expr.receiver_type.is_ptr() && !node.call_expr.left_type.is_ptr() { c.error('method in `go` statement cannot have non-reference mutable receiver', node.call_expr.left.pos()) } if c.is_js_backend { return c.table.find_or_register_promise(c.unwrap_generic(ret_type)) } else { return c.table.find_or_register_thread(c.unwrap_generic(ret_type)) } } @[direct_array_access] fn (mut c Checker) set_node_expected_arg_types(mut node ast.CallExpr, func &ast.Fn) { if node.expected_arg_types.len == 0 { start_idx := if func.is_method { 1 } else { 0 } for i in start_idx .. func.params.len { node.expected_arg_types << func.params[i].typ } } if func.generic_names.len > 0 { for t in node.expected_arg_types { c.unwrap_generic(t) // TODO: the result is not used, have to see if this is called just for the side effects } } } fn (mut c Checker) post_process_generic_fns() ! { mut all_generic_fns := map[string]int{} // Loop thru each generic function concrete type. // Check each specific fn instantiation. for i in 0 .. c.file.generic_fns.len { c.mod = c.file.generic_fns[i].mod fkey := c.file.generic_fns[i].fkey() all_generic_fns[fkey]++ if all_generic_fns[fkey] > generic_fn_cutoff_limit_per_fn { c.error('${fkey} generic function visited more than ${generic_fn_cutoff_limit_per_fn} times', c.file.generic_fns[i].pos) return error('fkey: ${fkey}') } gtypes := c.table.fn_generic_types[fkey] $if trace_post_process_generic_fns ? { eprintln('> post_process_generic_fns ${c.file.generic_fns[i].mod} | ${c.file.generic_fns[i].name} | fkey: ${fkey} | gtypes: ${gtypes} | c.file.generic_fns.len: ${c.file.generic_fns.len}') } for concrete_types in gtypes { c.table.cur_concrete_types = concrete_types mut concrete_fn := c.file.generic_fns[i] original_generic_names := concrete_fn.generic_names.clone() if concrete_fn.is_method && concrete_types.len > original_generic_names.len { receiver_generic_names := c.table.generic_type_names(concrete_fn.receiver.typ) if receiver_generic_names.len > 0 { mut effective_generic_names := []string{cap: receiver_generic_names.len + original_generic_names.len} for name in receiver_generic_names { if name !in effective_generic_names { effective_generic_names << name } } for name in original_generic_names { if name !in effective_generic_names { effective_generic_names << name } } if effective_generic_names.len == concrete_types.len { unsafe { mut p := &[]string(&concrete_fn.generic_names) *p = effective_generic_names } } } } c.fn_decl(mut concrete_fn) if concrete_fn.name in ['veb.run', 'veb.run_at'] { for ct in concrete_types { if ct !in c.veb_gen_types { c.veb_gen_types << ct } } } } c.table.cur_concrete_types = [] $if trace_post_process_generic_fns ? { if c.file.generic_fns[i].generic_names.len > 0 { eprintln(' > fn_decl node.name: ${c.file.generic_fns[i].name} | generic_names: ${c.file.generic_fns[i].generic_names} | ninstances: ${c.file.generic_fns[i].ninstances}') } } } } fn min_required_call_params(f &ast.Fn, is_method_call bool) int { start_idx := if is_method_call && f.params.len > 0 { 1 } else { 0 } if start_idx >= f.params.len { return 0 } mut required := f.params.len - start_idx mut idx := f.params.len - 1 for idx >= start_idx { if f.params[idx].typ.has_flag(.option) { required-- idx-- continue } break } return required } @[inline] fn variadic_call_arg_start_idx(f &ast.Fn, is_method_call bool) int { start_idx := if is_method_call && f.params.len > 0 { 1 } else { 0 } mut fixed_args := f.params.len - start_idx if f.is_variadic && !f.is_c_variadic && fixed_args > 0 { fixed_args-- } return if fixed_args >= 0 { fixed_args } else { 0 } } @[inline] fn call_arg_param_for_fn(f &ast.Fn, arg_idx int, is_method_call bool) ast.Param { start_idx := if is_method_call && f.params.len > 0 { 1 } else { 0 } variadic_start := variadic_call_arg_start_idx(f, is_method_call) if f.is_variadic && !f.is_c_variadic && arg_idx >= variadic_start { return f.params.last() } param_idx := start_idx + arg_idx if param_idx < f.params.len { return f.params[param_idx] } return ast.Param{ typ: ast.any_type } } fn call_can_fill_optional_args(node &ast.CallExpr) bool { if node.args.any(it.expr is ast.ArrayDecompose) { return false } return !node.args.any(it.expr is ast.CallExpr && it.expr.nr_ret_values > 1) } fn fill_trailing_optional_call_args(mut node ast.CallExpr, f &ast.Fn) { start_idx := if node.is_method && f.params.len > 0 { 1 } else { 0 } expected_args := f.params.len - start_idx for i := node.args.len; i < expected_args; i++ { if !f.params[start_idx + i].typ.has_flag(.option) { break } node.args << ast.CallArg{ expr: ast.None{ pos: node.pos } pos: node.pos } } } fn (mut c Checker) check_expected_arg_count(mut node ast.CallExpr, f &ast.Fn) ! { mut nr_args := node.args.len nr_params := if node.is_method && f.params.len > 0 { f.params.len - 1 } else { f.params.len } optional_required_params := min_required_call_params(f, node.is_method) mut min_required_params := optional_required_params can_fill_optional_args := call_can_fill_optional_args(node) if f.is_variadic { node.is_variadic = f.is_variadic node.is_c_variadic = f.is_c_variadic if !f.is_c_variadic { min_required_params-- c.markused_array_method(!c.is_builtin_mod, '') } } else { has_decompose := node.args.any(it.expr is ast.ArrayDecompose) if has_decompose { // if call(...args) is present min_required_params = nr_args - 1 } } // check if multi-return is used as unique argument to the function if node.args.len == 1 && mut node.args[0].expr is ast.CallExpr { is_multi := node.args[0].expr.nr_ret_values > 1 if is_multi && !(node.name in print_everything_fns && f.mod == 'builtin') { // it is a multi-return argument nr_args = node.args[0].expr.nr_ret_values if nr_args != nr_params { unexpected_args_pos := node.args[0].pos.extend(node.args.last().pos) // TODO use this here as well /* c.fn_call_error_have_want( nr_params: min_required_params nr_args: nr_args params: f.params args: node.args pos: node.pos ) */ c.error('expected ${min_required_params} arguments, but got ${nr_args} from multi-return ${c.table.type_to_str(node.args[0].expr.return_type)}', unexpected_args_pos) return error('') } } } else if node.args.len > 1 && node.args.any(it.expr is ast.CallExpr && it.expr.nr_ret_values > 1) { mut check_args := 0 for arg in node.args { if arg.expr is ast.CallExpr && arg.expr.nr_ret_values > 0 { check_args += arg.expr.nr_ret_values } else { check_args += 1 } } nr_args = check_args } if min_required_params < 0 { min_required_params = 0 } if nr_args < min_required_params { if min_required_params == nr_args + 1 { start_idx := if node.is_method && f.params.len > 0 { 1 } else { 0 } last_required_param := f.params[start_idx + min_required_params - 1] // params struct? last_typ := last_required_param.typ last_sym := c.table.sym(last_typ) if last_sym.info is ast.Struct { is_params := last_sym.info.attrs.any(it.name == 'params' && !it.has_arg) if is_params { // allow empty trailing struct syntax arg (`f()` where `f` is `fn(ConfigStruct)`) node.args << ast.CallArg{ pos: node.pos expr: ast.StructInit{ typ: last_typ pos: node.pos name_pos: node.name_pos } } return } } if c.try_add_implicit_veb_context_arg(mut node, f) { return } } c.fn_call_error_have_want( nr_params: min_required_params nr_args: nr_args params: f.params args: node.args pos: node.pos ) return error('') } else if !f.is_variadic && nr_args > nr_params { unexpected_args_pos := node.args[int_min(min_required_params, node.args.len - 1)].pos.extend(node.args.last().pos) // c.error('3expected ${min_required_params} arguments, but got ${nr_args}', unexpected_args_pos) c.fn_call_error_have_want( nr_params: min_required_params nr_args: nr_args params: f.params args: node.args pos: unexpected_args_pos ) return error('') } else if !f.is_variadic && nr_args < nr_params && optional_required_params < nr_params { if !can_fill_optional_args { c.fn_call_error_have_want( nr_params: nr_params nr_args: nr_args params: f.params args: node.args pos: node.pos ) return error('') } fill_trailing_optional_call_args(mut node, f) } } fn (mut c Checker) try_add_implicit_veb_context_arg(mut node ast.CallExpr, f &ast.Fn) bool { if f.params.len == 0 || f.params.last().name != 'ctx' || !c.has_veb_context(f.params.last().typ) { return false } scope := if c.fn_scope != unsafe { nil } { c.fn_scope } else { node.scope } if scope == unsafe { nil } { return false } ctx_var := scope.find_var('ctx') or { return false } node.args << ast.CallArg{ is_mut: f.params.last().is_mut typ: ctx_var.typ pos: node.pos expr: ast.Ident{ language: .v tok_kind: .name pos: node.pos scope: scope obj: *ctx_var mod: c.mod name: 'ctx' kind: .variable info: ast.IdentVar{ typ: ctx_var.typ is_mut: ctx_var.is_mut } is_mut: f.params.last().is_mut } } return true } @[params] struct HaveWantParams { nr_params int nr_args int args []ast.CallArg params []ast.Param pos token.Pos } fn (mut c Checker) fn_call_error_have_want(p HaveWantParams) { mut sb := strings.new_builder(20) sb.write_string('have (') // Fetch arg types, they are always 0 at this point // Duplicate logic, but we don't care, since this is an error, so no perf cost mut arg_types := []ast.Type{len: p.args.len} for i, arg in p.args { mut e := arg.expr arg_types[i] = c.expr(mut e) } // Args provided by the user for i, _ in p.args { if arg_types[i] == 0 { // arg.typ == 0 { // Arguments can have an unknown (invalid) type // This should never happen. sb.write_string('?') } else { sb.write_string(c.table.type_to_str(arg_types[i])) // arg.typ } if i < p.args.len - 1 { sb.write_string(', ') } } sb.write_string(')') c.add_error_detail(sb.str()) // Actual parameters we expect sb.write_string(' want (') for i, param in p.params { if i == 0 && p.nr_params == p.params.len - 1 { // Skip receiver continue } sb.write_string(c.table.type_to_str(param.typ)) if i < p.params.len - 1 { sb.write_string(', ') } } sb.write_string(')') c.add_error_detail(sb.str()) args_plural := if p.nr_params == 1 { 'argument' } else { 'arguments' } c.error('expected ${p.nr_params} ${args_plural}, but got ${p.nr_args}', p.pos) } fn (mut c Checker) check_predicate_param(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 } mut arg_expr := node.args[0].expr match mut arg_expr { ast.AnonFn { if arg_expr.decl.return_type.has_flag(.option) { c.error('option needs to be unwrapped before using it in map/filter', node.args[0].pos) } 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 ${c.table.type_to_str(elem_typ)}) 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 ${c.table.type_to_str(elem_typ)}) 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.return_type.has_flag(.option) { c.error('option needs to be unwrapped before using it in map/filter', node.pos) } 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 ${c.table.type_to_str(elem_typ)}) 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 ${c.table.type_to_str(elem_typ)}) bool {...}`', arg_expr.pos) } } else if arg_expr.kind == .variable { if mut arg_expr.obj is ast.Var { expr := arg_expr.obj.expr if expr is ast.AnonFn { // copied from above if expr.decl.return_type.has_flag(.option) { c.error('option needs to be unwrapped before using it in map/filter', arg_expr.pos) } 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 ${c.table.type_to_str(elem_typ)}) 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 ${c.table.type_to_str(elem_typ)}) bool {...}`', expr.decl.pos) } return } } // NOTE: using a local copy to avoid a cgen issue with mut match // and method calls on smartcast variants (double pointer). ident := arg_expr if !is_map && ident.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 { if arg_expr.or_block.kind != .absent && (arg_expr.return_type.has_flag(.option) || arg_expr.return_type.has_flag(.result)) && arg_expr.return_type.clear_option_and_result() == ast.bool_type { return } c.error('type mismatch, `${arg_expr.name}` must return a bool', arg_expr.pos) } if arg_expr.return_type.has_flag(.result) && arg_expr.or_block.kind != .block { if arg_expr.return_type.clear_option_and_result() in [ast.void_type, 0] { c.error('cannot use Result type in `${node.name}`', arg_expr.pos) } } } ast.StringLiteral, ast.StringInterLiteral { if !is_map { c.error('type mismatch, should use e.g. `${node.name}(it > 2)`', arg_expr.pos) } } ast.InfixExpr { if arg_expr.op == .left_shift && arg_expr.is_stmt && c.table.final_sym(arg_expr.left_type).kind == .array { c.error('array append cannot be used in an expression', arg_expr.pos) } } ast.LambdaExpr { if mut arg_expr.expr is ast.CallExpr && is_map && arg_expr.expr.return_type in [ast.void_type, 0] { c.error('type mismatch, `${arg_expr.expr.name}` does not return anything', arg_expr.expr.pos) } } else { if !is_map && c.expr(mut arg_expr) != ast.bool_type { c.error('invalid expression, expected infix expr, lambda or function', arg_expr.pos()) } } } } fn (mut c Checker) map_builtin_method_call(mut node ast.CallExpr, left_type_ ast.Type) ast.Type { method_name := node.name mut ret_type := ast.void_type // resolve T left_type := if c.table.final_sym(left_type_).kind == .any { c.unwrap_generic(left_type_) } else { left_type_ } left_sym := c.table.final_sym(left_type) match node.kind { .clone, .move { if node.args.len != 0 { c.error('`.${method_name}()` does not have any arguments', node.args[0].pos) } if method_name[0] == `m` { c.check_for_mut_receiver(mut node.left) } if node.left.is_auto_deref_var() || left_type.has_flag(.shared_f) { ret_type = node.left_type.deref() } else { ret_type = node.left_type } ret_type = ret_type.clear_flag(.shared_f) } .keys, .values { if node.args.len != 0 { c.error('`.${method_name}()` does not have any arguments', node.args[0].pos) } info := left_sym.info as ast.Map typ := if node.kind == .keys { c.table.find_or_register_array(info.key_type) } else { c.table.find_or_register_array(info.value_type) } ret_type = ast.idx_to_type(typ) if info.key_type.has_flag(.generic) && node.kind == .keys { ret_type = ret_type.set_flag(.generic) } if info.value_type.has_flag(.generic) && node.kind == .values { ret_type = ret_type.set_flag(.generic) } } .delete { c.check_for_mut_receiver(mut node.left) if node.args.len == 1 { info := left_sym.info as ast.Map arg_type := c.expr(mut 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 { c.error('expected 1 argument, but got ${node.args.len}', node.pos) } } else {} } node.receiver_type = node.left_type.ref() node.return_type = ret_type return node.return_type } // ensure_same_array_return_type makes sure, that the return type of .clone(), .sorted(), .sorted_with_compare() etc, // is `array_xxx`, instead of the plain `array` . fn (mut c Checker) ensure_same_array_return_type(mut node ast.CallExpr, left_type ast.Type) { node.receiver_type = left_type.ref() if node.left.is_auto_deref_var() && left_type.nr_muls() > 0 { node.return_type = left_type.deref() } else { node.return_type = node.receiver_type.set_nr_muls(0) } if node.return_type.has_flag(.shared_f) { node.return_type = node.return_type.clear_flag(.shared_f) } } fn (mut c Checker) array_builtin_method_call(mut node ast.CallExpr, left_type ast.Type) ast.Type { left_sym := c.table.final_sym(left_type) method_name := node.name mut elem_typ := ast.void_type if !c.is_builtin_mod && node.kind == .slice { c.error('.slice() is a private method, use `x[start..end]` instead', node.pos) return ast.void_type } unwrapped_left_type := c.unwrap_generic(left_type) unaliased_left_type := c.table.unaliased_type(unwrapped_left_type) mut array_info := ast.Array{} if left_sym.info is ast.Array { array_info = left_sym.info as ast.Array } else { array_info = c.table.sym(unaliased_left_type).info as ast.Array } elem_typ = array_info.elem_type node_args_len := node.args.len mut arg0 := if node_args_len > 0 { node.args[0] } else { ast.CallArg{} } if ast.builtin_array_generic_methods_no_sort_matcher.matches(method_name) { if node_args_len > 0 && mut arg0.expr is ast.LambdaExpr { if arg0.expr.params.len != 1 { c.error('lambda expressions used in the builtin array methods require exactly 1 parameter', arg0.expr.pos) return ast.void_type } if node.kind == .map { c.lambda_expr_fix_type_of_param(mut arg0.expr, mut arg0.expr.params[0], elem_typ) le_type := c.expr(mut arg0.expr.expr) // eprintln('>>>>> node.args[0].expr: ${ast.Expr(node.args[0].expr)} | elem_typ: ${elem_typ} | etype: ${le_type}') c.support_lambda_expr_one_param(elem_typ, le_type, mut arg0.expr) } else { c.support_lambda_expr_one_param(elem_typ, ast.bool_type, mut arg0.expr) } } else { // position of `it` doesn't matter scope_register_it(mut node.scope, node.pos, elem_typ) } } else if node.kind in [.insert, .prepend] { if node.kind == .insert { if node_args_len != 2 { c.error('`array.insert()` should have 2 arguments, e.g. `insert(1, val)`', node.pos) return ast.void_type } else { arg_type := c.expr(mut arg0.expr) if arg_type !in [ast.int_type, ast.int_literal_type] { c.error('the first argument of `array.insert()` should be integer', arg0.expr.pos()) return ast.void_type } } c.table.used_features.arr_insert = true } else { c.table.used_features.arr_prepend = true if node_args_len != 1 { c.error('`array.prepend()` should have 1 argument, e.g. `prepend(val)`', node.pos) return ast.void_type } } c.check_for_mut_receiver(mut node.left) info := left_sym.info as ast.Array val_arg_n := if node.kind == .insert { 1 } else { 0 } node.receiver_type = ast.array_type.ref() node.return_type = ast.void_type if method := c.table.find_method(left_sym, method_name) { for i, mut arg in node.args { node.args[i].typ = c.expr(mut arg.expr) if i == val_arg_n { arg_sym := c.table.sym(node.args[i].typ) base_arg_type := c.unwrap_generic(node.args[i].typ) if c.check_types(base_arg_type, info.elem_type) { if !base_arg_type.is_ptr() && info.elem_type.is_ptr() && info.elem_type.share() == .mut_t { c.error('cannot ${method_name} `${arg_sym.name}` to `${left_sym.name}`', arg.expr.pos()) continue } } else if !c.check_types(base_arg_type, unwrapped_left_type) && !c.check_types(elem_typ, base_arg_type) { c.error('cannot ${method_name} `${arg_sym.name}` to `${left_sym.name}`', arg.expr.pos()) continue } } c.check_expected_call_arg(arg.typ, method.params[i + 1].typ, node.language, arg) or { c.error('${err.msg()} in argument ${i + 1} to `${left_sym.name}.${method_name}`', node.args[i].pos) } } } } else if node.kind in [.sort_with_compare, .sorted_with_compare] { if node_args_len != 1 { c.error('`.${method_name}()` expected 1 argument, but got ${node_args_len}', node.pos) } else { if mut arg0.expr is ast.LambdaExpr { c.support_lambda_expr_in_sort(elem_typ.ref(), ast.int_type, mut arg0.expr) } arg_type := c.expr(mut arg0.expr) arg_sym := c.table.sym(arg_type) if arg_sym.kind == .function { func_info := arg_sym.info as ast.FnType if func_info.func.params.len == 2 { if func_info.func.params[0].typ.nr_muls() != elem_typ.nr_muls() + 1 { arg_typ_str := c.table.type_to_str(func_info.func.params[0].typ) expected_typ_str := c.table.type_to_str(elem_typ.ref()) c.error('${method_name} callback function parameter `${func_info.func.params[0].name}` with type `${arg_typ_str}` should be `${expected_typ_str}`', func_info.func.params[0].type_pos) } if func_info.func.params[1].typ.nr_muls() != elem_typ.nr_muls() + 1 { arg_typ_str := c.table.type_to_str(func_info.func.params[1].typ) expected_typ_str := c.table.type_to_str(elem_typ.ref()) c.error('${method_name} callback function parameter `${func_info.func.params[1].name}` with type `${arg_typ_str}` should be `${expected_typ_str}`', func_info.func.params[1].type_pos) } } } arg0.typ = arg_type if method := c.table.find_method(left_sym, method_name) { c.check_expected_call_arg(arg_type, method.params[1].typ, node.language, arg0) or { c.error('${err.msg()} in argument 1 to `${left_sym.name}.${method_name}`', arg0.pos) } } if node.kind == .sort_with_compare { c.check_for_mut_receiver(mut node.left) node.return_type = ast.void_type node.receiver_type = node.left_type.ref() } else { node.return_type = node.left_type node.receiver_type = node.left_type } } } else if node.kind in [.sort, .sorted] { if node.kind == .sort { if node.left is ast.CallExpr { c.error('the `sort()` method can be called only on mutable receivers, but `${ast.Expr(node.left)}` is a call expression', node.pos) } c.check_for_mut_receiver(mut 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 mut arg0.expr is ast.LambdaExpr { c.support_lambda_expr_in_sort(elem_typ.ref(), ast.bool_type, mut arg0.expr) } else if mut arg0.expr is ast.InfixExpr { c.check_sort_external_variable_access(ast.Expr(arg0.expr)) if arg0.expr.op !in [.gt, .lt] { c.error('`.${method_name}()` can only use `<` or `>` comparison', node.pos) } left_name := '${arg0.expr.left}'[0] right_name := '${arg0.expr.right}'[0] if left_name !in [`a`, `b`] || right_name !in [`a`, `b`] { c.error('`.${method_name}()` can only use `a` or `b` as argument, e.g. `arr.${method_name}(a < b)`', node.pos) } else if left_name == right_name { c.error('`.${method_name}()` cannot use same argument', node.pos) } if arg0.expr.left !in [ast.CallExpr, ast.Ident, ast.SelectorExpr, ast.IndexExpr] || arg0.expr.right !in [ast.CallExpr, ast.Ident, ast.SelectorExpr, ast.IndexExpr] { c.error('`.${method_name}()` can only use ident, index, selector or call as argument, \ne.g. `arr.${method_name}(a < b)`, `arr.${method_name}(a.id < b.id)`, `arr.${method_name}(a[0] < b[0])`', node.pos) } } else { c.error( '`.${method_name}()` requires a `<` or `>` comparison as the first and only argument' + '\ne.g. `users.${method_name}(a.id < b.id)`', node.pos) } } else if !(c.table.sym(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.i32_type, ast.i64_type, ast.u8_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 node.kind == .wait { elem_sym := c.table.sym(elem_typ) if elem_sym.kind == .thread { if node_args_len != 0 { c.error('`.wait()` does not have any arguments', arg0.pos) } thread_ret_type := c.unwrap_generic(elem_sym.thread_info().return_type) node.return_type = c.thread_array_wait_return_type(thread_ret_type) } else { c.error('`${left_sym.name}` has no method `wait()` (only thread handles and arrays of them have)', node.left.pos()) } } // map/filter are supposed to have 1 arg only mut arg_type := unaliased_left_type for mut arg in node.args { mut expr_type := c.expr(mut arg.expr) if arg.expr is ast.AnonFn { // fix anon fn when return is a fixed array expr_sym := c.table.sym(expr_type) info := expr_sym.info as ast.FnType return_type_sym := c.table.sym(info.func.return_type) if return_type_sym.kind == .array_fixed { expr_type = c.cast_fixed_array_ret(info.func.return_type, return_type_sym) } } arg_type = c.check_expr_option_or_result_call(arg.expr, expr_type) } // `arr.map(Type.from)`, `arr.filter(Type.ok)` etc.: a static method used as a // first-class value is parsed as an `ast.EnumVal` (it is syntactically identical // to an enum value, e.g. `Color.red`). The parser can only emit a function // `ast.Ident` for it when the static method is already registered, which fails // for cross-module (and forward) references, since imported modules are parsed // after the call site. Once the checker has resolved the value to a function // type, rewrite it into a function `ast.Ident`, so the backends call it and // markused keeps the static method, exactly like any other function value. if node.kind in [.map, .filter, .any, .all, .count] && node.args.len > 0 { arg_expr := node.args[0].expr if arg_expr is ast.EnumVal && c.table.sym(arg_expr.typ).kind == .function { enum_typ := ast.new_type(c.table.find_type_idx(arg_expr.enum_name)) typ_sym := c.table.sym(enum_typ) fsym := c.table.final_sym(enum_typ) if func := c.static_method_of_enum_val(arg_expr, typ_sym, fsym) { node.args[0].expr = ast.Ident{ name: func.name mod: c.mod kind: .function info: ast.IdentFn{ typ: ast.new_type(c.table.find_or_register_fn_type(func, false, true)) } pos: arg_expr.pos scope: node.scope } } } } if node.kind == .map { // eprintln('>>>>>>> map node.args[0].expr: ${node.args[0].expr}, left_type: ${left_type} | elem_typ: ${elem_typ} | arg_type: ${arg_type}') // check fn c.check_predicate_param(true, elem_typ, node) arg_sym := c.table.sym(arg_type) ret_type := match arg_sym.info { ast.FnType { if arg0.expr is ast.SelectorExpr { arg_type } else { arg_sym.info.func.return_type } } else { arg_type } } normalized_ret_type := if c.table.sym(ret_type).kind == .alias { unaliased_ret_type := c.table.unaliased_type(ret_type) if unaliased_ret_type.has_option_or_result() { unaliased_ret_type } else { ret_type } } else { ret_type } if normalized_ret_type.has_flag(.result) { c.error('cannot use Result type in `${node.name}`', arg0.expr.pos()) } if c.pref.new_generic_solver { node.return_type = c.table.find_or_register_array(ret_type) } else { node.return_type = c.table.find_or_register_array(c.unwrap_generic(ret_type)) } if node.return_type.has_flag(.shared_f) { node.return_type = node.return_type.clear_flag(.shared_f).deref() } ret_sym := c.table.sym(ret_type) if ret_sym.kind == .multi_return { c.error('returning multiple values is not supported in .map() calls', node.pos) } if c.pref.new_generic_solver && ret_type.has_flag(.generic) { node.return_type = node.return_type.set_flag(.generic) } } else if node.kind == .filter { // check fn if node.return_type.has_flag(.shared_f) { node.return_type = node.return_type.clear_flag(.shared_f).deref() } else if node.left.is_auto_deref_var() { node.return_type = node.return_type.deref() } c.check_predicate_param(false, elem_typ, node) } else if node.kind in [.any, .all] { c.check_predicate_param(false, elem_typ, node) node.return_type = ast.bool_type } else if node.kind == .count { c.check_predicate_param(false, elem_typ, node) node.return_type = ast.int_type } else if node.kind == .clone { if node_args_len != 0 { c.error('`.clone()` does not have any arguments', arg0.pos) } c.ensure_same_array_return_type(mut node, left_type) } else if node.kind == .sorted { c.ensure_same_array_return_type(mut node, left_type) } else if node.kind in [.sort_with_compare, .sorted_with_compare] { if node.kind == .sorted_with_compare { c.ensure_same_array_return_type(mut node, left_type) } // Inject a (voidptr) cast for the callback argument, to pass -cstrict, otherwise: // error: incompatible function pointer types passing // 'int (string *, string *)' (aka 'int (struct string *, struct string *)') // to parameter of type 'int (*)(voidptr, voidptr)' (aka 'int (*)(void *, void *)') node.args[0].expr = ast.CastExpr{ expr: arg0.expr typ: ast.voidptr_type typname: 'voidptr' expr_type: c.expr(mut arg0.expr) pos: node.pos } } else if node.kind == .sort { node.return_type = ast.void_type } else if node.kind == .contains { // c.warn('use `value in arr` instead of `arr.contains(value)`', node.pos) if node_args_len != 1 { c.error('`.contains()` expected 1 argument, but got ${node_args_len}', node.pos) } else if !left_sym.has_method('contains') { arg_typ := c.unwrap_generic(c.expr(mut arg0.expr)) c.check_expected_call_arg(arg_typ, c.unwrap_generic(elem_typ), node.language, arg0) or { c.error('${err.msg()} in argument 1 to `.contains()`', arg0.pos) } } for i, mut arg in node.args { node.args[i].typ = c.expr(mut arg.expr) } node.return_type = ast.bool_type } else if node.kind in [.index, .last_index] { if node_args_len != 1 { c.error('`.${method_name}()` expected 1 argument, but got ${node_args_len}', node.pos) } else if !left_sym.has_method(method_name) { arg_typ := c.unwrap_generic(c.expr(mut arg0.expr)) c.check_expected_call_arg(arg_typ, c.unwrap_generic(elem_typ), node.language, arg0) or { c.error('${err.msg()} in argument 1 to `.${method_name}()`', arg0.pos) } } for i, mut arg in node.args { node.args[i].typ = c.expr(mut arg.expr) } node.return_type = ast.int_type } else if method_name == 'get' { if node_args_len != 1 { c.error('`.get()` expected 1 argument, but got ${node_args_len}', node.pos) } else { arg_typ := c.unwrap_generic(c.expr(mut arg0.expr)) c.check_expected_call_arg(arg_typ, ast.int_type, node.language, arg0) or { c.error('${err.msg()} in argument 1 to `.get()`', arg0.pos) } } for i, mut arg in node.args { node.args[i].typ = c.expr(mut arg.expr) } node.receiver_type = ast.array_type node.return_type = array_info.elem_type.set_flag(.option) } else if node.kind in [.first, .last, .pop_left, .pop] { c.markused_array_method(!c.is_builtin_mod, method_name) if node_args_len != 0 { c.error('`.${method_name}()` does not have any arguments', arg0.pos) } node.return_type = array_info.elem_type if node.kind in [.pop_left, .pop] { c.check_for_mut_receiver(mut node.left) node.receiver_type = node.left_type.ref() } else { node.receiver_type = node.left_type } } else if node.kind == .delete { c.markused_array_method(!c.is_builtin_mod, method_name) c.check_for_mut_receiver(mut node.left) unwrapped_left_sym := c.table.sym(unwrapped_left_type) if method := c.table.find_method(unwrapped_left_sym, method_name) { node.receiver_type = method.receiver_type } if node_args_len != 1 { c.error('`.delete()` expected 1 argument, but got ${node_args_len}', node.pos) } else { arg_typ := c.unwrap_generic(c.expr(mut arg0.expr)) c.check_expected_call_arg(arg_typ, ast.int_type, node.language, arg0) or { c.error('${err.msg()} in argument 1 to `.delete()`', arg0.pos) } } node.return_type = ast.void_type } else if node.kind == .delete_many { if node_args_len != 2 { c.error('`.delete_many()` expected 2 arguments, but got ${node_args_len}', node.pos) } else { for i, mut arg in node.args { arg_typ := c.expr(mut arg.expr) c.check_expected_call_arg(arg_typ, ast.int_type, node.language, arg) or { c.error('${err.msg()} in argument ${i + 1} to `.delete_many()`', arg.pos) } } } node.return_type = ast.void_type } else if node.kind == .reverse { c.table.used_features.arr_reverse = true } return node.return_type } fn (mut c Checker) fixed_array_builtin_method_call(mut node ast.CallExpr, left_type ast.Type) ast.Type { left_sym := c.table.final_sym(left_type) method_name := node.name unwrapped_left_type := c.unwrap_generic(left_type) unaliased_left_type := c.table.unaliased_type(unwrapped_left_type) mut array_info := ast.ArrayFixed{} if left_sym.info is ast.ArrayFixed { array_info = left_sym.info as ast.ArrayFixed } else { array_info = c.table.sym(unaliased_left_type).info as ast.ArrayFixed } node_args_len := node.args.len mut arg0 := if node_args_len > 0 { node.args[0] } else { ast.CallArg{} } elem_typ := array_info.elem_type if node.kind in [.index, .last_index] { if node_args_len != 1 { c.error('`.${method_name}()` expected 1 argument, but got ${node_args_len}', node.pos) return ast.int_type } else if (node.kind == .index && !left_sym.has_method('index')) || (node.kind == .last_index && !left_sym.has_method('last_index')) { arg_typ := c.expr(mut arg0.expr) c.check_expected_call_arg(arg_typ, elem_typ, node.language, arg0) or { c.error('${err.msg()} in argument 1 to `.${method_name}()`', arg0.pos) return ast.int_type } } for i, mut arg in node.args { node.args[i].typ = c.expr(mut arg.expr) } node.return_type = ast.int_type } else if node.kind == .contains { if node_args_len != 1 { c.error('`.contains()` expected 1 argument, but got ${node_args_len}', node.pos) return ast.bool_type } else if !left_sym.has_method('contains') { arg_typ := c.expr(mut arg0.expr) c.check_expected_call_arg(arg_typ, elem_typ, node.language, arg0) or { c.error('${err.msg()} in argument 1 to `.contains()`', arg0.pos) return ast.bool_type } } for i, mut arg in node.args { node.args[i].typ = c.expr(mut arg.expr) } node.return_type = ast.bool_type } else if node.kind in [.any, .all] { if node_args_len != 1 { c.error('`.${method_name}` expected 1 argument, but got ${node_args_len}', node.pos) return ast.bool_type } if node_args_len > 0 && mut arg0.expr is ast.LambdaExpr { if arg0.expr.params.len != 1 { c.error('lambda expressions used in the builtin array methods require exactly 1 parameter', arg0.expr.pos) return ast.bool_type } c.support_lambda_expr_one_param(elem_typ, ast.bool_type, mut arg0.expr) } else { // position of `it` doesn't matter scope_register_it(mut node.scope, node.pos, elem_typ) } c.expr(mut arg0.expr) c.check_predicate_param(false, elem_typ, node) node.return_type = ast.bool_type } else if node.kind == .count { if node_args_len != 1 { c.error('`.${method_name}` expected 1 argument, but got ${node_args_len}', node.pos) return ast.bool_type } if node_args_len > 0 && mut arg0.expr is ast.LambdaExpr { if arg0.expr.params.len != 1 { c.error('lambda expressions used in the builtin array methods require exactly 1 parameter', arg0.expr.pos) return ast.bool_type } c.support_lambda_expr_one_param(elem_typ, ast.bool_type, mut arg0.expr) } else { // position of `it` doesn't matter scope_register_it(mut node.scope, node.pos, elem_typ) } c.expr(mut arg0.expr) c.check_predicate_param(false, elem_typ, node) node.return_type = ast.int_type } else if node.kind == .filter { if node_args_len != 1 { c.error('`.${method_name}` expected 1 argument, but got ${node_args_len}', node.pos) } // position of `it` doesn't matter scope_register_it(mut node.scope, node.pos, elem_typ) c.expr(mut arg0.expr) c.check_predicate_param(false, elem_typ, node) node.return_type = c.table.find_or_register_array(elem_typ) } else if node.kind == .wait { elem_sym := c.table.sym(elem_typ) if elem_sym.kind == .thread { if node_args_len != 0 { c.error('`.wait()` does not have any arguments', arg0.pos) } thread_ret_type := c.unwrap_generic(elem_sym.thread_info().return_type) node.return_type = c.thread_array_wait_return_type(thread_ret_type) } else { c.error('`${left_sym.name}` has no method `wait()` (only thread handles and arrays of them have)', node.left.pos()) } } else if node.kind == .map { if node_args_len != 1 { c.error('`.${method_name}` expected 1 argument, but got ${node_args_len}', node.pos) return ast.void_type } if mut arg0.expr is ast.LambdaExpr { if arg0.expr.params.len != 1 { c.error('lambda expressions used in the builtin array methods require exactly 1 parameter', arg0.expr.pos) return ast.void_type } c.lambda_expr_fix_type_of_param(mut arg0.expr, mut arg0.expr.params[0], elem_typ) le_type := c.expr(mut arg0.expr.expr) c.support_lambda_expr_one_param(elem_typ, le_type, mut arg0.expr) } else { // position of `it` doesn't matter scope_register_it(mut node.scope, node.pos, elem_typ) } c.check_predicate_param(true, elem_typ, node) arg_type := c.check_expr_option_or_result_call(arg0.expr, c.expr(mut arg0.expr)) arg_sym := c.table.sym(arg_type) ret_type := match arg_sym.info { ast.FnType { if arg0.expr is ast.SelectorExpr { arg_type } else { arg_sym.info.func.return_type } } else { arg_type } } node.return_type = c.table.find_or_register_array_fixed(c.unwrap_generic(ret_type), array_info.size, array_info.size_expr, false) if node.return_type.has_flag(.shared_f) { node.return_type = node.return_type.clear_flag(.shared_f).deref() } ret_sym := c.table.sym(ret_type) if ret_sym.kind == .multi_return { c.error('returning multiple values is not supported in .map() calls', node.pos) } } else if node.kind in [.sort, .sorted] { if node.kind == .sort { if node.left is ast.CallExpr { c.error('the `sort()` method can be called only on mutable receivers, but `${ast.Expr(node.left)}` is a call expression', node.pos) } c.check_for_mut_receiver(mut 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 mut arg0.expr is ast.LambdaExpr { c.support_lambda_expr_in_sort(elem_typ.ref(), ast.bool_type, mut arg0.expr) } else if mut arg0.expr is ast.InfixExpr { c.check_sort_external_variable_access(ast.Expr(arg0.expr)) if arg0.expr.op !in [.gt, .lt] { c.error('`.${method_name}()` can only use `<` or `>` comparison', node.pos) } left_name := '${arg0.expr.left}'[0] right_name := '${arg0.expr.right}'[0] if left_name !in [`a`, `b`] || right_name !in [`a`, `b`] { c.error('`.${method_name}()` can only use `a` or `b` as argument, e.g. `arr.${method_name}(a < b)`', node.pos) } else if left_name == right_name { c.error('`.${method_name}()` cannot use same argument', node.pos) } if arg0.expr.left !in [ast.Ident, ast.SelectorExpr, ast.IndexExpr] || arg0.expr.right !in [ast.Ident, ast.SelectorExpr, ast.IndexExpr] { c.error('`.${method_name}()` can only use ident, index or selector as argument, \ne.g. `arr.${method_name}(a < b)`, `arr.${method_name}(a.id < b.id)`, `arr.${method_name}(a[0] < b[0])`', node.pos) } } else { c.error( '`.${method_name}()` requires a `<` or `>` comparison as the first and only argument' + '\ne.g. `users.${method_name}(a.id < b.id)`', node.pos) } } else if !(c.table.sym(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.i32_type, ast.i64_type, ast.u8_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) } for mut arg in node.args { c.check_expr_option_or_result_call(arg.expr, c.expr(mut arg.expr)) } if node.kind == .sort { node.return_type = ast.void_type } else { node.return_type = node.left_type } } else if node.kind in [.sort_with_compare, .sorted_with_compare] { if node_args_len != 1 { c.error('`.${method_name}()` expected 1 argument, but got ${node_args_len}', node.pos) } else { if mut arg0.expr is ast.LambdaExpr { c.support_lambda_expr_in_sort(elem_typ.ref(), ast.int_type, mut arg0.expr) } arg_type := c.expr(mut arg0.expr) arg_sym := c.table.sym(arg_type) if arg_sym.kind == .function { func_info := arg_sym.info as ast.FnType if func_info.func.params.len == 2 { if func_info.func.params[0].typ.nr_muls() != elem_typ.nr_muls() + 1 { arg_typ_str := c.table.type_to_str(func_info.func.params[0].typ) expected_typ_str := c.table.type_to_str(elem_typ.ref()) c.error('${method_name} callback function parameter `${func_info.func.params[0].name}` with type `${arg_typ_str}` should be `${expected_typ_str}`', func_info.func.params[0].type_pos) } if func_info.func.params[1].typ.nr_muls() != elem_typ.nr_muls() + 1 { arg_typ_str := c.table.type_to_str(func_info.func.params[1].typ) expected_typ_str := c.table.type_to_str(elem_typ.ref()) c.error('${method_name} callback function parameter `${func_info.func.params[1].name}` with type `${arg_typ_str}` should be `${expected_typ_str}`', func_info.func.params[1].type_pos) } } } arg0.typ = arg_type if method := c.table.find_method(left_sym, method_name) { c.check_expected_call_arg(arg_type, method.params[1].typ, node.language, arg0) or { c.error('${err.msg()} in argument 1 to `${left_sym.name}.${method_name}`', arg0.pos) } } for mut arg in node.args { c.check_expr_option_or_result_call(arg.expr, c.expr(mut arg.expr)) } if node.kind == .sort_with_compare { c.check_for_mut_receiver(mut node.left) node.return_type = ast.void_type node.receiver_type = node.left_type.ref() } else { node.return_type = node.left_type node.receiver_type = node.left_type } } } else if node.kind in [.reverse, .reverse_in_place] { if node_args_len != 0 { c.error('`.${method_name}` does not have any arguments', arg0.pos) } else { if node.kind == .reverse { node.return_type = node.left_type } else { c.check_for_mut_receiver(mut node.left) node.return_type = ast.void_type } } } return node.return_type } fn (mut c Checker) check_for_mut_receiver(mut expr ast.Expr) (string, token.Pos) { to_lock, pos := c.fail_if_immutable(mut expr) if !expr.is_lvalue() { c.error('cannot pass expression as `mut`', expr.pos()) } return to_lock, pos } fn scope_register_it(mut s ast.Scope, pos token.Pos, typ ast.Type) { scope_register_special_var_name(mut s, pos, typ, 'it') } fn scope_register_a_b(mut s ast.Scope, pos token.Pos, typ ast.Type) { scope_register_special_var_name(mut s, pos, typ.ref(), 'a') scope_register_special_var_name(mut s, pos, typ.ref(), 'b') } fn scope_register_special_var_name(mut s ast.Scope, pos token.Pos, typ ast.Type, name string) { if name in s.objects { mut obj := unsafe { s.objects[name] } if mut obj is ast.Var { if obj.is_special { obj.pos = pos obj.typ = typ obj.orig_type = ast.no_type obj.smartcasts = [] obj.is_unwrapped = false return } } } s.register(ast.Var{ name: name pos: pos typ: typ is_used: false is_special: true }) } // resolve_fn_return_type resolves the generic return type of fn with its related CallExpr fn (mut c Checker) resolve_fn_return_type(func &ast.Fn, node ast.CallExpr, concrete_types []ast.Type) ast.Type { mut ret_type := func.return_type if node.is_method { // generic method being called from a non-generic func if func.generic_names.len > 0 && func.return_type.has_flag(.generic) && c.table.cur_fn != unsafe { nil } && c.table.cur_fn.generic_names.len == 0 { ret_type = c.table.unwrap_generic_type(func.return_type, func.generic_names, concrete_types) } // generic method called without generic type to be resolved on call if node.concrete_types.len > 0 && node.concrete_types.all(!it.has_flag(.generic)) && func.return_type.has_flag(.generic) && func.generic_names.len > 0 && func.generic_names.len == node.concrete_types.len { if typ := c.table.convert_generic_type(func.return_type, func.generic_names, concrete_types) { return typ } else { return c.table.unwrap_generic_type(func.return_type, func.generic_names, concrete_types) } } } else { // generic func called from non-generic func if node.concrete_types.len > 0 && func.return_type != 0 && c.table.cur_fn != unsafe { nil } && c.table.cur_fn.generic_names.len == 0 { if typ := c.table.convert_generic_type(func.return_type, func.generic_names, concrete_types) { return typ } return ret_type } if func.generic_names.len > 0 { has_generic := node.raw_concrete_types.any(it.has_flag(.generic)) has_any_generic := node.concrete_types.any(it.has_flag(.generic)) needs_resolved_concrete_types := node.raw_concrete_types.any(c.needs_unwrap_generic_type(it)) // fn call with any generic type to be resolved on call (e.g. foo[T]()) if has_generic || has_any_generic { if needs_resolved_concrete_types { if typ := c.table.convert_generic_type(func.return_type, func.generic_names, concrete_types) { return typ } } if typ := c.table.convert_generic_type(func.return_type, func.generic_names, node.concrete_types) { typ_sym := c.table.sym(typ) has_generic_concrete := typ_sym.kind == .generic_inst && typ_sym.info is ast.GenericInst && typ_sym.info.concrete_types.any(it.has_flag(.generic)) if typ.has_flag(.generic) || has_generic_concrete { return typ } } } else { // fn call with all generic types already resolved to its concrete ones (e.g. foo[int]()) if node.concrete_types.len > 0 && !has_any_generic { if typ := c.table.convert_generic_type(func.return_type, func.generic_names, node.concrete_types) { return typ } } // use fresh resolved concrete_types list if typ := c.table.convert_generic_type(func.return_type, func.generic_names, concrete_types) { return typ } } } } return ret_type } fn (mut c Checker) check_must_use_call_result(node &ast.CallExpr, f &ast.Fn, label string) { if node.is_return_used { return } if f.return_type == ast.void_type { return } if f.is_must_use { c.warn('return value must be used, ${label} `${f.name}` was tagged with `@[must_use]`', node.pos) return } if c.pref.is_check_return { c.note('return value must be used', node.pos) } } fn (mut c Checker) has_veb_context(typ ast.Type) bool { sym := c.table.final_sym(typ) if sym.name == 'veb.Context' { return true } else if sym.info is ast.Struct { for embed in sym.info.embeds { if c.table.sym(embed).name == 'veb.Context' { return true } } } return false } fn (mut c Checker) supports_veb_string_bound_param(typ ast.Type) bool { unaliased_typ := c.table.unalias_num_type(c.unwrap_generic(typ)) return unaliased_typ == ast.string_type || unaliased_typ.is_int() || unaliased_typ.idx() == ast.bool_type_idx } fn (mut c Checker) check_variadic_arg(arg_expr ast.Expr, typ ast.Type, expected_type ast.Type, param_typ ast.Type, arg_num int, fn_name string, is_method bool, fn_is_variadic bool, is_single_array_arg bool, is_generic_fn bool, call_pos token.Pos, arg_pos token.Pos) bool { kind := c.table.sym(typ).kind is_decompose := arg_expr is ast.ArrayDecompose mut allow_variadic_pass := false if arg_expr is ast.Ident && !(is_method && is_generic_fn) { ident := arg_expr as ast.Ident if ident.obj is ast.Var { var_obj := ident.obj as ast.Var if var_obj.is_arg && c.table.cur_fn != unsafe { nil } && c.table.cur_fn.is_variadic && c.table.cur_fn.params.len > 0 && ident.name == c.table.cur_fn.params.last().name && fn_is_variadic { cur_fn_param_sym := c.table.sym(c.table.cur_fn.params.last().typ) if cur_fn_param_sym.info is ast.Array { allow_variadic_pass = cur_fn_param_sym.info.elem_type == expected_type } } } } styp := c.table.type_to_str(typ) elem_styp := c.table.type_to_str(expected_type) expected_kind := c.table.sym(expected_type).kind sum_type_needs_spread := expected_kind == .sum_type && !c.table.sumtype_has_variant(expected_type, typ, false) && is_single_array_arg if kind == .array && !is_decompose && !allow_variadic_pass && (expected_kind !in [.sum_type, .interface] || sum_type_needs_spread) && !param_typ.has_flag(.generic) && expected_type != typ { c.error('to pass `${arg_expr}` (${styp}) to `${fn_name}` (which accepts type `...${elem_styp}`), use `...${arg_expr}`', call_pos) return true } else if is_decompose && !param_typ.has_flag(.generic) && expected_kind != .interface && expected_type.idx() != typ.idx() && typ != ast.void_type { c.error('cannot use `...${styp}` as `...${elem_styp}` in argument ${arg_num} to `${fn_name}`', arg_pos) return true } return kind == .array && !is_decompose && allow_variadic_pass } // autofree_expr_has_or_block_in_chain returns true if expr is a CallExpr whose call chain // contains an or_block (i.e. result/option propagation via `!` or `?`). Used by autofree to // skip tmp-var pre-generation for arguments like `get_str()!.to_upper()`, where the inner // call's or_block would interfere with output-buffer positioning in autofree_call_pregen. fn autofree_expr_has_or_block_in_chain(expr ast.Expr) bool { if expr is ast.CallExpr { if expr.or_block.kind != .absent { return true } if expr.is_method { return autofree_expr_has_or_block_in_chain(expr.left) } } return false }