fix(js): 修复可变参数桥接,JS 剩余参数逐个追加而非打包为单一切片(by AI)

Co-Authored-By: deepseek-v4-pro[1m] <deepseek-ai@claude-code-best.win>
This commit is contained in:
AI Engineer 2026-06-21 20:31:36 +08:00
parent 5ade0d0981
commit c0fa98a5e1
3 changed files with 68 additions and 6 deletions

View File

@ -1,5 +1,9 @@
# CHANGELOG - go/js # 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) ## v1.5.5 (2026-06-21)
- **JS 对齐**: 重构 JS 运行时的报错堆栈提取逻辑,采用高效的字符解析替换正则表达式,并支持 `jsmod.MakeError` 错误包装在桥接层还原出真实的 Go 运行时调用堆栈。 - **JS 对齐**: 重构 JS 运行时的报错堆栈提取逻辑,采用高效的字符解析替换正则表达式,并支持 `jsmod.MakeError` 错误包装在桥接层还原出真实的 Go 运行时调用堆栈。
- **依赖更新**: 升级依赖 `jsmod``v1.5.3``cast``v1.5.3``log``v1.5.8` - **依赖更新**: 升级依赖 `jsmod``v1.5.3``cast``v1.5.3``log``v1.5.8`

View File

@ -52,9 +52,10 @@ func wrapGoFunc(vm *goja.Runtime, fn any, isUnsafe bool) goja.Value {
// 2. Prepare Arguments // 2. Prepare Arguments
numIn := t.NumIn() numIn := t.NumIn()
goArgs := make([]reflect.Value, numIn) goArgs := make([]reflect.Value, 0, numIn+len(call.Arguments))
jsArgs := call.Arguments jsArgs := call.Arguments
jsArgIdx := 0 jsArgIdx := 0
isVariadic := t.IsVariadic()
for i := 0; i < numIn; i++ { for i := 0; i < numIn; i++ {
argType := t.In(i) argType := t.In(i)
@ -65,7 +66,7 @@ func wrapGoFunc(vm *goja.Runtime, fn any, isUnsafe bool) goja.Value {
if ctx == nil { if ctx == nil {
ctx = context.Background() ctx = context.Background()
} }
goArgs[i] = reflect.ValueOf(ctx) goArgs = append(goArgs, reflect.ValueOf(ctx))
continue continue
} }
@ -80,24 +81,42 @@ func wrapGoFunc(vm *goja.Runtime, fn any, isUnsafe bool) goja.Value {
if logger == nil { if logger == nil {
logger = log.DefaultLogger 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 continue
} }
// Normal JS Argument with go/cast // Normal JS Argument with go/cast
goArgs[i] = reflect.New(argType).Elem() goArg := reflect.New(argType).Elem()
if jsArgIdx < len(jsArgs) { if jsArgIdx < len(jsArgs) {
jsVal := jsArgs[jsArgIdx] jsVal := jsArgs[jsArgIdx]
exported := jsVal.Export() exported := jsVal.Export()
expV := reflect.ValueOf(exported) expV := reflect.ValueOf(exported)
if expV.IsValid() && expV.Type().AssignableTo(argType) { if expV.IsValid() && expV.Type().AssignableTo(argType) {
goArgs[i].Set(expV) goArg.Set(expV)
} else { } else {
cast.Convert(goArgs[i].Addr().Interface(), exported) cast.Convert(goArg.Addr().Interface(), exported)
} }
jsArgIdx++ jsArgIdx++
} }
goArgs = append(goArgs, goArg)
} }
// 3. Call the Go function // 3. Call the Go function

View File

@ -106,3 +106,42 @@ func TestBridgeOptionalParams(t *testing.T) {
t.Errorf("Optional param failed (val), got %v", val.Export()) 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)
}
}