diff --git a/CHANGELOG.md b/CHANGELOG.md index b591c1e..8476681 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG - go/js +## v1.5.6 (2026-06-21) +- **可变参数桥接修复**: `wrapGoFunc` 修复对 Go 可变参数(`...any`)的桥接处理。之前将 JS 剩余参数错误打包为单一切片元素,导致 `Call` 二次嵌套;现在改为逐个追加到 `goArgs` 尾部,由 `reflect.Call` 自动构建可变切片,确保 JS 调用 `redis.Do('HSET', a, b, c)` 正确展开。 +- **新增测试**: `TestBridgeVariadic` 覆盖可变参数 0/1/多参数场景。 + ## v1.5.5 (2026-06-21) - **JS 对齐**: 重构 JS 运行时的报错堆栈提取逻辑,采用高效的字符解析替换正则表达式,并支持 `jsmod.MakeError` 错误包装在桥接层还原出真实的 Go 运行时调用堆栈。 - **依赖更新**: 升级依赖 `jsmod` 至 `v1.5.3`,`cast` 至 `v1.5.3`,`log` 至 `v1.5.8`。 diff --git a/bridge.go b/bridge.go index c10faf4..4d587b2 100644 --- a/bridge.go +++ b/bridge.go @@ -52,9 +52,10 @@ func wrapGoFunc(vm *goja.Runtime, fn any, isUnsafe bool) goja.Value { // 2. Prepare Arguments numIn := t.NumIn() - goArgs := make([]reflect.Value, numIn) + goArgs := make([]reflect.Value, 0, numIn+len(call.Arguments)) jsArgs := call.Arguments jsArgIdx := 0 + isVariadic := t.IsVariadic() for i := 0; i < numIn; i++ { argType := t.In(i) @@ -65,7 +66,7 @@ func wrapGoFunc(vm *goja.Runtime, fn any, isUnsafe bool) goja.Value { if ctx == nil { ctx = context.Background() } - goArgs[i] = reflect.ValueOf(ctx) + goArgs = append(goArgs, reflect.ValueOf(ctx)) continue } @@ -80,24 +81,42 @@ func wrapGoFunc(vm *goja.Runtime, fn any, isUnsafe bool) goja.Value { if logger == nil { logger = log.DefaultLogger } - goArgs[i] = reflect.ValueOf(logger) + goArgs = append(goArgs, reflect.ValueOf(logger)) + continue + } + + // 可变参数:剩余 JS 参数直接追加到 goArgs,不由 Call 再包一层 + if isVariadic && i == numIn-1 { + elemType := argType.Elem() + for jsArgIdx < len(jsArgs) { + exported := jsArgs[jsArgIdx].Export() + expV := reflect.ValueOf(exported) + if !expV.IsValid() || !expV.Type().AssignableTo(elemType) { + elem := reflect.New(elemType).Elem() + cast.Convert(elem.Addr().Interface(), exported) + expV = elem + } + goArgs = append(goArgs, expV) + jsArgIdx++ + } continue } // Normal JS Argument with go/cast - goArgs[i] = reflect.New(argType).Elem() + goArg := reflect.New(argType).Elem() if jsArgIdx < len(jsArgs) { jsVal := jsArgs[jsArgIdx] exported := jsVal.Export() expV := reflect.ValueOf(exported) if expV.IsValid() && expV.Type().AssignableTo(argType) { - goArgs[i].Set(expV) + goArg.Set(expV) } else { - cast.Convert(goArgs[i].Addr().Interface(), exported) + cast.Convert(goArg.Addr().Interface(), exported) } jsArgIdx++ } + goArgs = append(goArgs, goArg) } // 3. Call the Go function diff --git a/bridge_test.go b/bridge_test.go index 3b1dd7a..10acb78 100644 --- a/bridge_test.go +++ b/bridge_test.go @@ -106,3 +106,42 @@ func TestBridgeOptionalParams(t *testing.T) { t.Errorf("Optional param failed (val), got %v", val.Export()) } } + +func TestBridgeVariadic(t *testing.T) { + vm := goja.New() + + variadicFn := func(cmd string, args ...any) string { + t.Logf("DEBUG variadicFn received: cmd=%q len(args)=%d args=%v argsType=%T", cmd, len(args), args, args) + for i, a := range args { + t.Logf("DEBUG args[%d] = %v (type=%T)", i, a, a) + } + return fmt.Sprintf("%s:%d:%v", cmd, len(args), args) + } + + vm.Set("vfn", wrapGoFunc(vm, variadicFn, false)) + + // 无额外参数 + val, _ := vm.RunString(`vfn("PING")`) + t.Logf("DEBUG vfn(PING) result: %v", val.Export()) + if val.Export() != "PING:0:[]" { + t.Errorf("Variadic zero args failed, got %v", val.Export()) + } + + // 一个额外参数 + val, _ = vm.RunString(`vfn("GET", "key1")`) + t.Logf("DEBUG vfn(GET, key1) result: %v", val.Export()) + if val.Export() != "GET:1:[key1]" { + t.Errorf("Variadic one arg failed, got %v", val.Export()) + } + + // 多个额外参数 + val, _ = vm.RunString(`vfn("HSET", "key1", "field1", "val1")`) + result := val.Export().(string) + t.Logf("DEBUG vfn(HSET, key1, field1, val1) result: %v", result) + if !strings.Contains(result, "HSET:3:") { + t.Errorf("Variadic multi args failed, got %v", result) + } + if !strings.Contains(result, "key1") || !strings.Contains(result, "field1") || !strings.Contains(result, "val1") { + t.Errorf("Variadic args content wrong, got %v", result) + } +}