From f811cd4ffd1400bb01368f4b7bda53605e0135c9 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Tue, 14 Apr 2026 12:45:27 +0300 Subject: [PATCH] js: JS backend errors (fixes #16415) --- vlib/builtin/js/builtin.js.v | 30 +++++++++++++++++++ vlib/v/gen/js/array.v | 2 ++ vlib/v/gen/js/builtin_types.v | 17 +++++++++++ vlib/v/gen/js/js.v | 5 ++-- .../js/tests/js_runtime_regression_16415.v | 20 +++++++++++++ 5 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 vlib/v/gen/js/tests/js_runtime_regression_16415.v diff --git a/vlib/builtin/js/builtin.js.v b/vlib/builtin/js/builtin.js.v index 54c70a4e3..0fbafa8c4 100644 --- a/vlib/builtin/js/builtin.js.v +++ b/vlib/builtin/js/builtin.js.v @@ -94,10 +94,40 @@ fn js_stacktrace() string { return stacktrace } +// v_clone_value preserves standalone JS semantics for V clone methods. +#function v_clone_value(value) { +#if (value === null || value === undefined) return value; +#if (value instanceof $ref) return new $ref(v_clone_value(value.val)); +#if (value instanceof array) return array_clone(value); +#if (value instanceof map) { +#let cloned = {} +#for (const key in value.map) cloned[key] = { key: v_clone_value(value.map[key].key), val: v_clone_value(value.map[key].val) } +#return new map(cloned); +#} +#if (typeof value !== 'object') return value; +#if (typeof value.$toJS === 'function') return value; +#let cloned; +#try { +#cloned = typeof value.constructor === 'function' ? new value.constructor({}) : Object.create(Object.getPrototypeOf(value)); +#} catch (e) { +#cloned = Object.create(Object.getPrototypeOf(value) || Object.prototype); +#} +#for (const key of Object.keys(value)) cloned[key] = v_clone_value(value[key]); +#return cloned; +#} + pub fn print_backtrace() { println(js_stacktrace()) } +pub fn (a array) clone() array { + mut res := empty_array() + #const cloned = a.arr.arr.slice(a.arr.index_start.valueOf(), a.arr.index_start.valueOf() + a.len.valueOf()).map(v_clone_value) + #res = new array(new array_buffer({arr: cloned, len: new int(cloned.length), cap: new int(cloned.length), index_start: new int(0), has_slice: new bool(false)})) + + return res +} + // Check for nil value pub fn isnil(val voidptr) bool { res := false diff --git a/vlib/v/gen/js/array.v b/vlib/v/gen/js/array.v index 5bddcb338..6caec5b6d 100644 --- a/vlib/v/gen/js/array.v +++ b/vlib/v/gen/js/array.v @@ -277,6 +277,8 @@ fn (mut g JsGen) gen_array_sort(node ast.CallExpr) { g.array_sort_fn[compare_fn] = true g.definitions.writeln('function ${compare_fn}(a,b) {') + g.definitions.writeln('\ta = new \$ref(a);') + g.definitions.writeln('\tb = new \$ref(b);') c_condition := if comparison_type.sym.has_method('<') { '${g.styp(comparison_type.typ)}__lt(${left_expr}, ${right_expr})' } else if comparison_type.unaliased_sym.has_method('<') { diff --git a/vlib/v/gen/js/builtin_types.v b/vlib/v/gen/js/builtin_types.v index 4bf56930b..497bcaa92 100644 --- a/vlib/v/gen/js/builtin_types.v +++ b/vlib/v/gen/js/builtin_types.v @@ -33,6 +33,9 @@ fn (mut g JsGen) to_js_typ_val(t ast.Type) string { .float_literal { styp = '${prefix}${g.sym_to_js_typ(sym)}(0)' } + .char { + styp = '${prefix}${g.sym_to_js_typ(sym)}(0)' + } .bool { styp = '${prefix}${g.sym_to_js_typ(sym)}(false)' } @@ -84,6 +87,9 @@ fn (mut g JsGen) sym_to_js_typ(sym ast.TypeSymbol) string { .u8 { styp = 'u8' } + .char { + styp = 'char' + } .u16 { styp = 'u16' } @@ -456,6 +462,17 @@ fn (mut g JsGen) gen_builtin_type_defs() { to_jsval: '+this' ) } + 'char' { + g.gen_builtin_prototype( + typ_name: typ_name + default_value: 'new Number(0)' + constructor: 'let n = typeof(val) == "string" ? val.charCodeAt() : val instanceof string ? val.str.charCodeAt() : Math.round(Number(val)); n &= 0xff; this.val = n > 0x7f ? n - 0x100 : n' + value_of: 'this.val | 0' + to_string: 'new string(this.val + "")' + eq: 'new bool(self.valueOf() === other.valueOf())' + to_jsval: '+this' + ) + } 'rune' { g.gen_builtin_prototype( typ_name: typ_name diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index f820c2a8c..9096d05be 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -18,9 +18,10 @@ const js_reserved = ['await', 'break', 'case', 'catch', 'class', 'const', 'conti 'document', 'Promise'] // used to generate type structs const v_types = ['i8', 'i16', 'i32', 'int', 'i64', 'u8', 'u16', 'u32', 'u64', 'f32', 'f64', - 'int_literal', 'float_literal', 'bool', 'string', 'map', 'array', 'rune', 'any', 'voidptr'] + 'int_literal', 'float_literal', 'bool', 'string', 'map', 'array', 'rune', 'char', 'any', + 'voidptr'] const shallow_equatables = [ast.Kind.i8, .i16, .i32, .int, .i64, .u8, .u16, .u32, .u64, .f32, .f64, - .int_literal, .float_literal, .bool, .string] + .int_literal, .float_literal, .bool, .string, .char] const option_name = '_option' struct SourcemapHelper { diff --git a/vlib/v/gen/js/tests/js_runtime_regression_16415.v b/vlib/v/gen/js/tests/js_runtime_regression_16415.v new file mode 100644 index 000000000..5da212b73 --- /dev/null +++ b/vlib/v/gen/js/tests/js_runtime_regression_16415.v @@ -0,0 +1,20 @@ +fn main() { + mut nums := [[1, 2], [3, 4]] + nums2 := nums.clone() + nums[0][0] = 9 + assert nums2[0][0] == 1 + + mut m := { + 'a': 1 + 'b': 2 + } + for k, v in m { + assert m[k] == v + } + + x := char(0b11111111) + assert int(x) == -1 + + s := 'hello! world!' + assert s.replace_each(['!', ':)', 'hello', 'hey']) == 'hey:) world:)' +} -- 2.39.5