Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 17 additions & 7 deletions vlib/v/checker/checker.v
Original file line number Diff line number Diff line change
Expand Up @@ -8372,17 +8372,27 @@ fn (mut c Checker) static_fn_value_from_enum_val(mut node ast.EnumVal, _ string,
return node.typ
}

fn (mut c Checker) enum_val_as_static_fn(mut node ast.EnumVal, typ_sym ast.TypeSymbol, fsym ast.TypeSymbol) ast.Type {
fn_name := '${typ_sym.name}__static__${node.val}'
if func := c.table.find_fn(fn_name) {
return c.static_fn_value_from_enum_val(mut node, fn_name, func)
// static_method_of_enum_val resolves the static method that an `ast.EnumVal`-shaped
// expression (`Type.method`, syntactically identical to an enum value `Color.red`)
// refers to. It also checks the final/unaliased symbol, so static methods reached
// through a supported type alias (`type Alias = Struct; Alias.new`) are found by
// their real fkey instead of the alias name.
fn (c &Checker) static_method_of_enum_val(node ast.EnumVal, typ_sym ast.TypeSymbol, fsym ast.TypeSymbol) ?ast.Fn {
if func := c.table.find_fn('${typ_sym.name}__static__${node.val}') {
return func
}
if fsym.name != typ_sym.name {
alias_fn_name := '${fsym.name}__static__${node.val}'
if func := c.table.find_fn(alias_fn_name) {
return c.static_fn_value_from_enum_val(mut node, alias_fn_name, func)
if func := c.table.find_fn('${fsym.name}__static__${node.val}') {
return func
}
}
return none
}

fn (mut c Checker) enum_val_as_static_fn(mut node ast.EnumVal, typ_sym ast.TypeSymbol, fsym ast.TypeSymbol) ast.Type {
if func := c.static_method_of_enum_val(node, typ_sym, fsym) {
return c.static_fn_value_from_enum_val(mut node, func.name, func)
}
return ast.void_type
}

Expand Down
28 changes: 28 additions & 0 deletions vlib/v/checker/fn.v
Original file line number Diff line number Diff line change
Expand Up @@ -4930,6 +4930,34 @@ fn (mut c Checker) array_builtin_method_call(mut node ast.CallExpr, left_type as
}
arg_type = c.check_expr_option_or_result_call(arg.expr, expr_type)
}
// `arr.map(Type.from)`, `arr.filter(Type.ok)` etc.: a static method used as a
// first-class value is parsed as an `ast.EnumVal` (it is syntactically identical
// to an enum value, e.g. `Color.red`). The parser can only emit a function
// `ast.Ident` for it when the static method is already registered, which fails
// for cross-module (and forward) references, since imported modules are parsed
// after the call site. Once the checker has resolved the value to a function
// type, rewrite it into a function `ast.Ident`, so the backends call it and
// markused keeps the static method, exactly like any other function value.
if node.kind in [.map, .filter, .any, .all, .count] && node.args.len > 0 {
arg_expr := node.args[0].expr
if arg_expr is ast.EnumVal && c.table.sym(arg_expr.typ).kind == .function {
enum_typ := ast.new_type(c.table.find_type_idx(arg_expr.enum_name))
typ_sym := c.table.sym(enum_typ)
fsym := c.table.final_sym(enum_typ)
if func := c.static_method_of_enum_val(arg_expr, typ_sym, fsym) {
node.args[0].expr = ast.Ident{
name: func.name
mod: c.mod
kind: .function
info: ast.IdentFn{
typ: ast.new_type(c.table.find_or_register_fn_type(func, false, true))
}
pos: arg_expr.pos
scope: node.scope
}
}
}
}
if node.kind == .map {
// eprintln('>>>>>>> map node.args[0].expr: ${node.args[0].expr}, left_type: ${left_type} | elem_typ: ${elem_typ} | arg_type: ${arg_type}')
// check fn
Expand Down
50 changes: 50 additions & 0 deletions vlib/v/tests/fns/static_method_as_map_arg_test.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Regression test for a static method used as a first-class value in the
// builtin array methods (`map`/`filter`/...). It is parsed as an `ast.EnumVal`
// (same syntax as an enum value, e.g. `Color.red`); when the static method is
// not yet registered while parsing the call site (forward reference here, or a
// cross-module reference), the parser cannot emit a function `ast.Ident`, so the
// checker has to rewrite the resolved value into one. See issue #27328.

fn test_static_method_as_map_arg() {
nums := [1, 2, 3]
foos := nums.map(Foo.from)
assert foos.map(it.x) == [1, 2, 3]
}

fn test_static_method_as_map_arg_explicit_call() {
nums := [1, 2, 3]
foos := nums.map(Foo.from(it))
assert foos.map(it.x) == [1, 2, 3]
}

fn test_static_method_as_filter_arg() {
nums := [1, 2, 3, 4]
odds := nums.filter(is_odd)
assert odds == [1, 3]
}

fn test_aliased_static_method_as_map_arg() {
// the static method is reached through a type alias, so it must be resolved
// by its real fkey (`Foo__static__from`), not the alias name
nums := [1, 2, 3]
foos := nums.map(FooAlias.from)
assert foos.map(it.x) == [1, 2, 3]
}

type FooAlias = Foo

// Defined *after* the call sites on purpose, to exercise the forward-reference
// path (the function is not in the table yet when the calls above are parsed).
struct Foo {
x int
}

fn Foo.from(n int) Foo {
return Foo{
x: n
}
}

fn is_odd(n int) bool {
return n % 2 == 1
}
Loading