Skip to content

Commit 90fdf10

Browse files
authored
jsgen: implement Map.keys() and Map.values() methods (fix #24209) (#24608)
1 parent 3ecffe6 commit 90fdf10

File tree

4 files changed

+228
-13
lines changed

4 files changed

+228
-13
lines changed

vlib/builtin/js/map.js.v

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,20 @@ pub fn (mut m map) delete(key JS.Any) {
4747

4848
pub fn (m &map) free() {}
4949

50+
pub fn (m map) keys() array {
51+
ret := JS.makeEmptyArray()
52+
#for (var key in m.map) array_push(ret,new string(`${key}`),false);
53+
54+
return ret
55+
}
56+
57+
pub fn (m map) values() array {
58+
ret := JS.makeEmptyArray()
59+
#for (var key in m.map) array_push(ret,m.map[key],false);
60+
61+
return ret
62+
}
63+
5064
//#Object.defineProperty(map.prototype,"len",{get: function() { return this.map.size; }})
5165
#map.prototype.toString = function () {
5266
#function fmtKey(key) { return typeof key == 'string' ? '\'' + key + '\'' : key}

vlib/v/gen/js/fn.v

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -267,8 +267,11 @@ fn (mut g JsGen) method_call(node ast.CallExpr) {
267267
left_sym := g.table.sym(node.left_type)
268268
final_left_sym := g.table.final_sym(node.left_type)
269269

270-
if final_left_sym.kind == .array {
271-
if final_left_sym.kind == .array && it.name in ['map', 'filter'] {
270+
if final_left_sym.kind == .map && it.name in special_map_methods {
271+
g.gen_map_method_call(it)
272+
return
273+
} else if final_left_sym.kind == .array {
274+
if it.name in ['map', 'filter'] {
272275
g.expr(it.left)
273276
mut ltyp := it.left_type
274277
for ltyp.is_ptr() {
@@ -310,18 +313,17 @@ fn (mut g JsGen) method_call(node ast.CallExpr) {
310313
return
311314
}
312315

313-
if final_left_sym.kind == .array {
314-
if it.name in special_array_methods {
315-
g.gen_array_method_call(it)
316-
return
317-
}
316+
if it.name in special_array_methods {
317+
g.gen_array_method_call(it)
318+
return
318319
}
319-
}
320-
if final_left_sym.kind == .array
321-
&& node.name in ['repeat', 'sort_with_compare', 'free', 'push_many', 'trim', 'first', 'last', 'pop', 'clone', 'reverse', 'slice', 'pointers'] {
322-
if !(left_sym.info is ast.Alias && typ_sym.has_method(node.name)) {
323-
// `array_Xyz_clone` => `array_clone`
324-
receiver_type_name = 'array'
320+
321+
if node.name in ['repeat', 'sort_with_compare', 'free', 'push_many', 'trim', 'first', 'last',
322+
'pop', 'clone', 'reverse', 'slice', 'pointers'] {
323+
if !(left_sym.info is ast.Alias && typ_sym.has_method(node.name)) {
324+
// `array_Xyz_clone` => `array_clone`
325+
receiver_type_name = 'array'
326+
}
325327
}
326328
}
327329

vlib/v/gen/js/map.v

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
module js
2+
3+
import v.ast
4+
5+
const special_map_methods = [
6+
'keys',
7+
'values',
8+
]
9+
10+
fn (mut g JsGen) gen_map_method_call(node ast.CallExpr) {
11+
g.write('map_${node.name}(')
12+
g.expr(node.left)
13+
g.gen_deref_ptr(node.left_type)
14+
g.write(')')
15+
}

vlib/v/gen/js/tests/map.v

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
struct Point {
2+
x f64
3+
y f64
4+
}
5+
6+
fn generic_map[T](items map[string]T) []T {
7+
return items.values()
8+
}
9+
10+
fn generic_map_with_constraint[T](items map[string]T) []T {
11+
return items.values()
12+
}
13+
14+
fn generic_map_keys[T](items map[string]T) []string {
15+
return items.keys()
16+
}
17+
18+
fn map_any[T](items []T, cb fn (item T) bool) bool {
19+
for item in items {
20+
if cb(item) {
21+
return true
22+
}
23+
}
24+
return false
25+
}
26+
27+
fn test_map_values_method() {
28+
// testing map[int]stirng
29+
items_1 := {
30+
1: 'item_1'
31+
2: 'item_2'
32+
3: 'item_3'
33+
}
34+
assert items_1.values().len == 3
35+
for i, item in items_1.values() {
36+
assert item == 'item_${i + 1}'
37+
}
38+
39+
// testing map[string]int
40+
items_2 := {
41+
'item_1': 1
42+
'item_2': 2
43+
'item_3': 3
44+
'item_4': 4
45+
}
46+
assert items_2.values().len == 4
47+
for i, item in items_2.values() {
48+
assert item == i + 1
49+
}
50+
51+
// testing generics
52+
items_3 := {
53+
'a': 10
54+
'b': 20
55+
'c': 30
56+
}
57+
generic_values := generic_map(items_3)
58+
assert generic_values.len == 3
59+
assert generic_values.contains(10)
60+
assert generic_values.contains(20)
61+
assert generic_values.contains(30)
62+
63+
// testing empty map
64+
empty_map := map[string]int{}
65+
empty_values := empty_map.values()
66+
assert empty_values.len == 0
67+
68+
// testing map with complex types (struct)
69+
points := {
70+
'origin': Point{0.0, 0.0}
71+
'unit_x': Point{1.0, 0.0}
72+
'unit_y': Point{0.0, 1.0}
73+
}
74+
point_values := points.values()
75+
assert point_values.len == 3
76+
assert map_any(point_values, fn (point Point) bool {
77+
return point.x == 0.0 && point.y == 0.0
78+
})
79+
assert map_any(point_values, fn (point Point) bool {
80+
return point.x == 1.0 && point.y == 0.0
81+
})
82+
assert map_any(point_values, fn (point Point) bool {
83+
return point.x == 0.0 && point.y == 1.0
84+
})
85+
}
86+
87+
fn test_map_values_method_with_generic_constraints() {
88+
// test with string constraint
89+
string_map := {
90+
'first': 'hello'
91+
'second': 'world'
92+
}
93+
string_result := generic_map_with_constraint(string_map)
94+
assert string_result.len == 2
95+
assert string_result.contains('hello')
96+
assert string_result.contains('world')
97+
98+
// test with int constraint
99+
int_map := {
100+
'one': 1
101+
'two': 2
102+
}
103+
int_result := generic_map_with_constraint(int_map)
104+
assert int_result.len == 2
105+
assert int_result.contains(1)
106+
assert int_result.contains(2)
107+
}
108+
109+
fn test_map_keys_method() {
110+
// testing map[string]int keys
111+
items_2 := {
112+
'item_1': 1
113+
'item_2': 2
114+
'item_3': 3
115+
'item_4': 4
116+
}
117+
keys_2 := items_2.keys()
118+
assert keys_2.len == 4
119+
assert keys_2.contains('item_1')
120+
assert keys_2.contains('item_2')
121+
assert keys_2.contains('item_3')
122+
assert keys_2.contains('item_4')
123+
124+
// testing empty map keys
125+
empty_map := map[string]int{}
126+
empty_keys := empty_map.keys()
127+
assert empty_keys.len == 0
128+
129+
// testing map with single element keys
130+
single_item := {
131+
'only': 42
132+
}
133+
single_keys := single_item.keys()
134+
assert single_keys.len == 1
135+
assert single_keys[0] == 'only'
136+
137+
// testing map with complex types as values but simple keys
138+
points := {
139+
'origin': Point{0.0, 0.0}
140+
'unit_x': Point{1.0, 0.0}
141+
'unit_y': Point{0.0, 1.0}
142+
}
143+
point_keys := points.keys()
144+
assert point_keys.len == 3
145+
assert point_keys.contains('origin')
146+
assert point_keys.contains('unit_x')
147+
assert point_keys.contains('unit_y')
148+
}
149+
150+
fn test_map_keys_method_with_generic_constraints() {
151+
// test with string values
152+
string_map := {
153+
'first': 'hello'
154+
'second': 'world'
155+
'third': 'test'
156+
}
157+
string_keys := generic_map_keys(string_map)
158+
assert string_keys.len == 3
159+
assert string_keys.contains('first')
160+
assert string_keys.contains('second')
161+
assert string_keys.contains('third')
162+
163+
// test with struct values
164+
point_map := {
165+
'origin': Point{0.0, 0.0}
166+
'center': Point{5.0, 5.0}
167+
}
168+
point_keys := generic_map_keys(point_map)
169+
assert point_keys.len == 2
170+
assert point_keys.contains('origin')
171+
assert point_keys.contains('center')
172+
173+
// test with empty map
174+
empty_map := map[string]int{}
175+
empty_keys := generic_map_keys(empty_map)
176+
assert empty_keys.len == 0
177+
}
178+
179+
fn main() {
180+
test_map_values_method()
181+
test_map_values_method_with_generic_constraints()
182+
test_map_keys_method()
183+
test_map_keys_method_with_generic_constraints()
184+
}

0 commit comments

Comments
 (0)