From 588c5c4120ca1972dce19655c0556cf6a539b125 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Tue, 21 Apr 2026 15:14:28 +0300 Subject: [PATCH] checker: delete the default zero value in v (fixes #23940) --- doc/docs.md | 19 ++++++++++++++++++ vlib/v/checker/checker.v | 18 ++++++++++++++--- vlib/v/checker/tests/strict_map_index_err.out | 6 ++++++ vlib/v/checker/tests/strict_map_index_err.vv | 9 +++++++++ vlib/v/parser/module.v | 1 + .../builtin_maps/map_get_anon_fn_value_test.v | 20 +++++++++++++++++++ 6 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 vlib/v/checker/tests/strict_map_index_err.out create mode 100644 vlib/v/checker/tests/strict_map_index_err.vv diff --git a/doc/docs.md b/doc/docs.md index bb0ad8972..29f32b54c 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -1535,6 +1535,25 @@ mm := map[string]int{} val := mm['bad_key'] or { panic('key not found') } ``` +Modules can opt into stricter map indexing: + +```v +@[strict_map_index] +module main + +fn main() { + m := { + 'abc': 'xyz' + } + value := m['abc'] or { panic('missing map key') } + if other := m['abc'] { + println(other) + } + // m['bad_key'] // compile error in `@[strict_map_index]` modules + println(value) +} +``` + You can also check, if a key is present, and get its value, if it was present, in one go: ```v diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index f3dd26acd..0579622bf 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -72,6 +72,7 @@ pub mut: expected_expr_type ast.Type // if/match is_expr: expected_type mod string // current module name has_globals_in_module bool // true if the current module has @[has_globals] attribute + strict_map_index_in_module bool // true if the current module has @[strict_map_index] attribute const_var &ast.ConstField = unsafe { nil } // the current constant, when checking const declarations const_deps []string const_names []string @@ -662,10 +663,16 @@ pub fn (mut c Checker) change_current_file(file &ast.File) { } // Check if the current module has has_globals attribute c.has_globals_in_module = false + c.strict_map_index_in_module = false for attr in file.mod.attrs { - if attr.name == 'has_globals' { - c.has_globals_in_module = true - break + match attr.name { + 'has_globals' { + c.has_globals_in_module = true + } + 'strict_map_index' { + c.strict_map_index_in_module = true + } + else {} } } } @@ -7923,6 +7930,11 @@ fn (mut c Checker) index_expr(mut node ast.IndexExpr) ast.Type { err := c.map_key_expected_msg(index_type, key_type, node.index, '${node.left}') c.error('invalid key: ${err}', node.pos) } + if c.strict_map_index_in_module && node.or_expr.kind == .absent && !node.is_setter + && !c.inside_if_guard { + c.error('`@[strict_map_index]` requires handling missing map keys with `or {}` or `if value := map[key] {}`', + node.pos) + } value_sym := c.table.sym(info.value_type) if !node.is_setter && value_sym.kind == .sum_type && node.or_expr.kind == .absent && !c.inside_unsafe && !c.inside_if_guard { diff --git a/vlib/v/checker/tests/strict_map_index_err.out b/vlib/v/checker/tests/strict_map_index_err.out new file mode 100644 index 000000000..acc734f0b --- /dev/null +++ b/vlib/v/checker/tests/strict_map_index_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/strict_map_index_err.vv:8:7: error: `@[strict_map_index]` requires handling missing map keys with `or {}` or `if value := map[key] {}` + 6 | 'abc': 'xyz' + 7 | } + 8 | _ = m['missing'] + | ~~~~~~~~~~~ + 9 | } diff --git a/vlib/v/checker/tests/strict_map_index_err.vv b/vlib/v/checker/tests/strict_map_index_err.vv new file mode 100644 index 000000000..8a24251b1 --- /dev/null +++ b/vlib/v/checker/tests/strict_map_index_err.vv @@ -0,0 +1,9 @@ +@[strict_map_index] +module main + +fn main() { + m := { + 'abc': 'xyz' + } + _ = m['missing'] +} diff --git a/vlib/v/parser/module.v b/vlib/v/parser/module.v index 0f6aaad5f..1747d6ab1 100644 --- a/vlib/v/parser/module.v +++ b/vlib/v/parser/module.v @@ -182,6 +182,7 @@ fn (mut p Parser) module_decl() ast.Module { 'has_globals' { p.has_globals = true } + 'strict_map_index' {} 'translated' { p.is_translated = true } diff --git a/vlib/v/tests/builtin_maps/map_get_anon_fn_value_test.v b/vlib/v/tests/builtin_maps/map_get_anon_fn_value_test.v index 6b0c10249..028ed55ed 100644 --- a/vlib/v/tests/builtin_maps/map_get_anon_fn_value_test.v +++ b/vlib/v/tests/builtin_maps/map_get_anon_fn_value_test.v @@ -1,3 +1,6 @@ +@[strict_map_index] +module main + const numbers = { 'one': fn () int { return 1 @@ -23,3 +26,20 @@ fn test_map_get_anon_fn_value() { println(ret2) assert ret2 == 2 } + +fn test_map_get_anon_fn_value_if_guard() { + values := { + 'one': 1 + } + mut found := false + if value := values['one'] { + found = true + assert value == 1 + } + assert found + mut missing_found := false + if _ := values['two'] { + missing_found = true + } + assert !missing_found +} -- 2.39.5