From 3278614abb2e413295d093640ba718b8663f6ed0 Mon Sep 17 00:00:00 2001 From: Felipe Pena Date: Thu, 7 Nov 2024 14:46:51 -0300 Subject: [PATCH] parser,checker: improve static method call resolution (fix #22773) (#22787) --- cmd/tools/vast/vast.v | 1 + vlib/v/ast/ast.v | 1 + vlib/v/ast/str.v | 4 +- vlib/v/checker/checker.v | 4 +- vlib/v/checker/fn.v | 114 +++++++++++------- vlib/v/fmt/fmt.v | 2 +- vlib/v/gen/c/fn.v | 4 +- vlib/v/parser/fn.v | 1 + vlib/v/tests/fns/aliased_static_method_test.v | 13 ++ 9 files changed, 91 insertions(+), 53 deletions(-) create mode 100644 vlib/v/tests/fns/aliased_static_method_test.v diff --git a/cmd/tools/vast/vast.v b/cmd/tools/vast/vast.v index 654c113b1..d39bb087b 100644 --- a/cmd/tools/vast/vast.v +++ b/cmd/tools/vast/vast.v @@ -1560,6 +1560,7 @@ fn (t Tree) call_expr(node ast.CallExpr) &Node { obj.add_terse('is_noreturn', t.bool_node(node.is_noreturn)) obj.add_terse('is_ctor_new', t.bool_node(node.is_ctor_new)) obj.add_terse('is_return_used', t.bool_node(node.is_return_used)) + obj.add_terse('is_static_method', t.bool_node(node.is_static_method)) obj.add('should_be_skipped', t.bool_node(node.should_be_skipped)) obj.add_terse('free_receiver', t.bool_node(node.free_receiver)) obj.add('scope', t.number_node(int(node.scope))) diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index fb5ba2d7f..a3eb7fd2f 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -805,6 +805,7 @@ pub mut: is_noreturn bool // whether the function/method is marked as [noreturn] is_ctor_new bool // if JS ctor calls requires `new` before call, marked as `[use_new]` in V is_file_translated bool // true, when the file it resides in is `@[translated]` + is_static_method bool // it is a static method call args []CallArg expected_arg_types []Type comptime_ret_val bool diff --git a/vlib/v/ast/str.v b/vlib/v/ast/str.v index b2b196dd4..63e2ce828 100644 --- a/vlib/v/ast/str.v +++ b/vlib/v/ast/str.v @@ -24,7 +24,7 @@ pub fn (table &Table) get_anon_fn_name(prefix string, func &Fn, pos int) string // get_name returns the real name for the function calling pub fn (f &CallExpr) get_name() string { - if f.name != '' && f.name.all_after_last('.')[0].is_capital() && f.name.contains('__static__') { + if f.is_static_method { return f.name.replace('__static__', '.') } else { return f.name @@ -474,7 +474,7 @@ pub fn (x &Expr) str() string { if x.name.contains('.') { return '${x.get_name()}(${sargs})${propagate_suffix}' } - if x.name.contains('__static__') { + if x.is_static_method { return '${x.mod}.${x.get_name()}(${sargs})${propagate_suffix}' } if x.mod == 'main' { diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index dba551c06..3b78c76cd 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -3613,7 +3613,7 @@ fn (mut c Checker) at_expr(mut node ast.AtExpr) ast.Type { if c.table.cur_fn == unsafe { nil } { return ast.void_type } - if _ := c.table.cur_fn.name.index('__static__') { + if c.table.cur_fn.is_static_type_method { node.val = c.table.cur_fn.name.all_after_last('__static__') } else { node.val = c.table.cur_fn.name.all_after_last('.') @@ -3627,7 +3627,7 @@ fn (mut c Checker) at_expr(mut node ast.AtExpr) ast.Type { if c.table.cur_fn.is_method { node.val = c.table.type_to_str(c.table.cur_fn.receiver.typ).all_after_last('.') + '.' + fname - } else if _ := fname.index('__static__') { + } else if c.table.cur_fn.is_static_type_method { node.val = fname.all_before('__static__') + '.' + fname.all_after('__static__') } else { node.val = fname diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index a12639b4d..1095acb02 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -340,7 +340,7 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) { && node.name.after_char(`.`) in reserved_type_names { c.error('top level declaration cannot shadow builtin type', node.pos) } - if _ := node.name.index('__static__') { + 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) @@ -827,9 +827,9 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast. is_va_arg := node.name == 'C.va_arg' is_json_decode := node.name == 'json.decode' mut fn_name := node.name - if index := node.name.index('__static__') { + if node.is_static_method { // resolve static call T.name() - if index > 0 && c.table.cur_fn != unsafe { nil } { + if c.table.cur_fn != unsafe { nil } { fn_name = c.table.convert_generic_static_type_name(fn_name, c.table.cur_fn.generic_names, c.table.cur_concrete_types) } @@ -1045,10 +1045,12 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast. unsafe { c.table.fns[fn_name].usages++ } } } - // already imported symbol (static Foo.new() in another module) - if !found && fn_name.len > 0 && fn_name[0].is_capital() { + + // 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) { @@ -1059,55 +1061,75 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast. break } } - } - } - // 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 + if !found { + // aliased static method on current mod + full_type_name := if !fn_name.contains('.') { + 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++ } + node.name = orig_name + } } } - } 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 + } + // 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 + } } - if !c.check_type_and_visibility(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 } - break } - } - if idx == 0 { - c.error('unknown enum `${enum_name}`', node.pos) - return ast.void_type - } + if idx == 0 { + c.error('unknown enum `${enum_name}`', node.pos) + return ast.void_type + } - ret_typ := ast.idx_to_type(idx).set_flag(.option) - if node.args.len != 1 { - c.error('expected 1 argument, but got ${node.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) + ret_typ := ast.idx_to_type(idx).set_flag(.option) + if node.args.len != 1 { + c.error('expected 1 argument, but got ${node.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 } - node.return_type = ret_typ - return ret_typ } mut is_native_builtin := false if !found && c.pref.backend == .native { diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index 47a992153..c06ee8ca7 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -2091,7 +2091,7 @@ pub fn (mut f Fmt) call_expr(node ast.CallExpr) { f.write('${node.name.after_char(`.`)}') } else { name := f.short_module(node.name) - if node.name.contains('__static__') { + if node.is_static_method { f.write_static_method(node.name, name) } else { f.mark_import_as_used(name) diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index ab707e494..8fe9c0fbc 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -2047,9 +2047,9 @@ fn (mut g Gen) fn_call(node ast.CallExpr) { is_selector_call = true } mut name := node.name - if index := node.name.index('__static__') { + if node.is_static_method { // resolve static call T.name() - if index > 0 && g.cur_fn != unsafe { nil } { + if g.cur_fn != unsafe { nil } { name = g.table.convert_generic_static_type_name(node.name, g.cur_fn.generic_names, g.cur_concrete_types) } diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index 364f3c723..57eff4bb4 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -112,6 +112,7 @@ fn (mut p Parser) call_expr(language ast.Language, mod string) ast.CallExpr { scope: p.scope comments: comments is_return_used: p.expecting_value + is_static_method: is_static_type_method } } diff --git a/vlib/v/tests/fns/aliased_static_method_test.v b/vlib/v/tests/fns/aliased_static_method_test.v new file mode 100644 index 000000000..8717b8e11 --- /dev/null +++ b/vlib/v/tests/fns/aliased_static_method_test.v @@ -0,0 +1,13 @@ +module main + +struct MyStruct {} + +fn MyStruct.new() {} + +type MyAlias = MyStruct + +fn test_main() { + MyStruct.new() + MyAlias.new() + assert true +} -- 2.39.5