Skip to content

Commit

Permalink
v: add map update-init syntax: `new_map := {...old_map, 'k1': 1, 'k2'…
Browse files Browse the repository at this point in the history
…: 5}` (#20561)
  • Loading branch information
edam committed Jan 17, 2024
1 parent a79a9cb commit c4b8036
Show file tree
Hide file tree
Showing 14 changed files with 427 additions and 72 deletions.
37 changes: 34 additions & 3 deletions doc/docs.md
Expand Up @@ -78,6 +78,7 @@ by using any of the following commands in a terminal:
* [Array slices](#array-slices)
* [Fixed size arrays](#fixed-size-arrays)
* [Maps](#maps)
* [Map update syntax](#map-update-syntax)

</td><td width=33% valign=top>

Expand Down Expand Up @@ -480,7 +481,7 @@ fn main() {
}
}
```
While variable shadowing is not allowed, field shadowing is allowed.
While variable shadowing is not allowed, field shadowing is allowed.
```v
pub struct Dimension {
width int = -1
Expand Down Expand Up @@ -1462,6 +1463,36 @@ See all methods of
and
[maps](https://modules.vlang.io/maps.html).

### Map update syntax

As with stucts, V lets you initialise a map with an update applied on top of
another map:

```v
const base_map = {
'a': 4
'b': 5
}
foo := {
...base_map
'b': 88
'c': 99
}
println(foo) // {'a': 4, 'b': 88, 'c': 99}
```

This is functionally equivalent to cloning the map and updating it, except that
you don't have to declare a mutable variable:

```v failcompile
// same as above (except mutable)
mut foo := base_map.clone()
foo['b'] = 88
foo['c'] = 99
```

## Module imports

For information about creating a module, see [Modules](#modules).
Expand Down Expand Up @@ -5634,7 +5665,7 @@ serializers for any data format. V has compile time `if` and `for` constructs:
#### <h4 id="comptime-fields">.fields</h4>
You can iterate over struct fields using `.fields`, it also works with generic types
You can iterate over struct fields using `.fields`, it also works with generic types
(e.g. `T.fields`) and generic arguments (e.g. `param.fields` where `fn gen[T](param T) {`).
```v
Expand Down Expand Up @@ -7325,4 +7356,4 @@ Assignment Operators
+= -= *= /= %=
&= |= ^=
>>= <<= >>>=
```
```
14 changes: 14 additions & 0 deletions vlib/builtin/map.v
Expand Up @@ -284,6 +284,20 @@ fn new_map_init(hash_fn MapHashFn, key_eq_fn MapEqFn, clone_fn MapCloneFn, free_
return out
}

fn new_map_update_init(update &map, n int, key_bytes int, value_bytes int, keys voidptr, values voidptr) map {
mut out := unsafe { update.clone() }
mut pkey := &u8(keys)
mut pval := &u8(values)
for _ in 0 .. n {
unsafe {
out.set(pkey, pval)
pkey = pkey + key_bytes
pval = pval + value_bytes
}
}
return out
}

// move moves the map to a new location in memory.
// It does this by copying to a new location, then setting the
// old location to all `0` with `vmemset`
Expand Down
16 changes: 10 additions & 6 deletions vlib/v/ast/ast.v
Expand Up @@ -1502,12 +1502,16 @@ pub:
comments [][]Comment // comments after key-value pairs
pre_cmnts []Comment // comments before the first key-value pair
pub mut:
keys []Expr
vals []Expr
val_types []Type
typ Type
key_type Type
value_type Type
keys []Expr
vals []Expr
val_types []Type
typ Type
key_type Type
value_type Type
has_update_expr bool // has `...a`
update_expr Expr // `a` in `...a`
update_expr_pos token.Pos
update_expr_comments []Comment
}

// s[10..20]
Expand Down
106 changes: 61 additions & 45 deletions vlib/v/checker/containers.v
Expand Up @@ -407,7 +407,7 @@ fn (mut c Checker) array_fixed_has_unresolved_size(info &ast.ArrayFixed) bool {

fn (mut c Checker) map_init(mut node ast.MapInit) ast.Type {
// `map = {}`
if node.keys.len == 0 && node.vals.len == 0 && node.typ == 0 {
if node.keys.len == 0 && node.vals.len == 0 && !node.has_update_expr && node.typ == 0 {
sym := c.table.sym(c.expected_type)
if sym.kind == .map {
info := sym.map_info()
Expand Down Expand Up @@ -455,90 +455,107 @@ fn (mut c Checker) map_init(mut node ast.MapInit) ast.Type {
return node.typ
}

if node.keys.len > 0 && node.vals.len > 0 {
mut key0_type := ast.void_type
mut val0_type := ast.void_type
if (node.keys.len > 0 && node.vals.len > 0) || node.has_update_expr {
mut map_type := ast.void_type
use_expected_type := c.expected_type != ast.void_type && !c.inside_const
&& c.table.sym(c.expected_type).kind == .map && !(c.inside_fn_arg
&& c.expected_type.has_flag(.generic))
if use_expected_type {
sym := c.table.sym(c.expected_type)
map_type = c.expected_type
}
if node.has_update_expr {
update_type := c.expr(mut node.update_expr)
if map_type != ast.void_type {
if update_type != map_type {
msg := c.expected_msg(update_type, map_type)
c.error('invalid map update: ${msg}', node.update_expr_pos)
}
} else if c.table.sym(update_type).kind != .map {
c.error('invalid map update: non-map type', node.update_expr_pos)
} else {
map_type = update_type
}
}

mut map_key_type := ast.void_type
mut map_val_type := ast.void_type
if map_type != ast.void_type {
sym := c.table.sym(map_type)
info := sym.map_info()
key0_type = c.unwrap_generic(info.key_type)
val0_type = c.unwrap_generic(info.value_type)
} else {
map_key_type = info.key_type
map_val_type = info.value_type
} else if node.keys.len > 0 {
// `{'age': 20}`
mut key_ := node.keys[0]
key0_type = ast.mktyp(c.expr(mut key_))
map_key_type = ast.mktyp(c.expr(mut key_))
if node.keys[0].is_auto_deref_var() {
key0_type = key0_type.deref()
map_key_type = map_key_type.deref()
}
mut val_ := node.vals[0]
val0_type = ast.mktyp(c.expr(mut val_))
map_val_type = ast.mktyp(c.expr(mut val_))
if node.vals[0].is_auto_deref_var() {
val0_type = val0_type.deref()
map_val_type = map_val_type.deref()
}
node.val_types << map_val_type
if node.keys.len == 1 && map_val_type == ast.none_type {
c.error('map value cannot be only `none`', node.vals[0].pos())
}
node.val_types << val0_type
}
key0_type = c.unwrap_generic(key0_type)
val0_type = c.unwrap_generic(val0_type)
map_type := ast.new_type(c.table.find_or_register_map(key0_type, val0_type))
node.typ = map_type
node.key_type = key0_type
node.value_type = val0_type
map_value_sym := c.table.sym(node.value_type)
expecting_interface_map := map_value_sym.kind == .interface_
//
mut same_key_type := true
map_key_type = c.unwrap_generic(map_key_type)
map_val_type = c.unwrap_generic(map_val_type)

if node.keys.len == 1 && val0_type == ast.none_type {
c.error('map value cannot be only `none`', node.vals[0].pos())
}
node.typ = ast.new_type(c.table.find_or_register_map(map_key_type, map_val_type))
node.key_type = map_key_type
node.value_type = map_val_type

map_value_sym := c.table.sym(map_val_type)
expecting_interface_map := map_value_sym.kind == .interface_
mut same_key_type := true
for i, mut key in node.keys {
if i == 0 && !use_expected_type {
continue
if i == 0 && map_type == ast.void_type {
continue // skip first key/value if we processed them above
}
mut val := node.vals[i]
c.expected_type = key0_type
c.expected_type = map_key_type
key_type := c.expr(mut key)
c.expected_type = val0_type
c.expected_type = map_val_type
val_type := c.expr(mut val)
node.val_types << val_type
val_type_sym := c.table.sym(val_type)
if !c.check_types(key_type, key0_type) || (i == 0 && key_type.is_number()
&& key0_type.is_number() && key0_type != ast.mktyp(key_type)) {
msg := c.expected_msg(key_type, key0_type)
if !c.check_types(key_type, map_key_type)
|| (i == 0 && key_type.is_number() && map_key_type.is_number()
&& map_key_type != ast.mktyp(key_type)) {
msg := c.expected_msg(key_type, map_key_type)
c.error('invalid map key: ${msg}', key.pos())
same_key_type = false
}
if expecting_interface_map {
if val_type == node.value_type {
if val_type == map_val_type {
continue
}
if val_type_sym.kind == .struct_
&& c.type_implements(val_type, node.value_type, val.pos()) {
&& c.type_implements(val_type, map_val_type, val.pos()) {
node.vals[i] = ast.CastExpr{
expr: val
typname: c.table.get_type_name(node.value_type)
typ: node.value_type
typname: c.table.get_type_name(map_val_type)
typ: map_val_type
expr_type: val_type
pos: val.pos()
}
continue
} else {
msg := c.expected_msg(val_type, node.value_type)
msg := c.expected_msg(val_type, map_val_type)
c.error('invalid map value: ${msg}', val.pos())
}
}
if val_type == ast.none_type && val0_type.has_flag(.option) {
if val_type == ast.none_type && map_val_type.has_flag(.option) {
continue
}
if !c.check_types(val_type, val0_type)
|| val0_type.has_flag(.option) != val_type.has_flag(.option)
|| (i == 0 && val_type.is_number() && val0_type.is_number()
&& val0_type != ast.mktyp(val_type)) {
msg := c.expected_msg(val_type, val0_type)
if !c.check_types(val_type, map_val_type)
|| map_val_type.has_flag(.option) != val_type.has_flag(.option)
|| (i == 0 && val_type.is_number() && map_val_type.is_number()
&& map_val_type != ast.mktyp(val_type)) {
msg := c.expected_msg(val_type, map_val_type)
c.error('invalid map value: ${msg}', val.pos())
}
}
Expand All @@ -547,7 +564,6 @@ fn (mut c Checker) map_init(mut node ast.MapInit) ast.Type {
c.check_dup_keys(node, i)
}
}
return map_type
}
return node.typ
}
Expand Down
14 changes: 14 additions & 0 deletions vlib/v/checker/tests/map_init_invalid_update.out
@@ -0,0 +1,14 @@
vlib/v/checker/tests/map_init_invalid_update.vv:8:6: error: invalid map update: non-map type
6 | foo := 'foo is a string'
7 | a := {
8 | ...foo // not a map
| ~~~
9 | 'a': 5
10 | 'b': 6
vlib/v/checker/tests/map_init_invalid_update.vv:19:6: error: invalid map update: non-map type
17 | println(b)
18 | c := {
19 | ...Foo{9} // also not ok
| ~~~~~~
20 | }
21 | println(c)
22 changes: 22 additions & 0 deletions vlib/v/checker/tests/map_init_invalid_update.vv
@@ -0,0 +1,22 @@
struct Foo {
x int
}

fn main() {
foo := 'foo is a string'
a := {
...foo // not a map
'a': 5
'b': 6
}
println(a)
b := {
...(a.clone()) // ok
'c': 99
}
println(b)
c := {
...Foo{9} // also not ok
}
println(c)
}
49 changes: 49 additions & 0 deletions vlib/v/checker/tests/map_init_wrong_expected_type.out
@@ -0,0 +1,49 @@
vlib/v/checker/tests/map_init_wrong_expected_type.vv:18:9: error: invalid map value: expected `int`, not `string`
16 | b := Bar{
17 | m: {
18 | 'a': '5'
| ~~~
19 | 'b': '6'
20 | } // bad
vlib/v/checker/tests/map_init_wrong_expected_type.vv:19:9: error: invalid map value: expected `int`, not `string`
17 | m: {
18 | 'a': '5'
19 | 'b': '6'
| ~~~
20 | } // bad
21 | }
vlib/v/checker/tests/map_init_wrong_expected_type.vv:36:9: error: invalid map value: expected `int`, not `string`
34 | m: {
35 | ...a.m
36 | 'c': '7'
| ~~~
37 | } // bad values
38 | }
vlib/v/checker/tests/map_init_wrong_expected_type.vv:43:9: error: invalid map update: expected `map[string]int`, not `map[string]string`
41 | f := Bar{
42 | m: {
43 | ...x.m
| ^
44 | } // bad update
45 | }
vlib/v/checker/tests/map_init_wrong_expected_type.vv:48:9: error: invalid map update: expected `map[string]int`, not `map[string]string`
46 | g := Bar{
47 | m: {
48 | ...x.m
| ^
49 | 'c': 7
50 | } // bad update, ok values
vlib/v/checker/tests/map_init_wrong_expected_type.vv:54:9: error: invalid map update: expected `map[string]int`, not `map[string]string`
52 | h := Bar{
53 | m: {
54 | ...x.m
| ^
55 | 'c': '7'
56 | } // bad update, bad values
vlib/v/checker/tests/map_init_wrong_expected_type.vv:55:9: error: invalid map value: expected `int`, not `string`
53 | m: {
54 | ...x.m
55 | 'c': '7'
| ~~~
56 | } // bad update, bad values
57 | }

0 comments on commit c4b8036

Please sign in to comment.