Skip to content

checker: fix static method used as map/filter argument (#27328)#27331

Merged
medvednikov merged 2 commits into
masterfrom
fix-27328-static-method-map
Jun 3, 2026
Merged

checker: fix static method used as map/filter argument (#27328)#27331
medvednikov merged 2 commits into
masterfrom
fix-27328-static-method-map

Conversation

@medvednikov
Copy link
Copy Markdown
Member

Fixes #27328

A static method used as a first-class value — e.g. arr.map(mtime.MTime.from) — is parsed as an ast.EnumVal, because Type.from is syntactically identical to an enum value such as Color.red. The parser only rewrites it into a function ast.Ident when the static method is already registered in the function table. That is not the case for:

  • cross-module references — imported modules are parsed after the call site, so the static method isn't in the table yet; and
  • forward references within a file.

In those cases the ast.EnumVal reaches the backend, which emits the static method's name as a bare value without calling it (and markused never keeps the method, so it is not generated):

error: unknown enum `mtime.MTime`
error: use of undeclared identifier 'mtime__MTime__static__from'

Fix

Resolve it in the checker. In array_builtin_method_call, once the callback argument of map/filter/any/all/count has been resolved to a function type, rewrite the ast.EnumVal into a function ast.Ident. Then all existing function-value handling applies — the backends call it and markused keeps the static method, exactly like any other function value.

The rewrite only triggers when the value resolves to a function type, so genuine enum values (nums.map(Color.green)) are left untouched.

Test

Added vlib/v/tests/fns/static_method_as_map_arg_test.v, which covers the forward-reference path (map(Foo.from), the explicit-call form, and filter). It fails on master and passes with this change.

Verified no regressions: enums/ (40/40), fns/ (162/162), the full compiler_errors_test.v harness, and the existing static-method tests all pass; nums.map(Color.green) still produces enum values.

A static method used as a first-class value, e.g. `arr.map(Type.from)`, is
parsed as an `ast.EnumVal`, since `Type.from` is syntactically identical to an
enum value (`Color.red`). The parser only rewrites it into a function `ast.Ident`
when the static method is already registered in the function table, which is not
the case for cross-module references (imported modules are parsed after the call
site) nor for forward references within a file.

In those cases the `EnumVal` reached the backend, which emitted the static
method's name as a bare value without calling it (and markused did not keep the
method, so it was never generated) -> `undeclared identifier` / `unknown enum`.

Resolve the value in the checker: in `array_builtin_method_call`, once the
callback argument of `map`/`filter`/`any`/`all`/`count` has been resolved to a
function type, rewrite the `ast.EnumVal` into a function `ast.Ident`, so the
backends call it and markused keeps the static method, exactly like any other
function value.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9a465e6b96

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread vlib/v/checker/fn.v Outdated
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 {
static_fn_name := '${arg_expr.enum_name}__static__${arg_expr.val}'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Resolve aliased static methods by their actual fkey

When the callback is written through a supported type alias, e.g. type MyAlias = MyStruct; nums.map(MyAlias.new), enum_val_as_static_fn() can resolve the EnumVal via the final symbol (MyStruct__static__new) and set its type to a function, but this reconstruction only looks for MyAlias__static__new. That misses the real registered function, so the expression is left as an EnumVal; markused still will not keep the static method and cgen emits a non-existent alias-named function value. Reuse the resolved function/fkey path rather than rebuilding it from enum_name.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — fixed in 035ae39. The alias-aware lookup that enum_val_as_static_fn already used (which checks the final/unaliased symbol) is now factored into a shared static_method_of_enum_val helper and reused at the rewrite site, taking the resolved function's real name. Added an aliased-static-method case to the regression test.

Address review feedback: when the callback is a static method reached through a
type alias (`type Alias = Struct; arr.map(Alias.new)`), rebuilding the function
name from the EnumVal's `enum_name` produced `Alias__static__new`, which is not
a registered function, so the value was left as an EnumVal (markused dropped the
method, cgen emitted an alias-named, non-existent function).

Factor the alias-aware lookup used by `enum_val_as_static_fn` into a shared
`static_method_of_enum_val` helper (it also checks the final/unaliased symbol)
and reuse it at the rewrite site, taking the resolved function's real name.
@medvednikov
Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@medvednikov medvednikov merged commit b02a629 into master Jun 3, 2026
87 of 93 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

cgen/checker: module-qualified static method as map callback misresolved as enum value

1 participant