From ddc1a1fc084bc055736d5dcbf601ebae12d92f13 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Tue, 15 Nov 2022 19:51:57 +0200 Subject: [PATCH] checker,cgen: support `const y = term.yellow`, then `println(y('abc'))` (#16436) --- vlib/v/ast/ast.v | 6 +- vlib/v/ast/types.v | 4 +- vlib/v/checker/fn.v | 27 +++++++- vlib/v/gen/c/cgen.v | 15 ++++- vlib/v/gen/c/fn.v | 9 ++- vlib/v/tests/const_name_equals_fn_name_test.v | 65 +++++++++++++++++++ 6 files changed, 116 insertions(+), 10 deletions(-) create mode 100644 vlib/v/tests/const_name_equals_fn_name_test.v diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index f290a571bc..e9e922c81a 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -580,7 +580,8 @@ pub mut: name string // left.name() is_method bool is_field bool // temp hack, remove ASAP when re-impl CallExpr / Selector (joe) - is_fn_var bool // fn variable + is_fn_var bool // fn variable, `a := fn() {}`, then: `a()` + is_fn_a_const bool // fn const, `const c = abc`, where `abc` is a function, then: `c()` is_keep_alive bool // GC must not free arguments before fn returns 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 @@ -592,7 +593,8 @@ pub mut: left_type Type // type of `user` receiver_type Type // User return_type Type - fn_var_type Type // fn variable type + fn_var_type Type // the fn type, when `is_fn_a_const` or `is_fn_var` is true + const_name string // the fully qualified name of the const, i.e. `main.c`, given `const c = abc`, and callexpr: `c()` should_be_skipped bool // true for calls to `[if someflag?]` functions, when there is no `-d someflag` concrete_types []Type // concrete types, e.g. concrete_list_pos token.Pos diff --git a/vlib/v/ast/types.v b/vlib/v/ast/types.v index e91c6043a8..e49ac4d200 100644 --- a/vlib/v/ast/types.v +++ b/vlib/v/ast/types.v @@ -1434,8 +1434,8 @@ pub fn (t &Table) fn_signature_using_aliases(func &Fn, import_aliases map[string return sb.str() } -// Get the name of the complete quanlified name of the type -// without the generic parts. +// symbol_name_except_generic return the name of the complete qualified name of the type, +// but without the generic parts. For example, `main.Abc` -> `main.Abc` pub fn (t &TypeSymbol) symbol_name_except_generic() string { // main.Abc mut embed_name := t.name diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index 7fff7e261f..1feea62b09 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -832,12 +832,37 @@ pub fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) if obj.typ != 0 { sym := c.table.sym(obj.typ) if sym.kind == .function { - found = true func = (sym.info as ast.FnType).func + found = true } } } } + // a same module constant? + if !found { + // allow for `const abc = myfunc`, then calling `abc()` + qualified_const_name := if fn_name.contains('.') { 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(obj.expr) + } + if obj.typ != 0 { + sym := c.table.sym(obj.typ) + if sym.kind == .function { + // at this point, the const metadata should be already known, + // and we are sure that it is just a function + c.table.fns[qualified_const_name].usages++ + c.table.fns[func.name].usages++ + found = true + func = (sym.info as ast.FnType).func + node.is_fn_a_const = true + node.fn_var_type = obj.typ + node.const_name = qualified_const_name + } + } + } + } + if !found { continue_check = false if dot_index := fn_name.index('.') { diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 4c47165b23..893548b0b8 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -4890,11 +4890,15 @@ fn (mut g Gen) const_decl_simple_define(mod string, name string, val string) { } } +fn (mut g Gen) c_const_name(name string) string { + return if g.pref.translated && !g.is_builtin_mod { name } else { '_const_${name}' } +} + fn (mut g Gen) const_decl_init_later(mod string, name string, expr ast.Expr, typ ast.Type, unwrap_option bool) { // Initialize more complex consts in `void _vinit/2{}` // (C doesn't allow init expressions that can't be resolved at compile time). mut styp := g.typ(typ) - cname := if g.pref.translated && !g.is_builtin_mod { name } else { '_const_${name}' } + cname := g.c_const_name(name) mut init := strings.new_builder(100) if cname == '_const_os__args' { if g.pref.os == .windows { @@ -4911,9 +4915,16 @@ fn (mut g Gen) const_decl_init_later(mod string, name string, expr ast.Expr, typ init.writeln(g.expr_string_surround('\t${cname} = ', expr, ';')) } } + mut def := '${styp} ${cname}' + expr_sym := g.table.sym(typ) + if expr_sym.kind == .function { + // allow for: `const xyz = abc`, where `abc` is `fn abc() {}` + func := (expr_sym.info as ast.FnType).func + def = g.fn_var_signature(func.return_type, func.params.map(it.typ), cname) + } g.global_const_defs[util.no_dots(name)] = GlobalConstDef{ mod: mod - def: '${styp} ${cname}; // inited later' + def: '${def}; // inited later' init: init.str().trim_right('\n') dep_names: g.table.dependent_names_in_expr(expr) } diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index e3b05602ae..6d6a6e43eb 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -638,6 +638,9 @@ fn (mut g Gen) get_anon_fn_type_name(mut node ast.AnonFn, var_name string) strin } fn (mut g Gen) call_expr(node ast.CallExpr) { + if node.should_be_skipped { + return + } // NOTE: everything could be done this way // see my comment in parser near anon_fn if node.left is ast.AnonFn { @@ -659,9 +662,6 @@ fn (mut g Gen) call_expr(node ast.CallExpr) { } else if node.left is ast.CallExpr && node.name == '' { g.expr(node.left) } - if node.should_be_skipped { - return - } old_inside_call := g.inside_call g.inside_call = true defer { @@ -1358,6 +1358,9 @@ fn (mut g Gen) fn_call(node ast.CallExpr) { } } } + if node.is_fn_a_const { + name = g.c_const_name(node.const_name.replace('.', '__')) + } // TODO2 // cgen shouldn't modify ast nodes, this should be moved // g.generate_tmp_autofree_arg_vars(node, name) diff --git a/vlib/v/tests/const_name_equals_fn_name_test.v b/vlib/v/tests/const_name_equals_fn_name_test.v new file mode 100644 index 0000000000..ae3eba09cb --- /dev/null +++ b/vlib/v/tests/const_name_equals_fn_name_test.v @@ -0,0 +1,65 @@ +module main + +import term + +fn abc() { + println('xyz') +} + +fn def() string { + return 'xyz' +} + +const a_const_that_is_fn = abc + +const a_const_that_is_fn_returning_value = def + +const a_const_same_as_fn_in_another_module = term.yellow + +fn test_simple_fn_assigned_to_const_can_be_called() { + a_const_that_is_fn() + assert true +} + +fn test_simple_fn_assigned_to_const_can_be_called_and_returns_value() { + assert def == a_const_that_is_fn_returning_value + assert def() == 'xyz' + assert a_const_that_is_fn_returning_value() == 'xyz' + assert def() == a_const_that_is_fn_returning_value() + assert a_const_that_is_fn_returning_value() == def() +} + +// + +fn test_a_const_that_is_alias_to_fn_from_module() { + assert a_const_same_as_fn_in_another_module == term.yellow + assert term.yellow('x') == a_const_same_as_fn_in_another_module('x') + assert ptr_str(term.yellow) == ptr_str(a_const_same_as_fn_in_another_module) +} + +// + +const pg = fn_generator() + +const pg2 = main.fn_generator()() + +fn fn_generator() fn () string { + return fn () string { + println('hello') + return 'ok' + } +} + +fn test_a_const_can_be_assigned_a_fn_produced_by_a_fn_generator_and_the_const_can_be_used() { + assert main.fn_generator()() == 'ok' + + x := fn_generator() + assert x() == 'ok' + + y := pg + assert y() == 'ok' + + assert pg() == 'ok' + + assert pg2 == 'ok' +}