diff --git a/cmd/tools/check_os_api_parity.v b/cmd/tools/check_os_api_parity.v index 32e597e2a0..af3de574a3 100644 --- a/cmd/tools/check_os_api_parity.v +++ b/cmd/tools/check_os_api_parity.v @@ -105,7 +105,7 @@ fn (app App) gen_api_for_module_in_os(mod_name string, os_name string) string { for s in f.stmts { if s is ast.FnDecl { if s.is_pub { - fn_signature := s.stringify(b.table, mod_name, map[string]string{}) + fn_signature := s.stringify_fn_decl(b.table, mod_name, map[string]string{}) fn_mod := s.modname() if fn_mod == mod_name { fline := '${fn_mod}: ${fn_signature}' diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index ad800bd82c..294097e325 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -505,40 +505,41 @@ pub mut: [minify] pub struct FnDecl { pub: - name string // 'math.bits.normalize' - short_name string // 'normalize' - mod string // 'math.bits' - is_deprecated bool - is_pub bool - is_variadic bool - is_anon bool - is_noreturn bool // true, when [noreturn] is used on a fn - is_manualfree bool // true, when [manualfree] is used on a fn - is_main bool // true for `fn main()` - is_test bool // true for `fn test_abcde() {}`, false for `fn test_abc(x int) {}`, or for fns that do not start with test_ - is_conditional bool // true for `[if abc] fn abc(){}` - is_exported bool // true for `[export: 'exact_C_name']` - is_keep_alive bool // passed memory must not be freed (by GC) before function returns - is_unsafe bool // true, when [unsafe] is used on a fn - is_markused bool // true, when an explicit `[markused]` tag was put on a fn; `-skip-unused` will not remove that fn - is_file_translated bool // true, when the file it resides in is `[translated]` - receiver StructField // TODO this is not a struct field - receiver_pos token.Pos // `(u User)` in `fn (u User) name()` position - is_method bool - method_type_pos token.Pos // `User` in ` fn (u User)` position - method_idx int - rec_mut bool // is receiver mutable - rec_share ShareType - language Language // V, C, JS - file_mode Language // whether *the file*, where a function was a '.c.v', '.js.v' etc. - no_body bool // just a definition `fn C.malloc()` - is_builtin bool // this function is defined in builtin/strconv - body_pos token.Pos // function bodys position - file string - generic_names []string - is_direct_arr bool // direct array access - attrs []Attr - ctdefine_idx int = -1 // the index in fn.attrs of `[if xyz]`, when such attribute exists + name string // 'math.bits.normalize' + short_name string // 'normalize' + mod string // 'math.bits' + is_deprecated bool + is_pub bool + is_variadic bool + is_anon bool + is_noreturn bool // true, when [noreturn] is used on a fn + is_manualfree bool // true, when [manualfree] is used on a fn + is_main bool // true for `fn main()` + is_test bool // true for `fn test_abcde() {}`, false for `fn test_abc(x int) {}`, or for fns that do not start with test_ + is_conditional bool // true for `[if abc] fn abc(){}` + is_exported bool // true for `[export: 'exact_C_name']` + is_keep_alive bool // passed memory must not be freed (by GC) before function returns + is_unsafe bool // true, when [unsafe] is used on a fn + is_markused bool // true, when an explicit `[markused]` tag was put on a fn; `-skip-unused` will not remove that fn + is_file_translated bool // true, when the file it resides in is `[translated]` + receiver StructField // TODO this is not a struct field + receiver_pos token.Pos // `(u User)` in `fn (u User) name()` position + is_method bool + is_static_type_method bool // true for `fn Foo.bar() {}` + method_type_pos token.Pos // `User` in ` fn (u User)` position + method_idx int + rec_mut bool // is receiver mutable + rec_share ShareType + language Language // V, C, JS + file_mode Language // whether *the file*, where a function was a '.c.v', '.js.v' etc. + no_body bool // just a definition `fn C.malloc()` + is_builtin bool // this function is defined in builtin/strconv + body_pos token.Pos // function bodys position + file string + generic_names []string + is_direct_arr bool // direct array access + attrs []Attr + ctdefine_idx int = -1 // the index in fn.attrs of `[if xyz]`, when such attribute exists pub mut: idx int // index in an external container; can be used to refer to the function in a more efficient way, just by its integer index params []Param @@ -578,25 +579,26 @@ pub fn (f &FnDecl) new_method_with_receiver_type(new_type Type) FnDecl { [minify] pub struct Fn { pub: - is_variadic bool - language Language - is_pub bool - is_ctor_new bool // `[use_new] fn JS.Array.prototype.constructor()` - is_deprecated bool // `[deprecated] fn abc(){}` - is_noreturn bool // `[noreturn] fn abc(){}` - is_unsafe bool // `[unsafe] fn abc(){}` - is_placeholder bool - is_main bool // `fn main(){}` - is_test bool // `fn test_abc(){}` - is_keep_alive bool // passed memory must not be freed (by GC) before function returns - is_method bool // true for `fn (x T) name()`, and for interface declarations (which are also for methods) - no_body bool // a pure declaration like `fn abc(x int)`; used in .vh files, C./JS. fns. - is_file_translated bool // true, when the file it resides in is `[translated]` - mod string - file string - file_mode Language - pos token.Pos - return_type_pos token.Pos + is_variadic bool + language Language + is_pub bool + is_ctor_new bool // `[use_new] fn JS.Array.prototype.constructor()` + is_deprecated bool // `[deprecated] fn abc(){}` + is_noreturn bool // `[noreturn] fn abc(){}` + is_unsafe bool // `[unsafe] fn abc(){}` + is_placeholder bool + is_main bool // `fn main(){}` + is_test bool // `fn test_abc(){}` + is_keep_alive bool // passed memory must not be freed (by GC) before function returns + is_method bool // true for `fn (x T) name()`, and for interface declarations (which are also for methods) + is_static_type_method bool // true for `fn Foo.bar() {}` + no_body bool // a pure declaration like `fn abc(x int)`; used in .vh files, C./JS. fns. + is_file_translated bool // true, when the file it resides in is `[translated]` + mod string + file string + file_mode Language + pos token.Pos + return_type_pos token.Pos pub mut: return_type Type receiver_type Type // != 0, when .is_method == true diff --git a/vlib/v/ast/str.v b/vlib/v/ast/str.v index 39f48da004..2a181c64df 100644 --- a/vlib/v/ast/str.v +++ b/vlib/v/ast/str.v @@ -41,7 +41,7 @@ pub fn (node &CallExpr) fkey() string { } // These methods are used only by vfmt, vdoc, and for debugging. -pub fn (node &AnonFn) stringify(t &Table, cur_mod string, m2a map[string]string) string { +pub fn (node &AnonFn) stringify_anon_decl(t &Table, cur_mod string, m2a map[string]string) string { mut f := strings.new_builder(30) f.write_string('fn ') if node.inherited_vars.len > 0 { @@ -65,7 +65,7 @@ pub fn (node &AnonFn) stringify(t &Table, cur_mod string, m2a map[string]string) return f.str() } -pub fn (node &FnDecl) stringify(t &Table, cur_mod string, m2a map[string]string) string { +pub fn (node &FnDecl) stringify_fn_decl(t &Table, cur_mod string, m2a map[string]string) string { mut f := strings.new_builder(30) if node.is_pub { f.write_string('pub ') @@ -85,12 +85,19 @@ pub fn (node &FnDecl) stringify(t &Table, cur_mod string, m2a map[string]string) styp = styp.trim('&') } f.write_string(styp + ') ') + } else if node.is_static_type_method { + mut styp := util.no_cur_mod(t.type_to_code(node.receiver.typ.clear_flag(.shared_f)), + cur_mod) + f.write_string(styp + '.') } - name := if !node.is_method && node.language == .v { + mut name := if !node.is_method && node.language == .v { node.name.all_after_last('.') } else { node.name } + if node.is_static_type_method { + name = name.after('__static__') + } f.write_string(name) if name in ['+', '-', '*', '/', '%', '<', '>', '==', '!=', '>=', '<='] { f.write_string(' ') @@ -374,6 +381,9 @@ pub fn (x Expr) str() string { if x.name.contains('.') { return '${x.name}(${sargs})${propagate_suffix}' } + if x.name.contains('__static__') { + return '${x.mod}.${x.name}(${sargs})${propagate_suffix}1' + } return '${x.mod}.${x.name}(${sargs})${propagate_suffix}' } CharLiteral { diff --git a/vlib/v/builder/builder.v b/vlib/v/builder/builder.v index a707ee4df8..0e7d6ee523 100644 --- a/vlib/v/builder/builder.v +++ b/vlib/v/builder/builder.v @@ -590,7 +590,7 @@ pub fn (mut b Builder) print_warnings_and_errors() { for stmt in file.stmts { if stmt is ast.FnDecl { if stmt.name == fn_name { - fheader := stmt.stringify(b.table, 'main', map[string]string{}) + fheader := stmt.stringify_fn_decl(b.table, 'main', map[string]string{}) redefines << FunctionRedefinition{ fpath: file.path fline: stmt.pos.line_nr diff --git a/vlib/v/doc/utils.v b/vlib/v/doc/utils.v index b2e2462737..201bf710f0 100644 --- a/vlib/v/doc/utils.v +++ b/vlib/v/doc/utils.v @@ -118,7 +118,7 @@ pub fn (mut d Doc) stmt_signature(stmt ast.Stmt) string { return 'module ${stmt.name}' } ast.FnDecl { - return stmt.stringify(d.table, d.fmt.cur_mod, d.fmt.mod2alias) + return stmt.stringify_fn_decl(d.table, d.fmt.cur_mod, d.fmt.mod2alias) } else { d.fmt.out = strings.new_builder(1000) diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index 0fa72fa6c3..324cc79dde 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -1007,7 +1007,7 @@ pub fn (mut f Fmt) enum_decl(node ast.EnumDecl) { pub fn (mut f Fmt) fn_decl(node ast.FnDecl) { f.attrs(node.attrs) - f.write(node.stringify(f.table, f.cur_mod, f.mod2alias)) // `Expr` instead of `ast.Expr` in mod ast + f.write(node.stringify_fn_decl(f.table, f.cur_mod, f.mod2alias)) // `Expr` instead of `ast.Expr` in mod ast // Handle trailing comments after fn header declarations if node.no_body && node.end_comments.len > 0 { first_comment := node.end_comments[0] @@ -1032,7 +1032,7 @@ pub fn (mut f Fmt) fn_decl(node ast.FnDecl) { } pub fn (mut f Fmt) anon_fn(node ast.AnonFn) { - f.write(node.stringify(f.table, f.cur_mod, f.mod2alias)) // `Expr` instead of `ast.Expr` in mod ast + f.write(node.stringify_anon_decl(f.table, f.cur_mod, f.mod2alias)) // `Expr` instead of `ast.Expr` in mod ast f.fn_body(node.decl) } @@ -1305,7 +1305,7 @@ pub fn (mut f Fmt) interface_field(field ast.StructField) { pub fn (mut f Fmt) interface_method(method ast.FnDecl) { f.write('\t') - f.write(method.stringify(f.table, f.cur_mod, f.mod2alias).all_after_first('fn ')) + f.write(method.stringify_fn_decl(f.table, f.cur_mod, f.mod2alias).all_after_first('fn ')) f.comments(method.comments, inline: true, has_nl: false, level: .indent) f.writeln('') f.comments(method.next_comments, inline: false, has_nl: true, level: .indent) @@ -1790,6 +1790,7 @@ pub fn (mut f Fmt) call_expr(node ast.CallExpr) { for arg in node.args { f.comments(arg.comments) } + mut is_method_newline := false if node.is_method { if node.name in ['map', 'filter', 'all', 'any'] { @@ -1826,7 +1827,11 @@ pub fn (mut f Fmt) call_expr(node ast.CallExpr) { } else { name := f.short_module(node.name) f.mark_import_as_used(name) - f.write('${name}') + if node.name.contains('__static__') { + f.write(name.replace('__static__', '.').capitalize()) + } else { + f.write(name) + } } } if node.mod == '' && node.name == '' { diff --git a/vlib/v/gen/golang/golang.v b/vlib/v/gen/golang/golang.v index 7f9750888a..c5c875ba10 100644 --- a/vlib/v/gen/golang/golang.v +++ b/vlib/v/gen/golang/golang.v @@ -893,12 +893,12 @@ pub fn (mut f Gen) enum_decl(node ast.EnumDecl) { pub fn (mut f Gen) fn_decl(node ast.FnDecl) { f.attrs(node.attrs) - f.write(node.stringify(f.table, f.cur_mod, f.mod2alias).replace('fn ', 'func ')) + f.write(node.stringify_fn_decl(f.table, f.cur_mod, f.mod2alias).replace('fn ', 'func ')) f.fn_body(node) } pub fn (mut f Gen) anon_fn(node ast.AnonFn) { - f.write(node.stringify(f.table, f.cur_mod, f.mod2alias)) // `Expr` instead of `ast.Expr` in mod ast + f.write(node.stringify_fn_decl(f.table, f.cur_mod, f.mod2alias)) // `Expr` instead of `ast.Expr` in mod ast f.fn_body(node.decl) } @@ -1123,7 +1123,7 @@ pub fn (mut f Gen) interface_field(field ast.StructField) { pub fn (mut f Gen) interface_method(method ast.FnDecl) { f.write('\t') - f.write(method.stringify(f.table, f.cur_mod, f.mod2alias).after('fn ')) + f.write(method.stringify_fn_decl(f.table, f.cur_mod, f.mod2alias).after('fn ')) f.writeln('') for param in method.params { f.mark_types_import_as_used(param.typ) diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index 1b90b286dc..83860b7e5c 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -1,6 +1,6 @@ // Copyright (c) 2019-2023 Alexander Medvednikov. All rights reserved. // Use of this source code is governed by an MIT license -// that can be found in the LICENSE file. +// that ca be found in the LICENSE file. module parser import v.ast @@ -10,16 +10,22 @@ import os 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[0].is_capital() && p.tok.kind == .dot + if is_static_type_method { + p.check(.dot) + name = name.to_lower() + '__static__' + p.check_name() + } mut fn_name := if language == .c { - 'C.${p.check_name()}' + 'C.${name}' } else if language == .js { - 'JS.${p.check_js_name()}' + 'JS.${name}' } else if language == .wasm { - 'WASM.${p.check_name()}' + 'WASM.${name}' } else if mod.len > 0 { - '${mod}.${p.check_name()}' + '${mod}.${name}' } else { - p.check_name() + name } if language != .v { p.check_for_impure_v(language, first_pos) @@ -271,6 +277,7 @@ fn (mut p Parser) fn_decl() ast.FnDecl { language: language } mut is_method := false + mut is_static_type_method := false mut params := []ast.Param{} if p.tok.kind == .lpar { is_method = true @@ -288,7 +295,16 @@ fn (mut p Parser) fn_decl() ast.FnDecl { name_pos := p.tok.pos() if p.tok.kind == .name { // TODO high order fn - name = if language == .js { p.check_js_name() } else { p.check_name() } + 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" + rec.typ = p.parse_type() //_with_mut(false) // ast.Type(p.table.find_type_idx(name)) + p.check(.dot) + name = type_name.to_lower() + '__static__' + p.check_name() // "foo__bar" + } else { + name = if language == .js { p.check_js_name() } else { p.check_name() } + } if language == .v && !p.pref.translated && !p.is_translated && util.contains_capital(name) && !p.builtin_mod { p.error_with_pos('function names cannot contain uppercase letters, use snake_case instead', @@ -519,6 +535,8 @@ run them via `v file.v` instead', is_test: is_test is_keep_alive: is_keep_alive is_method: false + is_static_type_method: is_static_type_method + receiver_type: rec.typ // used only if is static type method is_file_translated: p.is_translated // attrs: p.attrs @@ -588,6 +606,7 @@ run them via `v file.v` instead', generic_names: generic_names receiver_pos: rec.pos is_method: is_method + is_static_type_method: is_static_type_method method_type_pos: rec.type_pos method_idx: type_sym_method_idx rec_mut: rec.is_mut diff --git a/vlib/v/parser/parse_type.v b/vlib/v/parser/parse_type.v index 22458cbbc4..b72abb3e3d 100644 --- a/vlib/v/parser/parse_type.v +++ b/vlib/v/parser/parse_type.v @@ -515,7 +515,7 @@ fn (mut p Parser) parse_any_type(language ast.Language, is_ptr bool, check_dot b name = 'C.${name}' } else if language == .js { name = 'JS.${name}' - } else if p.peek_tok.kind == .dot && check_dot { + } else if p.peek_tok.kind == .dot && check_dot && !name[0].is_capital() { // `module.Type` mut mod := name mut mod_pos := p.tok.pos() diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 3563f6ee13..b4a878d9fe 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -1941,6 +1941,7 @@ fn (mut p Parser) note(s string) { } fn (mut p Parser) error_with_pos(s string, pos token.Pos) ast.NodeError { + // print_backtrace() mut kind := 'error:' if p.pref.fatal_errors { util.show_compiler_message(kind, pos: pos, file_path: p.file_name, message: s) @@ -2592,6 +2593,7 @@ fn (mut p Parser) name_expr() ast.Expr { is_generic_call := p.is_generic_call() is_generic_cast := p.is_generic_cast() is_generic_struct_init := p.is_generic_struct_init() + // mut is_static_type_method := false // p.warn('name expr $p.tok.lit $p.peek_tok.str()') same_line := p.tok.line_nr == p.peek_tok.line_nr // `(` must be on same line as name token otherwise it's a ParExpr @@ -2673,6 +2675,7 @@ fn (mut p Parser) name_expr() ast.Expr { return node } else { // fn call + // fn_call: if is_option { p.unexpected_with_pos(p.prev_tok.pos(), got: '${p.prev_tok}' @@ -2745,8 +2748,14 @@ fn (mut p Parser) name_expr() ast.Expr { } } if p.peek_token(2).kind == .name && p.peek_token(3).kind == .lpar && !known_var { - p.error_with_pos('the receiver of the method call must be an instantiated object, e.g. `foo.bar()`', - p.tok.pos()) + if lit0_is_capital && p.peek_tok.kind == .dot && language == .v { + // New static method call + p.expr_mod = '' + return p.call_expr(language, mod) + } else { + p.error_with_pos('${lit0_is_capital} the receiver of the method call must be an instantiated object, e.g. `foo.bar()`', + p.tok.pos()) + } } // `Color.green` mut enum_name := p.check_name() diff --git a/vlib/v/parser/tests/method_call_receiver_err.out b/vlib/v/parser/tests/method_call_receiver_err.out index 9b4eb8651b..13503ca7b2 100644 --- a/vlib/v/parser/tests/method_call_receiver_err.out +++ b/vlib/v/parser/tests/method_call_receiver_err.out @@ -1,7 +1,21 @@ -vlib/v/parser/tests/method_call_receiver_err.vv:9:11: error: the receiver of the method call must be an instantiated object, e.g. `foo.bar()` +vlib/v/parser/tests/method_call_receiver_err.vv:6:2: warning: unused variable: `s1` + 4 | + 5 | fn main() { + 6 | s1 := S1{} + | ~~ + 7 | + 8 | $for method in S1.methods { +vlib/v/parser/tests/method_call_receiver_err.vv:8:7: warning: unused variable: `method` + 6 | s1 := S1{} + 7 | + 8 | $for method in S1.methods { + | ~~~~~~ + 9 | println(S1.method_hello('yo')) + 10 | } +vlib/v/parser/tests/method_call_receiver_err.vv:9:11: error: unknown function: s1__static__method_hello 7 | 8 | $for method in S1.methods { 9 | println(S1.method_hello('yo')) - | ~~ + | ~~~~~~~~~~~~~~~~~~~~~ 10 | } 11 | } diff --git a/vlib/v/tests/fn_test.v b/vlib/v/tests/fn_test.v index 5762cc3cb9..2b403644e7 100644 --- a/vlib/v/tests/fn_test.v +++ b/vlib/v/tests/fn_test.v @@ -172,3 +172,31 @@ fn test_fn_return_fn() { f := ff() assert f() == 22 } + +// Test new static methods + +struct Foo { + x int +} + +struct Foo2 { + x int +} + +fn (f Foo) normal_method() { +} + +fn Foo.static_method() int { + return 7 +} + +fn Foo2.static_method() int { + return 8 +} + +fn test_static_method() { + x := Foo.static_method() + assert x == 7 + x2 := Foo2.static_method() + assert x2 == 8 +}