// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved. // Use of this source code is governed by an MIT license // that can be found in the LICENSE file. module parser import v.ast import v.token import v.util import os fn table_fn_lookup(table &ast.Table, name string) (ast.Fn, bool) { if name !in table.fns { return ast.Fn{}, false } return unsafe { table.fns[name] }, true } fn comptime_define_idx(attrs []ast.Attr) int { for idx in 0 .. attrs.len { if attrs[idx].kind == .comptime_define { return idx } } return ast.invalid_type_idx } fn type_method_name_pos(sym &ast.TypeSymbol, name string, fallback token.Pos) token.Pos { for method in sym.methods { if method.name == name { return method.name_pos } } return fallback } fn (mut p Parser) call_expr(language ast.Language, mod string) ast.CallExpr { first_pos := p.tok.pos() mut name := if language == .js { p.check_js_name() } else { p.check_name() } mut is_static_type_method := language == .v && name != '' && name[0].is_capital() && p.tok.kind == .dot if is_static_type_method { p.check(.dot) name = name + '__static__' + p.check_name() } mut fn_name := if language == .c { 'C.${name}' } else if language == .js { 'JS.${name}' } else if language == .wasm { 'WASM.${name}' } else if mod != '' { '${mod}.${name}' } else { name } if language != .v { p.check_for_impure_v(language, first_pos) } mut or_kind := ast.OrKind.absent if fn_name == 'json.decode' || fn_name == 'C.va_arg' { p.expecting_type = true // Makes name_expr() parse the type `User` in `json.decode(User, txt)` } old_expr_mod := p.expr_mod defer { p.expr_mod = old_expr_mod } p.expr_mod = '' mut concrete_types := []ast.Type{} mut concrete_list_pos := p.tok.pos() if p.tok.kind == .lsbr { // `foo[int](10)` p.expr_mod = '' concrete_types = p.parse_concrete_types() concrete_list_pos = concrete_list_pos.extend(p.prev_tok.pos()) } p.check(.lpar) args := p.call_args() if p.tok.kind != .rpar && !p.pref.is_vls { mut params := []ast.Param{} fn_info, has_fn_info := table_fn_lookup(p.table, fn_name) if has_fn_info { params = fn_info.params.clone() } else { mod_fn_info, has_mod_fn_info := table_fn_lookup(p.table, '${p.mod}.${fn_name}') if has_mod_fn_info { params = mod_fn_info.params.clone() } } min_required_params := min_required_call_args(params) if args.len < min_required_params && p.prev_tok.kind != .comma { pos := if p.tok.kind == .eof { p.prev_tok.pos() } else { p.tok.pos() } p.unexpected_with_pos(pos, expecting: '`,`') } else if args.len > params.len { ok_arg_pos := if params.len > 0 { args[params.len - 1].pos } else { args[0].pos } pos := token.Pos{ ...ok_arg_pos col: u16(ok_arg_pos.col + ok_arg_pos.len) } p.unexpected_with_pos(pos.extend(p.tok.pos()), expecting: '`)`') } else { pos := if p.tok.kind == .eof { p.prev_tok.pos() } else { p.tok.pos() } p.unexpected_with_pos(pos, expecting: '`)`') } } last_pos := p.tok.pos() if p.tok.kind == .rpar { p.next() } mut pos := first_pos.extend(last_pos) mut or_stmts := []ast.Stmt{} // TODO: remove unnecessary allocations by just using .absent mut or_pos := p.tok.pos() mut or_scope := ast.empty_scope if p.tok.kind == .key_orelse { // `foo() or {}`` or_kind = .block or_stmts, or_pos, or_scope = p.or_block(.with_err_var) } if p.tok.kind in [.question, .not] { is_not := p.tok.kind == .not // `foo()?` p.next() if p.inside_defer { p.error_with_pos('error propagation not allowed inside `defer` blocks', p.prev_tok.pos()) } or_kind = if is_not { .propagate_result } else { .propagate_option } or_scope = p.scope } if p.is_imported_symbol(fn_name) { check := !p.imported_symbols_used[fn_name] fn_name = p.imported_symbols[fn_name] if check { p.register_used_import_for_symbol_name(fn_name) } } comments := p.eat_comments(same_line: true) pos.update_last_line(p.prev_tok.line_nr) return ast.CallExpr{ name: fn_name name_pos: first_pos args: args mod: p.mod kind: p.call_kind(fn_name) pos: pos language: language concrete_types: concrete_types concrete_list_pos: concrete_list_pos raw_concrete_types: concrete_types or_block: ast.OrExpr{ stmts: or_stmts kind: or_kind pos: or_pos scope: or_scope } scope: p.scope comments: comments is_return_used: p.expecting_value is_static_method: is_static_type_method } } fn min_required_call_args(params []ast.Param) int { if params.len == 0 { return 0 } mut required := params.len mut idx := params.len - 1 for idx >= 0 { if params[idx].typ.has_flag(.option) { required-- idx-- continue } break } return required } fn (mut p Parser) call_kind(fn_name string) ast.CallKind { if fn_name.len < 3 || fn_name.len > 20 { return .unknown } return match fn_name.len { 3 { match fn_name { 'str' { .str } 'map' { .map } 'any' { .any } 'all' { .all } 'pop' { .pop } else { .unknown } } } 4 { match fn_name { 'wait' { .wait } 'free' { .free } 'keys' { .keys } 'sort' { .sort } 'trim' { .trim } 'last' { .last } 'drop' { .drop } 'main' { .main } 'move' { .move } else { .unknown } } } 5 { return match fn_name { 'count' { .count } 'print' { .print } 'close' { .close } 'slice' { .slice } 'clone' { .clone } 'index' { .index } 'first' { .first } 'panic' { .panic } 'clear' { .clear } 'error' { .error } else { .unknown } } } 6 { return match fn_name { 'values' { .values } 'eprint' { .eprint } 'sorted' { .sorted } 'filter' { .filter } 'insert' { .insert } 'delete' { .delete } 'repeat' { .repeat } '__addr' { .addr } 'malloc' { .malloc } else { .unknown } } } 7 { return match fn_name { 'prepend' { .prepend } 'writeln' { .writeln } 'println' { .println } 'try_pop' { .try_pop } 'reverse' { .reverse } 'reserve' { .reserve } else { .unknown } } } 8 { return match fn_name { 'try_push' { .try_push } 'eprintln' { .eprintln } 'pointers' { .pointers } 'contains' { .contains } 'pop_left' { .pop_left } 'type_idx' { .type_idx } 'C.va_arg' { .va_arg } 'JS.await' { .jsawait } 'grow_len' { .grow_len } 'grow_cap' { .grow_cap } else { .unknown } } } 9 { return match fn_name { 'type_name' { .type_name } 'main.main' { .main_main } 'push_many' { .push_many } else { .unknown } } } 10 { return match fn_name { 'last_index' { .last_index } else { .unknown } } } 11 { return match fn_name { 'delete_many' { .delete_many } 'delete_last' { .delete_last } 'json.decode' { .json_decode } 'json.encode' { .json_encode } else { .unknown } } } else { return match fn_name { 'sort_with_compare' { .sort_with_compare } 'sorted_with_compare' { .sorted_with_compare } 'reverse_in_place' { .reverse_in_place } 'json.encode_pretty' { .json_encode_pretty } 'clone_to_depth' { .clone_to_depth } else { .unknown } } } } } @[inline] fn (p &Parser) is_start_of_call_arg_expr() bool { if p.tok.kind == .name && p.peek_tok.kind == .string && p.tok.lit !in ['c', 'r', 'js'] { return false } return match p.tok.kind { .name, .number, .string, .chartoken, .dot, .at, .dollar, .amp, .mul, .not, .bit_not, .arrow, .minus, .lpar, .lsbr, .lcbr, .pipe, .logical_or, .question, .key_fn, .key_type, .key_struct, .key_none, .key_nil, .key_true, .key_false, .key_if, .key_match, .key_select, .key_lock, .key_rlock, .key_go, .key_spawn, .key_unsafe, .key_likely, .key_unlikely, .key_typeof, .key_sizeof, .key_isreftype, .key_offsetof, .key_dump, .key_mut, .key_shared, .key_atomic, .key_static, .key_volatile { true } else { false } } } @[inline] fn (p &Parser) can_omit_comma_between_fn_params() bool { return p.tok.line_nr > p.prev_tok.line_nr && p.tok.kind in [.name, .key_mut, .key_shared, .key_atomic, .ellipsis] } fn (mut p Parser) call_args() []ast.CallArg { prev_inside_call_args := p.inside_call_args p.inside_call_args = true defer { p.inside_call_args = prev_inside_call_args } mut args := []ast.CallArg{} for p.tok.kind != .rpar { if p.tok.kind == .eof { return args } is_shared := p.tok.kind == .key_shared is_atomic := p.tok.kind == .key_atomic is_mut := p.tok.kind == .key_mut || is_shared || is_atomic if is_mut { p.next() } mut comments := p.eat_comments() arg_start_pos := p.tok.pos() mut array_decompose := false if p.tok.kind == .ellipsis { p.next() array_decompose = true } mut expr := ast.empty_expr if p.peek_tok.kind == .colon { // `foo(key:val, key2:val2)` expr = p.struct_init('void_type', .short_syntax, false) } else { expr = p.expr(0) if mut expr is ast.Ident { if p.is_imported_symbol(expr.name) && !p.imported_symbols_used[expr.name] { // func call arg is another function call // import term { bright_cyan, colorize } ... colorize(bright_cyan, 'hello') p.register_used_import_for_symbol_name(p.imported_symbols[expr.name]) } } } if array_decompose { expr = ast.ArrayDecompose{ expr: expr pos: p.tok.pos() } } if mut expr is ast.StructInit { expr.pre_comments << comments comments = []ast.Comment{} } pos := arg_start_pos.extend(p.prev_tok.pos()) comments << p.eat_comments() args << ast.CallArg{ is_mut: is_mut share: ast.sharetype_from_flags(is_shared, is_atomic) expr: expr comments: comments pos: pos } if p.tok.kind != .comma { if !p.is_start_of_call_arg_expr() { break } continue } p.next() } return args } struct ReceiverParsingInfo { mut: name string pos token.Pos typ ast.Type type_pos token.Pos is_mut bool language ast.Language } fn (mut p Parser) fn_decl() ast.FnDecl { p.top_level_statement_start() start_pos := p.tok.pos() mut is_manualfree := p.is_manualfree mut is_deprecated := false mut is_direct_arr := false mut is_keep_alive := false mut is_exported := false mut is_unsafe := false mut is_must_use := false mut is_trusted := false mut is_noreturn := false mut is_ctor_new := false mut is_c2v_variadic := false mut is_c_extern := false mut is_markused := false mut is_ignore_overflow := false mut is_weak := false mut is_expand_simple_interpolation := false mut comments := []ast.Comment{} fn_attrs := p.attrs p.attrs = [] for fna in fn_attrs { match fna.name { 'noreturn' { is_noreturn = true } 'manualfree' { is_manualfree = true } 'deprecated' { is_deprecated = true } 'direct_array_access' { if !p.pref.force_bounds_checking { is_direct_arr = true } } 'keep_args_alive' { is_keep_alive = true } 'export' { is_exported = true } 'wasm_export' { is_exported = true } 'unsafe' { is_unsafe = true } 'must_use' { is_must_use = true } 'trusted' { is_trusted = true } 'c2v_variadic' { is_c2v_variadic = true } 'weak' { is_weak = true } 'use_new' { is_ctor_new = true } 'markused' { is_markused = true } 'ignore_overflow' { is_ignore_overflow = true } 'c_extern' { is_c_extern = true } 'windows_stdcall' { p.note_with_pos('the tag [windows_stdcall] has been deprecated, it will be an error after 2022-06-01, use `[callconv: stdcall]` instead', p.tok.pos()) } '_fastcall' { p.note_with_pos('the tag [_fastcall] has been deprecated, it will be an error after 2022-06-01, use `[callconv: fastcall]` instead', p.tok.pos()) } 'callconv' { if !fna.has_arg { p.error_with_pos('callconv attribute is present but its value is missing', p.prev_tok.pos()) } if fna.arg !in ['stdcall', 'fastcall', 'cdecl'] { p.error_with_pos('unsupported calling convention, supported are stdcall, fastcall and cdecl', p.prev_tok.pos()) } } 'expand_simple_interpolation' { is_expand_simple_interpolation = true } else {} } } conditional_ctdefine_idx := comptime_define_idx(fn_attrs) is_pub := p.tok.kind == .key_pub if is_pub { p.next() } p.check(.key_fn) mut comments_before_key_fn := if p.pref.is_vls { p.cur_comments.clone() } else { [] } comments << p.eat_comments() p.open_scope() defer { p.close_scope() } language_tok_pos := p.tok.pos() mut language := p.parse_language() p.fn_language = language if language != .v { for fna in fn_attrs { if fna.name == 'export' { p.error_with_pos('interop function cannot be exported', fna.pos) break } } } if is_keep_alive && language != .c { p.error_with_pos('attribute [keep_args_alive] is only supported for C functions', language_tok_pos) } if language != .v { p.check_for_impure_v(language, language_tok_pos) if language == .c { is_unsafe = !is_trusted } } // Receiver? mut rec := ReceiverParsingInfo{ typ: ast.void_type language: language } mut is_method := false mut is_static_type_method := false mut params := []ast.Param{} if p.tok.kind == .lpar { is_method = true p.fn_receiver(mut params, mut rec) or { return ast.FnDecl{ scope: unsafe { nil } } } // rec.language was initialized with language variable. // So language is changed only if rec.language has been changed. language = rec.language p.fn_language = language } mut name := '' mut type_sym := p.table.sym(rec.typ) mut name_pos := p.tok.pos() mut static_type_pos := p.tok.pos() if p.tok.kind == .name || is_ident_name(p.tok.lit) { mut check_name := '' // TODO: high order fn is_static_type_method = p.tok.lit.len > 0 && p.tok.lit[0].is_capital() && p.peek_tok.kind == .dot && language == .v // `fn Foo.bar() {}` if is_static_type_method { type_name := p.tok.lit // "Foo" static_type_pos = p.tok.pos() rec.typ = p.parse_type() p.check(.dot) check_name = p.check_name() name = type_name + '__static__' + check_name // "foo__bar" name_pos = name_pos.extend(p.prev_tok.pos()) } else { check_name = if language == .js { p.check_js_name() } else { p.check_name() } name = check_name } if language == .v && !p.pref.translated && !p.is_translated && !p.builtin_mod && util.contains_capital(check_name) { p.error_with_pos('function names cannot contain uppercase letters, use snake_case instead', name_pos) return ast.FnDecl{ scope: unsafe { nil } } } if is_method { mut is_duplicate := type_sym.has_method(name) // make sure this is a normal method and not an interface method if type_sym.kind == .interface && is_duplicate { if mut type_sym.info is ast.Interface { // if the method is in info then its an interface method is_duplicate = !type_sym.info.has_method(name) } } // when formatting, methods in mutually exclusive $if/$else branches // may appear as duplicates since all branches are parsed if is_duplicate && !p.pref.is_fmt { if type_sym.kind == .enum && name in ['is_empty', 'has', 'all', 'set', 'set_all', 'clear', 'clear_all', 'toggle', 'zero', 'from'] { name_pos = type_method_name_pos(type_sym, name, name_pos) p.error_with_pos('duplicate method `${name}`, `${name}` is an enum type built-in method', name_pos) } else { p.error_with_pos('duplicate method `${name}`', name_pos) } return ast.FnDecl{ scope: unsafe { nil } } } } if !p.pref.is_fmt { if p.is_imported_symbol(name) { p.error_with_pos('cannot redefine imported function `${name}`', name_pos) return ast.FnDecl{ scope: unsafe { nil } } } } } else if p.tok.kind in [.plus, .minus, .mul, .power, .div, .mod, .lt, .eq] && p.peek_tok.kind == .lpar { name = p.tok.kind.str() // op_to_fn_name() if rec.typ == ast.void_type { p.error_with_pos('cannot use operator overloading with normal functions', p.tok.pos()) } if type_sym.has_method(name) { p.error_with_pos('cannot duplicate operator overload `${name}`', p.tok.pos()) } p.next() } else if p.tok.kind == .lsbr && p.peek_tok.kind == .rsbr && p.peek_token(2).kind == .lpar { name = '[]' if rec.typ == ast.void_type { p.error_with_pos('cannot use operator overloading with normal functions', p.tok.pos()) } if type_sym.has_method(name) { p.error_with_pos('cannot duplicate operator overload `${name}`', p.tok.pos()) } p.next() p.next() } else if p.tok.kind == .lsbr && p.peek_tok.kind == .rsbr && p.peek_token(2).kind == .assign && p.peek_token(3).kind == .lpar { name = '[]=' if rec.typ == ast.void_type { p.error_with_pos('cannot use operator overloading with normal functions', p.tok.pos()) } if type_sym.has_method(name) { p.error_with_pos('cannot duplicate operator overload `${name}`', p.tok.pos()) } p.next() p.next() p.next() } else if p.tok.kind in [.ne, .gt, .ge, .le] && p.peek_tok.kind == .lpar { p.error_with_pos('cannot overload `!=`, `>`, `<=` and `>=` as they are auto generated from `==` and`<`', p.tok.pos()) } else if p.tok.kind in [.plus_assign, .minus_assign, .div_assign, .mult_assign, .power_assign, .mod_assign] { extracted_op := match p.tok.kind { .plus_assign { '+' } .minus_assign { '-' } .div_assign { '/' } .mod_assign { '%' } .mult_assign { '*' } .power_assign { '**' } else { 'unknown op' } } if type_sym.has_method(extracted_op) { p.error('cannot overload `${p.tok.kind}`, operator is implicitly overloaded because the `${extracted_op}` operator is overloaded') } p.error('cannot overload `${p.tok.kind}`, overload `${extracted_op}` and `${p.tok.kind}` will be automatically generated') } else { p.error_with_pos('expecting method name', p.tok.pos()) return ast.FnDecl{ scope: unsafe { nil } } } // [T] _, mut generic_names := p.parse_generic_types() // generic names can be infer with receiver's generic names if is_method && rec.typ.has_flag(.generic) { sym := p.table.sym(rec.typ) mut rec_generic_types := []ast.Type{} match sym.info { ast.Struct { rec_generic_types = sym.info.generic_types.clone() } ast.Interface { rec_generic_types = sym.info.generic_types.clone() } else {} } if rec_generic_types.len > 0 { decl_generic_names := p.types_to_names(rec_generic_types, p.tok.pos(), 'rec_generic_types') or { return ast.FnDecl{ scope: unsafe { nil } } } fn_generic_names := generic_names.clone() generic_names = p.table.generic_type_names(rec.typ) if decl_generic_names.len != generic_names.len { plural := if decl_generic_names.len == 1 { '' } else { 's' } p.error_with_pos('expected ${decl_generic_names.len} generic parameter${plural}, got ${generic_names.len}', rec.type_pos) } for gname in fn_generic_names { if gname !in generic_names { generic_names << gname } } } } if generic_names.len > 0 { for fna in fn_attrs { if fna.name == 'export' { p.error_with_pos('generic functions cannot be exported', fna.pos) break } } } // Params params_t, are_params_type_only, mut is_variadic, mut is_c_variadic := p.fn_params() if is_c2v_variadic { is_variadic = true is_c_variadic = true } params << params_t // Return type mut return_type_pos := p.tok.pos() mut return_type := ast.void_type // don't confuse token on the next line: fn decl, [attribute] same_line := p.tok.line_nr == p.prev_tok.line_nr // `fn foo()[` is usually a mistaken body opener; report it as such // instead of trying to parse `[` as a fixed array return type. invalid_body_opener := same_line && p.tok.kind == .lsbr && p.peek_tok.line_nr > p.tok.line_nr if invalid_body_opener { p.unexpected(got: '${p.tok} after function signature', expecting: '`{`') } else if (p.tok.kind.is_start_of_type() && (same_line || p.tok.kind != .lsbr)) || (same_line && p.tok.kind == .key_fn) { // Disallow [T] as return type if p.tok.kind == .lsbr && p.peek_tok.kind == .name && p.peek_tok.lit.len == 1 && p.peek_tok.lit[0].is_capital() { return_type_pos = return_type_pos.extend(p.peek_tok.pos()).extend(p.peek_token(2).pos()) p.error_with_pos('invalid generic return, use `${p.peek_tok.lit}` instead', return_type_pos) } p.inside_fn_return = true return_type = p.parse_type() p.inside_fn_return = false return_type_pos = return_type_pos.extend(p.prev_tok.pos()) if p.tok.kind in [.question, .not] { ret_type_sym := p.table.sym(return_type) p.error_with_pos('wrong syntax, it must be ${p.tok.kind}${ret_type_sym.name}, not ${ret_type_sym.name}${p.tok.kind}', return_type_pos) } } if p.tok.kind == .comma { mr_pos := return_type_pos.extend(p.peek_tok.pos()) p.error_with_pos('multiple return types in function declaration must use parentheses, e.g. (int, string)', mr_pos) } mut type_sym_method_idx := 0 no_body := p.tok.kind != .lcbr end_pos := p.prev_tok.pos() short_fn_name := name is_main := short_fn_name == 'main' && p.mod == 'main' if is_main { p.main_already_defined = true } is_test := (!is_method && params.len == 0) && p.inside_test_file && (short_fn_name.starts_with('test_') || short_fn_name.starts_with('testsuite_') || short_fn_name in ['before_each', 'after_each']) file_mode := p.file_backend_mode if is_main { if 'main.main' in p.table.fns { if '.' in os.args { p.error_with_pos('multiple `main` functions detected, and you ran `v .` perhaps there are multiple V programs in this directory, and you need to run them via `v file.v` instead', name_pos) } } } if is_method && is_static_type_method { p.error_with_pos('cannot declare a static function as a receiver method', name_pos) } // Register if !are_params_type_only { for k, param in params { if p.scope.known_var(param.name) { p.error_with_pos('redefinition of parameter `${param.name}`', param.pos) return ast.FnDecl{ scope: unsafe { nil } } } effective_is_mut := if is_method && k == 0 { param.is_mut } else { p.scope_var_is_mut(param.is_mut) } is_stack_obj := !param.typ.has_flag(.shared_f) && (param.is_mut || param.typ.is_ptr()) p.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: effective_is_mut is_auto_deref: param.is_mut is_stack_obj: is_stack_obj pos: param.pos is_used: is_pub || no_body || (is_method && k == 0) || p.builtin_mod is_arg: true ct_type_var: if (!is_method || k >= 0) && param.typ.has_flag(.generic) && !param.typ.has_flag(.variadic) { .generic_param } else { .no_comptime } }) } } if is_method { // Do not allow to modify / add methods to types from other modules // arrays/maps dont belong to a module only their element types do // we could also check if kind is .array, .array_fixed, .map instead of mod.len mut is_non_local := type_sym.mod.len > 0 && type_sym.mod != p.mod && type_sym.language == .v // check maps & arrays, must be defined in same module as the elem type if !is_non_local && !(p.builtin_mod && p.pref.is_fmt) && type_sym.kind in [.array, .map] { elem_type_sym := p.table.sym(p.table.value_type(rec.typ)) is_non_local = elem_type_sym.mod.len > 0 && elem_type_sym.mod != p.mod && elem_type_sym.language == .v } if is_non_local && !(p.file_backend_mode == .js && type_sym.mod.starts_with('Promise')) { p.error_with_pos('cannot define new methods on non-local type ${type_sym.name}. Define an alias and use that instead like `type AliasName = ${type_sym.name}` ', rec.type_pos) return ast.FnDecl{ scope: unsafe { nil } } } type_sym_method_idx = type_sym.register_method(ast.Fn{ name: name file_mode: file_mode params: params return_type: return_type is_variadic: is_variadic generic_names: generic_names is_pub: is_pub is_deprecated: is_deprecated is_noreturn: is_noreturn is_unsafe: is_unsafe is_must_use: is_must_use is_main: is_main is_test: is_test is_keep_alive: is_keep_alive is_method: true receiver_type: rec.typ // attrs: fn_attrs is_conditional: conditional_ctdefine_idx != ast.invalid_type_idx ctdefine_idx: conditional_ctdefine_idx // no_body: no_body mod: p.mod file: p.file_path pos: start_pos name_pos: name_pos language: language // is_expand_simple_interpolation: is_expand_simple_interpolation }) } else { name = match language { .c { 'C.${name}' } .js { 'JS.${name}' } .wasm { 'WASM.${name}' } else { p.prepend_mod(name) } } if language == .v { existing, has_existing := table_fn_lookup(p.table, name) if has_existing { if existing.name != '' { if file_mode == .v && existing.file_mode != .v { // a definition made in a .c.v file, should have a priority over a .v file definition of the same function if !p.pref.is_fmt { name = p.prepend_mod('pure_v_but_overridden_by_${existing.file_mode}_${short_fn_name}') } } else if !p.pref.translated { p.table.redefined_fns << name } } } } p.table.register_fn(ast.Fn{ name: name file_mode: file_mode params: params return_type: return_type is_variadic: is_variadic is_c_variadic: is_c_variadic generic_names: generic_names is_pub: is_pub is_deprecated: is_deprecated is_noreturn: is_noreturn is_ctor_new: is_ctor_new is_unsafe: is_unsafe is_must_use: is_must_use is_main: is_main is_test: is_test is_keep_alive: is_keep_alive is_method: false is_static_type_method: is_static_type_method receiver_type: if is_static_type_method { rec.typ } else { 0 } // used only if is static type method is_file_translated: p.is_translated // attrs: fn_attrs is_conditional: conditional_ctdefine_idx != ast.invalid_type_idx ctdefine_idx: conditional_ctdefine_idx // no_body: no_body mod: p.mod file: p.file_path pos: start_pos name_pos: name_pos language: language // is_expand_simple_interpolation: is_expand_simple_interpolation }) } /* // Register implicit context var p.scope.register(ast.Var{ name: 'ctx' typ: ast.error_type pos: p.tok.pos() is_used: true is_stack_obj: true }) */ // Body keep_fn_name := p.cur_fn_name p.cur_fn_name = name mut stmts := []ast.Stmt{} body_start_pos := p.tok.pos() if p.tok.kind == .lcbr { if language != .v && !(language == .js && type_sym.info is ast.Interface) { p.error_with_pos('interop functions cannot have a body', body_start_pos) } last_fn_scope := p.scope p.inside_fn = true p.inside_unsafe_fn = is_unsafe p.cur_fn_scope = p.scope if p.is_vls_skip_file { p.skip_scope() } else { stmts = p.parse_block_no_scope(true) } p.cur_fn_scope = last_fn_scope p.inside_unsafe_fn = false p.inside_fn = false } p.cur_fn_name = keep_fn_name if !no_body && are_params_type_only { p.error_with_pos('functions with type only params can not have bodies', body_start_pos) return ast.FnDecl{ scope: unsafe { nil } } } // if no_body && !name.starts_with('C.') { // p.error_with_pos('did you mean C.${name} instead of ${name}', start_pos) // } fn_decl := ast.FnDecl{ name: name short_name: short_fn_name mod: p.mod kind: p.call_kind(name) stmts: stmts return_type: return_type return_type_pos: return_type_pos params: params is_noreturn: is_noreturn is_manualfree: is_manualfree is_deprecated: is_deprecated is_exported: is_exported is_direct_arr: is_direct_arr is_pub: is_pub is_variadic: is_variadic is_c_variadic: is_c_variadic is_c_extern: is_c_extern is_main: is_main is_test: is_test is_keep_alive: is_keep_alive is_unsafe: is_unsafe is_must_use: is_must_use is_markused: is_markused is_ignore_overflow: is_ignore_overflow is_weak: is_weak is_file_translated: p.is_translated // attrs: fn_attrs is_conditional: conditional_ctdefine_idx != ast.invalid_type_idx ctdefine_idx: conditional_ctdefine_idx // receiver: ast.StructField{ name: rec.name typ: rec.typ type_pos: rec.type_pos pos: rec.pos } generic_names: generic_names receiver_pos: rec.pos is_method: is_method is_static_type_method: is_static_type_method static_type_pos: static_type_pos method_type_pos: rec.type_pos method_idx: type_sym_method_idx rec_mut: rec.is_mut language: language no_body: no_body pos: start_pos.extend_with_last_line(end_pos, p.prev_tok.line_nr) end_pos: p.tok.pos() name_pos: name_pos body_pos: body_start_pos file: p.file_path is_builtin: p.builtin_mod || p.mod in util.builtin_module_parts scope: p.scope label_names: p.label_names end_comments: p.eat_comments(same_line: true) comments: comments // is_expand_simple_interpolation: is_expand_simple_interpolation } if generic_names.len > 0 { p.table.register_fn_generic_types(fn_decl.fkey()) } p.label_names = [] if p.pref.is_vls { type_str := if (is_method || is_static_type_method) && rec.typ != ast.no_type { p.table.sym(rec.typ.idx_type()).name.all_after_last('.') } else { '' } key := 'fn_${p.mod}[${type_str}]${short_fn_name}' val := ast.VlsInfo{ pos: fn_decl.pos doc: p.keyword_comments_to_string(short_fn_name, comments_before_key_fn) + p.comments_to_string(fn_decl.end_comments) } p.table.register_vls_info(key, val) } return fn_decl } fn (mut p Parser) fn_receiver(mut params []ast.Param, mut rec ReceiverParsingInfo) ! { p.inside_receiver_param = true defer { p.inside_receiver_param = false } lpar_pos := p.tok.pos() p.next() // ( is_shared := p.tok.kind == .key_shared is_atomic := p.tok.kind == .key_atomic rec.is_mut = p.tok.kind == .key_mut || is_shared || is_atomic if rec.is_mut { p.next() // `mut` } if is_shared { p.register_auto_import('sync') } rec_start_pos := p.tok.pos() rec.name = p.check_name() if !rec.is_mut { rec.is_mut = p.tok.kind == .key_mut if rec.is_mut { ptoken2 := p.peek_token(2) // needed to prevent codegen bug, where .pos() expects &Token p.warn_with_pos('use `(mut f Foo)` instead of `(f mut Foo)`', lpar_pos.extend(ptoken2.pos())) } } if p.tok.kind == .key_shared { ptoken2 := p.peek_token(2) // needed to prevent codegen bug, where .pos() expects &Token p.error_with_pos('use `(shared f Foo)` instead of `(f shared Foo)`', lpar_pos.extend(ptoken2.pos())) } rec.pos = rec_start_pos.extend(p.tok.pos()) is_amp := p.tok.kind == .amp if p.tok.kind == .name && p.tok.lit == 'JS' { rec.language = ast.Language.js } // if rec.is_mut { // p.check(.key_mut) // } // TODO: talk to alex, should mut be parsed with the type like this? // or should it be a property of the arg, like this ptr/mut becomes indistinguishable rec.type_pos = p.tok.pos() rec.typ = p.parse_type_with_mut(rec.is_mut) if rec.typ.idx() == 0 { // error is set in parse_type return error('void receiver type') } rec.type_pos = rec.type_pos.extend(p.prev_tok.pos()) if is_amp && rec.is_mut { p.error_with_pos('use `(mut f Foo)` or `(f &Foo)` instead of `(mut f &Foo)`', lpar_pos.extend(p.tok.pos())) return error('invalid `mut f &Foo`') } if is_shared { rec.typ = rec.typ.set_flag(.shared_f) } if is_atomic { rec.typ = rec.typ.set_flag(.atomic_f) } if rec.language != .v { p.check_for_impure_v(rec.language, rec.type_pos) } p.check(.rpar) params << ast.Param{ pos: rec_start_pos name: rec.name is_mut: rec.is_mut is_atomic: is_atomic is_shared: is_shared typ: rec.typ type_pos: rec.type_pos } } fn (mut p Parser) anon_fn() ast.AnonFn { pos := p.tok.pos() p.check(.key_fn) if p.tok.kind == .name { if p.disallow_declarations_in_script_mode() { return ast.AnonFn{} } } old_inside_defer := p.inside_defer p.inside_defer = false p.open_scope() defer { p.close_scope() } p.scope.detached_from_parent = true inherited_vars := if p.tok.kind == .lsbr && !(p.peek_tok.kind == .name && p.peek_tok.lit.len == 1 && p.peek_tok.lit[0].is_capital()) { p.closure_vars() } else { []ast.Param{} } inherited_vars_name := inherited_vars.map(it.name) _, generic_names := p.parse_generic_types() params, _, is_variadic, _ := p.fn_params() for param in params { if param.name == '' && p.table.sym(param.typ).kind != .placeholder { p.error_with_pos('use `_` to name an unused parameter', param.pos) } if param.name in inherited_vars_name { p.error_with_pos('the parameter name `${param.name}` conflicts with the captured value name', param.pos) } else if p.scope.known_var(param.name) { p.error_with_pos('redefinition of parameter `${param.name}`', param.pos) } is_stack_obj := !param.typ.has_flag(.shared_f) && (param.is_mut || param.typ.is_ptr()) p.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: p.scope_var_is_mut(param.is_mut) is_auto_deref: param.is_mut pos: param.pos is_used: true is_arg: true is_stack_obj: is_stack_obj }) } mut same_line := p.tok.line_nr == p.prev_tok.line_nr mut return_type := ast.void_type mut return_type_pos := p.tok.pos() // lpar: multiple return types if same_line { if (p.tok.kind.is_start_of_type() && (same_line || p.tok.kind != .lsbr)) || (same_line && p.tok.kind == .key_fn) { p.inside_fn_return = true return_type = p.parse_type() p.inside_fn_return = false return_type_pos = return_type_pos.extend(p.tok.pos()) } else if p.tok.kind != .lcbr { p.error_with_pos('expected return type, not ${p.tok} for anonymous function', p.tok.pos()) } } mut stmts := []ast.Stmt{} no_body := p.tok.kind != .lcbr same_line = p.tok.line_nr == p.prev_tok.line_nr if no_body && same_line { p.unexpected(got: '${p.tok} after anonymous function signature', expecting: '`{`') } mut label_names := []string{} mut func := ast.Fn{ params: params is_variadic: is_variadic return_type: return_type is_method: false } name := p.table.get_anon_fn_name(p.unique_prefix, func, p.tok.pos()) keep_fn_name := p.cur_fn_name p.cur_fn_name = name if p.tok.kind == .lcbr { tmp := p.label_names p.label_names = [] old_assign_rhs := p.inside_assign_rhs p.inside_assign_rhs = false stmts = p.parse_block_no_scope(false) p.inside_assign_rhs = old_assign_rhs label_names = p.label_names.clone() p.label_names = tmp } p.cur_fn_name = keep_fn_name func.name = name idx := p.table.find_or_register_fn_type(func, true, false) typ := if generic_names.len > 0 { ast.new_type(idx).set_flag(.generic) } else { ast.new_type(idx) } p.inside_defer = old_inside_defer // name := p.table.get_type_name(typ) return ast.AnonFn{ decl: ast.FnDecl{ name: name short_name: '' mod: p.mod stmts: stmts return_type: return_type return_type_pos: return_type_pos params: params is_variadic: is_variadic is_closure: inherited_vars.len > 0 is_method: false generic_names: generic_names is_anon: true no_body: no_body pos: pos.extend(p.prev_tok.pos()) file: p.file_path scope: p.scope label_names: label_names } inherited_vars: inherited_vars typ: typ } } // part of fn declaration // returns: params, are_params_type_only, mut is_variadic, mut is_c_variadic fn (mut p Parser) fn_params() ([]ast.Param, bool, bool, bool) { p.check(.lpar) mut params := []ast.Param{} mut is_variadic := false mut is_c_variadic := false // `int, int, string` (no names, just types) param_name := if p.tok.kind == .name && p.tok.lit.len > 0 && p.tok.lit[0].is_capital() { p.prepend_mod(p.tok.lit) } else { p.tok.lit } is_generic_type := p.tok.kind == .name && p.tok.lit.len == 1 && p.tok.lit[0].is_capital() types_only := p.tok.kind in [.question, .not, .amp, .ellipsis, .key_fn, .lsbr] || (p.peek_tok.kind == .comma && (p.table.known_type(param_name) || is_generic_type)) || p.peek_tok.kind == .dot || p.peek_tok.kind == .rpar || p.fn_language == .c || (p.tok.kind == .key_mut && (p.peek_tok.kind in [.amp, .ellipsis, .key_fn, .lsbr] || (p.peek_token(2).kind == .comma && (p.tok.kind != .key_mut || p.peek_tok.lit[0].is_capital())) || p.peek_token(2).kind == .rpar || (p.peek_tok.kind == .name && p.peek_token(2).kind == .dot))) mut prev_param_newline := p.tok.pos().line_nr // TODO: copy paste, merge 2 branches if types_only { mut param_no := 1 for p.tok.kind != .rpar { if p.tok.kind == .eof { p.error_with_pos('expecting `)`', p.tok.pos()) return []ast.Param{}, false, false, false } is_shared := p.tok.kind == .key_shared is_atomic := p.tok.kind == .key_atomic is_mut := p.tok.kind == .key_mut || is_shared || is_atomic mut name := '' if is_mut { p.next() } if p.fn_language == .c && (is_ident_name(p.tok.lit) || (p.tok.lit.len > 1 && p.tok.lit[0] == `@` && is_ident_name(p.tok.lit[1..]))) && p.tok.kind !in [.key_fn, .key_struct] && p.peek_tok.kind !in [.comma, .rpar, .dot] { name = p.tok.lit p.next() } if p.tok.kind == .ellipsis { p.next() is_variadic = true is_c_variadic = p.tok.kind == .rpar if is_c_variadic { p.check(.rpar) return params, types_only, is_variadic, is_c_variadic } } pos := p.tok.pos() prev_inside_fn_param := p.inside_fn_param p.inside_fn_param = true mut param_type := p.parse_type() p.inside_fn_param = prev_inside_fn_param orig_param_type := param_type type_pos := pos.extend(p.prev_tok.pos()) if param_type == 0 { // error is added in parse_type return []ast.Param{}, false, false, false } if param_type == ast.chan_type { p.chan_type_error() return []ast.Param{}, false, false, false } if is_mut { if !param_type.has_flag(.generic) { if is_variadic { p.error_with_pos('variadic arguments cannot be `mut`, `shared` or `atomic`', pos) } if is_shared { p.check_fn_shared_arguments(param_type, pos) } else if is_atomic { p.check_fn_atomic_arguments(param_type, pos) } else { p.check_fn_mutable_arguments(param_type, pos) } } else if is_shared || is_atomic { p.error_with_pos('generic object cannot be `atomic`or `shared`', pos) return []ast.Param{}, false, false, false } if param_type.is_ptr() && p.table.sym(param_type).kind == .struct { param_type = param_type.ref() } else { param_type = param_type.set_nr_muls(1) } if param_type.has_flag(.option) { param_type = param_type.set_flag(.option_mut_param_t) } if is_shared { param_type = param_type.set_flag(.shared_f) } if is_atomic { param_type = param_type.set_flag(.atomic_f) } } if is_variadic { param_type = ast.new_type(p.table.find_or_register_array(param_type)).set_flag(.variadic) } if p.tok.kind == .eof { p.error_with_pos('expecting `)`', p.prev_tok.pos()) return []ast.Param{}, false, false, false } if p.tok.kind == .comma { if is_variadic { p.error_with_pos('cannot use ...(variadic) with non-final parameter no ${param_no}', pos) return []ast.Param{}, false, false, false } p.next() } alanguage := p.table.sym(param_type).language if alanguage != .v { p.check_for_impure_v(alanguage, pos) } params << ast.Param{ pos: pos name: name is_mut: is_mut orig_typ: orig_param_type typ: param_type type_pos: type_pos on_newline: prev_param_newline != pos.line_nr } prev_param_newline = pos.line_nr param_no++ if param_no > 1024 { p.error_with_pos('too many parameters', pos) return []ast.Param{}, false, false, false } } } else { for p.tok.kind != .rpar { if p.tok.kind == .eof { p.error_with_pos('expecting `)`', p.tok.pos()) return []ast.Param{}, false, false, false } is_shared := p.tok.kind == .key_shared is_atomic := p.tok.kind == .key_atomic mut is_mut := p.tok.kind == .key_mut || is_shared || is_atomic if is_mut { p.next() } if p.tok.kind == .ellipsis && p.peek_tok.kind == .rpar { p.check(.ellipsis) p.check(.rpar) return params, types_only, true, true } mut param_pos := [p.tok.pos()] name := p.check_name() mut param_names := [name] if name != '' && p.fn_language == .v && name[0].is_capital() { p.error_with_pos('parameter name must not begin with upper case letter (`${param_names[0]}`)', p.prev_tok.pos()) } mut type_pos := [p.tok.pos()] // `a, b, c int` for p.tok.kind == .comma { if !p.pref.is_fmt { p.error('`fn f(x, y Type)` syntax has been deprecated. ' + 'Use `fn f(x Type, y Type)` instead. You can run `v fmt -w "${p.scanner.file_path}"` to automatically fix your code.') } p.next() param_pos << p.tok.pos() param_names << p.check_name() type_pos << p.tok.pos() } if p.tok.kind == .key_mut { // TODO: remove old syntax if !p.pref.is_fmt { p.warn_with_pos('use `mut f Foo` instead of `f mut Foo`', p.tok.pos()) } is_mut = true } if p.tok.kind == .key_shared { p.error_with_pos('use `shared f Foo` instead of `f shared Foo`', p.tok.pos()) } if p.tok.kind == .ellipsis { p.next() is_variadic = true is_c_variadic = p.tok.kind == .rpar if is_c_variadic { p.check(.rpar) return params, types_only, is_variadic, is_c_variadic } } pos := p.tok.pos() prev_inside_fn_param := p.inside_fn_param p.inside_fn_param = true mut typ := p.parse_type() p.inside_fn_param = prev_inside_fn_param orig_typ := typ type_pos[0] = pos.extend(p.prev_tok.pos()) if typ == 0 { // error is added in parse_type return []ast.Param{}, false, false, false } if typ == ast.chan_type { p.chan_type_error() return []ast.Param{}, false, false, false } if is_mut { if !typ.has_flag(.generic) { if is_variadic { p.error_with_pos('variadic arguments cannot be `mut`, `shared` or `atomic`', pos) } if is_shared { p.check_fn_shared_arguments(typ, pos) } else if is_atomic { p.check_fn_atomic_arguments(typ, pos) } else { p.check_fn_mutable_arguments(typ, pos) } } else if is_shared || is_atomic { p.error_with_pos('generic object cannot be `atomic` or `shared`', pos) return []ast.Param{}, false, false, false } if typ.is_ptr() && p.table.sym(typ).kind == .struct { typ = typ.ref() } else { typ = typ.set_nr_muls(1) } if typ.has_flag(.option) { typ = typ.set_flag(.option_mut_param_t) } if is_shared { typ = typ.set_flag(.shared_f) } if is_atomic { typ = typ.set_flag(.atomic_f) } } if is_variadic { // derive flags, however nr_muls only needs to be set on the array elem type, so clear it on the arg type typ = ast.new_type(p.table.find_or_register_array(typ)).derive(typ).set_nr_muls(0).set_flag(.variadic) } for i, para_name in param_names { alanguage := p.table.sym(typ).language if alanguage != .v { p.check_for_impure_v(alanguage, type_pos[i]) } can_omit_comma := p.can_omit_comma_between_fn_params() params << ast.Param{ pos: param_pos[i] name: para_name is_mut: is_mut is_atomic: is_atomic is_shared: is_shared orig_typ: orig_typ typ: typ type_pos: type_pos[i] on_newline: prev_param_newline != param_pos[i].line_nr } prev_param_newline = param_pos[i].line_nr if is_variadic && ((p.tok.kind == .comma && p.peek_tok.kind != .rpar) || can_omit_comma) { p.error_with_pos('cannot use ...(variadic) with non-final parameter ${para_name}', param_pos[i]) return []ast.Param{}, false, false, false } } if p.tok.kind == .eof { p.error_with_pos('expecting `)`', p.prev_tok.pos()) return []ast.Param{}, false, false, false } if p.tok.kind == .comma { p.next() } else if p.tok.kind != .rpar && !p.can_omit_comma_between_fn_params() { p.check(.comma) } } } p.check(.rpar) return params, types_only, is_variadic, is_c_variadic } fn (mut p Parser) spawn_expr() ast.SpawnExpr { p.next() spos := p.tok.pos() old_inside_assign_rhs := p.inside_assign_rhs p.inside_assign_rhs = false expr := p.expr(0) p.inside_assign_rhs = old_inside_assign_rhs mut call_expr := if expr is ast.CallExpr { expr } else { p.error_with_pos('expression in `spawn` must be a function call', expr.pos()) ast.CallExpr{ scope: p.scope } } call_expr.is_return_used = true pos := spos.extend(p.prev_tok.pos()) p.register_auto_import('sync.threads') p.table.gostmts++ return ast.SpawnExpr{ call_expr: call_expr pos: pos } } fn (mut p Parser) go_expr() ast.GoExpr { p.next() spos := p.tok.pos() old_inside_assign_rhs := p.inside_assign_rhs p.inside_assign_rhs = false expr := p.expr(0) p.inside_assign_rhs = old_inside_assign_rhs mut call_expr := if expr is ast.CallExpr { expr } else { p.error_with_pos('expression in `go` must be a function call', expr.pos()) ast.CallExpr{ scope: p.scope } } call_expr.is_return_used = true pos := spos.extend(p.prev_tok.pos()) // p.register_auto_import('coroutines') p.table.gostmts++ return ast.GoExpr{ call_expr: call_expr pos: pos } } fn (mut p Parser) closure_vars() []ast.Param { p.check(.lsbr) mut vars := []ast.Param{cap: 5} for { is_shared := p.tok.kind == .key_shared is_atomic := p.tok.kind == .key_atomic is_mut := p.tok.kind == .key_mut || is_shared || is_atomic // FIXME: is_shared & is_atomic aren't used further if is_mut { p.next() } var_pos := p.tok.pos() p.check(.name) var_name := p.prev_tok.lit mut var := p.scope.parent.find_var(var_name) or { if p.table.global_scope.known_global(var_name) { p.error_with_pos('no need to capture global variable `${var_name}` in closure', p.prev_tok.pos()) return [] } p.error_with_pos('undefined ident: `${var_name}`', p.prev_tok.pos()) return [] } var.is_used = true if is_mut { var.is_changed = true } p.scope.register(ast.Var{ ...(*var) pos: var_pos is_inherited: true has_inherited: var.is_inherited is_used: false is_changed: false is_mut: p.scope_var_is_mut(is_mut) }) vars << ast.Param{ pos: var_pos name: var_name is_mut: p.scope_var_is_mut(is_mut) is_atomic: is_atomic is_shared: is_shared } if p.tok.kind != .comma { break } p.next() } p.check(.rsbr) return vars } fn (mut p Parser) check_fn_mutable_arguments(typ ast.Type, pos token.Pos) { sym := p.table.sym(typ) if sym.kind in [.array, .array_fixed, .interface, .map, .placeholder, .struct, .generic_inst, .sum_type] { return } if typ.is_any_kind_of_pointer() { return } if sym.kind == .alias { atyp := (sym.info as ast.Alias).parent_type p.check_fn_mutable_arguments(atyp, pos) return } if p.fn_language == .c { return } p.error_with_pos( 'mutable arguments are only allowed for arrays, interfaces, maps, pointers, structs or their aliases\n' + 'return values instead: `fn foo(mut n ${sym.name}) {` => `fn foo(n ${sym.name}) ${sym.name} {`', pos) } fn (mut p Parser) check_fn_shared_arguments(typ ast.Type, pos token.Pos) { mut sym := p.table.sym(typ) if sym.kind == .generic_inst { sym = p.table.type_symbols[(sym.info as ast.GenericInst).parent_idx] } if sym.kind == .function { p.error_with_pos('shared arguments are not allowed for function types', pos) } } fn (mut p Parser) check_fn_atomic_arguments(typ ast.Type, pos token.Pos) { sym := p.table.sym(typ) if sym.kind !in [.u32, .int, .u64] { p.error_with_pos('atomic arguments are only allowed for 32/64 bit integers\n' + 'use shared arguments instead: `fn foo(atomic n ${sym.name}) {` => `fn foo(shared n ${sym.name}) {`', pos) } } fn have_fn_main(stmts []ast.Stmt) bool { for stmt in stmts { if stmt is ast.FnDecl { if stmt.name == 'main.main' { return true } } } return false }