From 144fc16fd476a08b63c7d00aaebde4d06696c225 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 25 Mar 2026 16:42:17 +0300 Subject: [PATCH] js: fix tos functions support (fixes #26304) --- vlib/builtin/js/string.js.v | 42 +++++++++++++++++++++++++++++--- vlib/builtin/js/string_test.js.v | 8 ++++++ vlib/v/gen/js/js.v | 42 ++++++++++++++++++++++---------- 3 files changed, 76 insertions(+), 16 deletions(-) diff --git a/vlib/builtin/js/string.js.v b/vlib/builtin/js/string.js.v index 7a9be3f43..7550521bb 100644 --- a/vlib/builtin/js/string.js.v +++ b/vlib/builtin/js/string.js.v @@ -8,6 +8,8 @@ pub: len int } +interface TosSource {} + pub fn (s string) runes() []rune { ret := JS.makeEmptyArray() #for (r of s.str) array_push(ret,new rune(r),false); @@ -1122,9 +1124,43 @@ pub fn (_rune string) utf32_code() int { return res } -pub fn tos(jsstr JS.String) string { - res := '' - #res.str = jsstr +// tos converts a JS string or an addressable byte range into a V string. +pub fn tos(source TosSource, lens ...int) string { + mut res := '' + #const source_value = source instanceof $ref ? source.valueOf() : source + if lens.len == 0 { + #if (source_value instanceof string) { + #res.str = source_value.str + #} else if (typeof source_value === 'string' || source_value instanceof String) { + #res.str = source_value.toString() + #} else { + panic('tos(): unsupported source in JS backend') + #} + + return res + } + if lens.len != 1 { + panic('tos(): expected exactly one length argument') + } + len := lens[0] + if len < 0 { + panic('tos(): negative length') + } + #if (source_value === null || source_value === undefined) { + panic('tos(): nil string') + #} + #if (source && source._v_array !== undefined && source._v_index !== undefined) { + #const start = source._v_index.valueOf() + #for (let i = 0; i < len.valueOf(); ++i) res.str += String.fromCharCode(source._v_array.arr.get(new int(start + i)).valueOf()) + #} else if (source_value.arr !== undefined && typeof source_value.arr.get === 'function') { + #for (let i = 0; i < len.valueOf(); ++i) res.str += String.fromCharCode(source_value.arr.get(new int(i)).valueOf()) + #} else if (source_value instanceof string) { + #res.str = source_value.str.slice(0, len.valueOf()) + #} else if (typeof source_value === 'string' || source_value instanceof String) { + #res.str = source_value.toString().slice(0, len.valueOf()) + #} else { + panic('tos(): unsupported source in JS backend') + #} return res } diff --git a/vlib/builtin/js/string_test.js.v b/vlib/builtin/js/string_test.js.v index 8a504947f..ba5ba42e2 100644 --- a/vlib/builtin/js/string_test.js.v +++ b/vlib/builtin/js/string_test.js.v @@ -1004,3 +1004,11 @@ fn test_js_string() { assert s.startsWith(js'hello') == JS.Boolean(true) assert s.endsWith(js'V') == JS.Boolean(true) } + +fn test_tos_from_u8_ptr() { + mut buf := [u8(`0`), `0`, `0`, `0`, `-`, `0`, `0`, `-`, `0`, `0`, `T`, `0`, `0`, `:`, `0`, + `0`, `:`, `0`, `0`, `.`, `0`, `0`, `0`, `0`, `0`, `0`, `0`, `0`, `0`, `Z`]! + s := unsafe { tos(&buf[0], buf.len) } + + assert s == '0000-00-00T00:00:00.000000000Z' +} diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index 99d9b2eb7..45f91d860 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -458,6 +458,7 @@ pub fn (mut g JsGen) init() { } g.definitions.writeln('function \$ref(value) { if (value instanceof \$ref) { return value; } this.val = value; } ') g.definitions.writeln('\$ref.prototype.valueOf = function() { return this.val; } ') + g.definitions.writeln('function \$ref_index(value, parent, index) { let ref = new \$ref(value); ref._v_array = parent; ref._v_index = index; return ref; } ') if g.pref.backend != .js_node { g.definitions.writeln('const \$process = {') g.definitions.writeln(' arch: "js",') @@ -1021,19 +1022,34 @@ fn (mut g JsGen) expr(node_ ast.Expr) { ast.PrefixExpr { if node.op in [.amp, .mul] { if node.op == .amp { - // if !node.right_type.is_pointer() { - // kind of weird way to handle references but it allows us to access type methods easily. - /* - g.write('(function(x) {') - g.write(' return { val: x, __proto__: Object.getPrototypeOf(x), valueOf: function() { return this.val; } }})( ') - g.expr(node.right) - g.write(')')*/ - g.write('new \$ref(') - g.expr(node.right) - g.write(')') - //} else { - // g.expr(node.right) - // } + mut is_array_index_ref := false + mut right := node.right + if mut right is ast.IndexExpr { + mut parent_type := right.left_type + if parent_type.is_ptr() { + parent_type = parent_type.deref() + } + parent_sym := g.table.final_sym(parent_type) + if parent_sym.kind in [.array, .array_fixed] && !right.is_map { + is_array_index_ref = true + g.write('\$ref_index(') + g.expr(right) + g.write(', ') + g.expr(right.left) + if right.left_type.is_ptr() { + g.write('.valueOf()') + } + g.write(', ') + g.expr(right.index) + g.write(')') + } + } + if !is_array_index_ref { + // kind of weird way to handle references but it allows us to access type methods easily. + g.write('new \$ref(') + g.expr(node.right) + g.write(')') + } } else { g.write('(') g.expr(node.right) -- 2.39.5