jsgen: implement Map.keys() and Map.values() methods (fix #24209) (#24608)

This commit is contained in:
Gonzalo Chumillas 2025-05-30 03:54:44 +01:00 committed by GitHub
parent 3ecffe68ff
commit 90fdf102fc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 228 additions and 13 deletions

View file

@ -47,6 +47,20 @@ pub fn (mut m map) delete(key JS.Any) {
pub fn (m &map) free() {}
pub fn (m map) keys() array {
ret := JS.makeEmptyArray()
#for (var key in m.map) array_push(ret,new string(`${key}`),false);
return ret
}
pub fn (m map) values() array {
ret := JS.makeEmptyArray()
#for (var key in m.map) array_push(ret,m.map[key],false);
return ret
}
//#Object.defineProperty(map.prototype,"len",{get: function() { return this.map.size; }})
#map.prototype.toString = function () {
#function fmtKey(key) { return typeof key == 'string' ? '\'' + key + '\'' : key}

View file

@ -267,8 +267,11 @@ fn (mut g JsGen) method_call(node ast.CallExpr) {
left_sym := g.table.sym(node.left_type)
final_left_sym := g.table.final_sym(node.left_type)
if final_left_sym.kind == .array {
if final_left_sym.kind == .array && it.name in ['map', 'filter'] {
if final_left_sym.kind == .map && it.name in special_map_methods {
g.gen_map_method_call(it)
return
} else if final_left_sym.kind == .array {
if it.name in ['map', 'filter'] {
g.expr(it.left)
mut ltyp := it.left_type
for ltyp.is_ptr() {
@ -310,18 +313,17 @@ fn (mut g JsGen) method_call(node ast.CallExpr) {
return
}
if final_left_sym.kind == .array {
if it.name in special_array_methods {
g.gen_array_method_call(it)
return
}
if it.name in special_array_methods {
g.gen_array_method_call(it)
return
}
}
if final_left_sym.kind == .array
&& node.name in ['repeat', 'sort_with_compare', 'free', 'push_many', 'trim', 'first', 'last', 'pop', 'clone', 'reverse', 'slice', 'pointers'] {
if !(left_sym.info is ast.Alias && typ_sym.has_method(node.name)) {
// `array_Xyz_clone` => `array_clone`
receiver_type_name = 'array'
if node.name in ['repeat', 'sort_with_compare', 'free', 'push_many', 'trim', 'first', 'last',
'pop', 'clone', 'reverse', 'slice', 'pointers'] {
if !(left_sym.info is ast.Alias && typ_sym.has_method(node.name)) {
// `array_Xyz_clone` => `array_clone`
receiver_type_name = 'array'
}
}
}

15
vlib/v/gen/js/map.v Normal file
View file

@ -0,0 +1,15 @@
module js
import v.ast
const special_map_methods = [
'keys',
'values',
]
fn (mut g JsGen) gen_map_method_call(node ast.CallExpr) {
g.write('map_${node.name}(')
g.expr(node.left)
g.gen_deref_ptr(node.left_type)
g.write(')')
}

184
vlib/v/gen/js/tests/map.v Normal file
View file

@ -0,0 +1,184 @@
struct Point {
x f64
y f64
}
fn generic_map[T](items map[string]T) []T {
return items.values()
}
fn generic_map_with_constraint[T](items map[string]T) []T {
return items.values()
}
fn generic_map_keys[T](items map[string]T) []string {
return items.keys()
}
fn map_any[T](items []T, cb fn (item T) bool) bool {
for item in items {
if cb(item) {
return true
}
}
return false
}
fn test_map_values_method() {
// testing map[int]stirng
items_1 := {
1: 'item_1'
2: 'item_2'
3: 'item_3'
}
assert items_1.values().len == 3
for i, item in items_1.values() {
assert item == 'item_${i + 1}'
}
// testing map[string]int
items_2 := {
'item_1': 1
'item_2': 2
'item_3': 3
'item_4': 4
}
assert items_2.values().len == 4
for i, item in items_2.values() {
assert item == i + 1
}
// testing generics
items_3 := {
'a': 10
'b': 20
'c': 30
}
generic_values := generic_map(items_3)
assert generic_values.len == 3
assert generic_values.contains(10)
assert generic_values.contains(20)
assert generic_values.contains(30)
// testing empty map
empty_map := map[string]int{}
empty_values := empty_map.values()
assert empty_values.len == 0
// testing map with complex types (struct)
points := {
'origin': Point{0.0, 0.0}
'unit_x': Point{1.0, 0.0}
'unit_y': Point{0.0, 1.0}
}
point_values := points.values()
assert point_values.len == 3
assert map_any(point_values, fn (point Point) bool {
return point.x == 0.0 && point.y == 0.0
})
assert map_any(point_values, fn (point Point) bool {
return point.x == 1.0 && point.y == 0.0
})
assert map_any(point_values, fn (point Point) bool {
return point.x == 0.0 && point.y == 1.0
})
}
fn test_map_values_method_with_generic_constraints() {
// test with string constraint
string_map := {
'first': 'hello'
'second': 'world'
}
string_result := generic_map_with_constraint(string_map)
assert string_result.len == 2
assert string_result.contains('hello')
assert string_result.contains('world')
// test with int constraint
int_map := {
'one': 1
'two': 2
}
int_result := generic_map_with_constraint(int_map)
assert int_result.len == 2
assert int_result.contains(1)
assert int_result.contains(2)
}
fn test_map_keys_method() {
// testing map[string]int keys
items_2 := {
'item_1': 1
'item_2': 2
'item_3': 3
'item_4': 4
}
keys_2 := items_2.keys()
assert keys_2.len == 4
assert keys_2.contains('item_1')
assert keys_2.contains('item_2')
assert keys_2.contains('item_3')
assert keys_2.contains('item_4')
// testing empty map keys
empty_map := map[string]int{}
empty_keys := empty_map.keys()
assert empty_keys.len == 0
// testing map with single element keys
single_item := {
'only': 42
}
single_keys := single_item.keys()
assert single_keys.len == 1
assert single_keys[0] == 'only'
// testing map with complex types as values but simple keys
points := {
'origin': Point{0.0, 0.0}
'unit_x': Point{1.0, 0.0}
'unit_y': Point{0.0, 1.0}
}
point_keys := points.keys()
assert point_keys.len == 3
assert point_keys.contains('origin')
assert point_keys.contains('unit_x')
assert point_keys.contains('unit_y')
}
fn test_map_keys_method_with_generic_constraints() {
// test with string values
string_map := {
'first': 'hello'
'second': 'world'
'third': 'test'
}
string_keys := generic_map_keys(string_map)
assert string_keys.len == 3
assert string_keys.contains('first')
assert string_keys.contains('second')
assert string_keys.contains('third')
// test with struct values
point_map := {
'origin': Point{0.0, 0.0}
'center': Point{5.0, 5.0}
}
point_keys := generic_map_keys(point_map)
assert point_keys.len == 2
assert point_keys.contains('origin')
assert point_keys.contains('center')
// test with empty map
empty_map := map[string]int{}
empty_keys := generic_map_keys(empty_map)
assert empty_keys.len == 0
}
fn main() {
test_map_values_method()
test_map_values_method_with_generic_constraints()
test_map_keys_method()
test_map_keys_method_with_generic_constraints()
}