1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00

all: static type methods

This commit is contained in:
Alexander Medvednikov 2023-06-27 01:23:24 +03:00
parent aacdd61e67
commit a9f8b5dadc
12 changed files with 165 additions and 78 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 == '' {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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