From bff77a8fefe7cf59c422736261a35e4c88cb3a9b Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 15 Apr 2026 05:21:28 +0300 Subject: [PATCH] fmt: automatically add imports for vlib modules (fixes #23389) --- .../fmt/tests/array_init_eol_comments_keep.vv | 2 + vlib/v/fmt/tests/infix_expr_expected.vv | 2 + vlib/v/fmt/tests/missing_import_expected.vv | 2 +- vlib/v/fmt/tests/missing_import_input.vv | 2 +- vlib/v/parser/parser.v | 46 ++++++++++++++----- 5 files changed, 41 insertions(+), 13 deletions(-) diff --git a/vlib/v/fmt/tests/array_init_eol_comments_keep.vv b/vlib/v/fmt/tests/array_init_eol_comments_keep.vv index a9e45704d..3c5102140 100644 --- a/vlib/v/fmt/tests/array_init_eol_comments_keep.vv +++ b/vlib/v/fmt/tests/array_init_eol_comments_keep.vv @@ -1,3 +1,5 @@ +import math + fn abc() { test_cases_f32 := [ f32_from_bits1(0x0000_0000), // +0 diff --git a/vlib/v/fmt/tests/infix_expr_expected.vv b/vlib/v/fmt/tests/infix_expr_expected.vv index bc3cc0d29..261c10642 100644 --- a/vlib/v/fmt/tests/infix_expr_expected.vv +++ b/vlib/v/fmt/tests/infix_expr_expected.vv @@ -1,3 +1,5 @@ +import js + fn grouped_cond_single_line() { // fmt tries to keep grouped conditions together... _ := one_condition_before && another_condition diff --git a/vlib/v/fmt/tests/missing_import_expected.vv b/vlib/v/fmt/tests/missing_import_expected.vv index c46339294..950548c94 100644 --- a/vlib/v/fmt/tests/missing_import_expected.vv +++ b/vlib/v/fmt/tests/missing_import_expected.vv @@ -1,5 +1,5 @@ import time fn main() { - println(time.now()) + println(time.minute) } diff --git a/vlib/v/fmt/tests/missing_import_input.vv b/vlib/v/fmt/tests/missing_import_input.vv index d173cb218..10f9b081f 100644 --- a/vlib/v/fmt/tests/missing_import_input.vv +++ b/vlib/v/fmt/tests/missing_import_input.vv @@ -1,3 +1,3 @@ fn main() { - println(time.now()) + println(time.minute) } diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index f9dcb502d..f0d2ae6c8 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -2338,17 +2338,7 @@ fn (mut p Parser) dot_expr(left ast.Expr) ast.Expr { if mut left_node is ast.CallExpr { left_node.is_return_used = true } - if p.pref.is_fmt { - if mut left_node is ast.Ident { - // `time.now()` without `time imported` is processed as a method call with `time` being - // a `left_node` expression. Import `time` automatically. - // TODO: fetch all available modules - if left_node.name in ['time', 'os', 'strings', 'math', 'json', 'base64'] - && !left_node.scope.known_var(left_node.name) { - p.register_implied_import(left_node.name) - } - } - } + p.maybe_register_implied_vlib_import(left) mcall_expr := ast.CallExpr{ left: left name: field_name @@ -2417,9 +2407,43 @@ fn (mut p Parser) dot_expr(left ast.Expr) ast.Expr { if mut left_node is ast.CallExpr { left_node.is_return_used = true } + p.maybe_register_implied_vlib_import(left) return sel_expr } +fn (p &Parser) vfmt_vlib_path() string { + if p.pref.vlib != '' { + return p.pref.vlib + } + return os.join_path(os.dir(pref.vexe_path()), 'vlib') +} + +fn (mut p Parser) maybe_register_implied_vlib_import(left ast.Expr) { + if !p.pref.is_fmt || left !is ast.Ident { + return + } + left_node := left as ast.Ident + if left_node.name == '' || left_node.name in p.imports + || left_node.name == p.mod.all_after_last('.') + || left_node.name == p.cur_fn_name.all_after_last('.') + || left_node.scope.known_var(left_node.name) { + return + } + for _, imported_mod in p.imports { + if imported_mod == left_node.name || imported_mod.all_after_last('.') == left_node.name { + // The module is already imported, potentially under an alias, so this is not a missing import. + return + } + } + if left_node.name in p.imported_symbols { + return + } + // vfmt can infer a missing import when the selector prefix matches a top-level vlib module. + if os.is_dir(os.join_path(p.vfmt_vlib_path(), left_node.name)) { + p.register_implied_import(left_node.name) + } +} + fn (mut p Parser) parse_generic_types() ([]ast.Type, []string) { mut types := []ast.Type{} mut param_names := []string{} -- 2.39.5