Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pkg/rt/generated.manifest
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ pkg/rt/core_compiled.lgb pkg/vm/map.go generator f8d314a14ca7c5c91055b7ec963d986
pkg/rt/core_compiled.lgb pkg/vm/meta_value.go generator 457b232d744935a2be249ab6d0a7b1029a6c7315086c0065fa6cb08e74bcead2
pkg/rt/core_compiled.lgb pkg/vm/multifn.go generator faabb5b0a048d17c744e7e8bed33087f7d671063f8b5e2142252674bf7576efe
pkg/rt/core_compiled.lgb pkg/vm/namespace.go generator b847271519c2b7eeba5bee173e31b7d4dd4c49361476ded23baf4a6d2cd5942b
pkg/rt/core_compiled.lgb pkg/vm/native_func.go generator 63b68016e0875f5b2dc6d3615297d7923dc74bece0676cca728cb84a929afdc9
pkg/rt/core_compiled.lgb pkg/vm/native_func.go generator 32527c009e38ed2f19724f9e4fe0035c56e4156e6c0e7310394b025689ee341a
pkg/rt/core_compiled.lgb pkg/vm/native_func_reflect.go generator ded0face3302f48463a8021aa78e318cc1e9eaa7ccff488e19c592c930ddc133
pkg/rt/core_compiled.lgb pkg/vm/native_func_tinygo.go generator c03c3ec4a5656b532ccb03aade7c9416e31413837c603d9ca75e3555ae850b29
pkg/rt/core_compiled.lgb pkg/vm/nil.go generator 6bde1028333ea2286660e67121034d02e730c9d06db271b1ef93ebae5c09f177
Expand Down Expand Up @@ -546,7 +546,7 @@ pkg/rt/core_go_lowered/ pkg/vm/map.go generator f8d314a14ca7c5c91055b7ec963d9869
pkg/rt/core_go_lowered/ pkg/vm/meta_value.go generator 457b232d744935a2be249ab6d0a7b1029a6c7315086c0065fa6cb08e74bcead2
pkg/rt/core_go_lowered/ pkg/vm/multifn.go generator faabb5b0a048d17c744e7e8bed33087f7d671063f8b5e2142252674bf7576efe
pkg/rt/core_go_lowered/ pkg/vm/namespace.go generator b847271519c2b7eeba5bee173e31b7d4dd4c49361476ded23baf4a6d2cd5942b
pkg/rt/core_go_lowered/ pkg/vm/native_func.go generator 63b68016e0875f5b2dc6d3615297d7923dc74bece0676cca728cb84a929afdc9
pkg/rt/core_go_lowered/ pkg/vm/native_func.go generator 32527c009e38ed2f19724f9e4fe0035c56e4156e6c0e7310394b025689ee341a
pkg/rt/core_go_lowered/ pkg/vm/native_func_reflect.go generator ded0face3302f48463a8021aa78e318cc1e9eaa7ccff488e19c592c930ddc133
pkg/rt/core_go_lowered/ pkg/vm/native_func_tinygo.go generator c03c3ec4a5656b532ccb03aade7c9416e31413837c603d9ca75e3555ae850b29
pkg/rt/core_go_lowered/ pkg/vm/nil.go generator 6bde1028333ea2286660e67121034d02e730c9d06db271b1ef93ebae5c09f177
Expand Down
2 changes: 1 addition & 1 deletion pkg/rt/generated.sums
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
# Digest of generated.manifest: generator input provenance plus declared
# file-output readiness records. Input checks and output-readiness queries
# interpret those record kinds separately.
c774c0ded555af88679076c9a86347ae597dc01354f727fbf2fed3421dce0315
909b16ed145ab5e7d9a04d1ed92ac7e7d8a322c771632df0d7adcd5c84589827
13 changes: 13 additions & 0 deletions pkg/vm/native_func.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,19 @@ func boxArgForReflect(v Value, target reflect.Type) (reflect.Value, error) {
var convErr error

if target.Kind() == reflect.Slice || target.Kind() == reflect.Array {
// TypedArray is the mutable array boundary: when its native backing
// slice already satisfies the Go parameter, pass that slice through so
// writes made by APIs such as io.Reader.Read remain visible to let-go.
// Incompatible element types and fixed arrays still use the ordinary
// per-element conversion below, as do persistent collections.
if target.Kind() == reflect.Slice {
if arr, ok := v.(*TypedArray); ok && arr != nil {
backing := reflect.ValueOf(arr.Unbox())
if usableAsReflectArg(backing, target) {
return backing, nil
}
}
}
if sq, ok := v.(Sequable); ok {
out := reflect.New(target).Elem()
err := unboxSliceInto(out, sq.Seq())
Expand Down
109 changes: 109 additions & 0 deletions pkg/vm/native_func_reflect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,115 @@ func TestBoxReflectFuncMultiReturn(t *testing.T) {
}
}

func TestBoxReflectFuncTypedArrayPreservesBackingSlice(t *testing.T) {
invoke := func(t *testing.T, fn any, arg Value) {
t.Helper()
boxed, err := NativeFnType.Box(fn)
if err != nil {
t.Fatalf("Box: %v", err)
}
if _, err := boxed.(*NativeFn).proxy([]Value{arg}); err != nil {
t.Fatalf("proxy: %v", err)
}
}

t.Run("byte array", func(t *testing.T) {
backing := []byte{1, 2}
arr := NewByteArrayFrom(backing)
sameBacking := false
invoke(t, func(xs []byte) {
sameBacking = &xs[0] == &backing[0]
xs[0] = 9
}, arr)
if !sameBacking || backing[0] != 9 || arr.Get(0) != Int(9) {
t.Fatalf("byte-array backing was copied: same=%v backing=%v value=%v", sameBacking, backing, arr.Get(0))
}
})

t.Run("int array", func(t *testing.T) {
backing := []int64{1, 2}
arr := NewIntArrayFrom(backing)
sameBacking := false
invoke(t, func(xs []int64) {
sameBacking = &xs[0] == &backing[0]
xs[0] = 9
}, arr)
if !sameBacking || backing[0] != 9 || arr.Get(0) != Int(9) {
t.Fatalf("int-array backing was copied: same=%v backing=%v value=%v", sameBacking, backing, arr.Get(0))
}
})

t.Run("double array", func(t *testing.T) {
backing := []float64{1, 2}
arr := NewFloatArrayFrom(backing)
sameBacking := false
invoke(t, func(xs []float64) {
sameBacking = &xs[0] == &backing[0]
xs[0] = 9.5
}, arr)
if !sameBacking || backing[0] != 9.5 || arr.Get(0) != Float(9.5) {
t.Fatalf("double-array backing was copied: same=%v backing=%v value=%v", sameBacking, backing, arr.Get(0))
}
})

t.Run("object array", func(t *testing.T) {
backing := []Value{Int(1), Int(2)}
arr := NewObjectArrayFrom(backing)
sameBacking := false
invoke(t, func(xs []Value) {
sameBacking = &xs[0] == &backing[0]
xs[0] = String("changed")
}, arr)
if !sameBacking || backing[0] != String("changed") || arr.Get(0) != String("changed") {
t.Fatalf("object-array backing was copied: same=%v backing=%v value=%v", sameBacking, backing, arr.Get(0))
}
})

t.Run("compatible named slice", func(t *testing.T) {
type namedBytes []byte
backing := []byte{1, 2}
arr := NewByteArrayFrom(backing)
sameBacking := false
invoke(t, func(xs namedBytes) {
sameBacking = &xs[0] == &backing[0]
xs[0] = 9
}, arr)
if !sameBacking || arr.Get(0) != Int(9) {
t.Fatalf("named slice lost typed-array backing: same=%v value=%v", sameBacking, arr.Get(0))
}
})
}

func TestBoxReflectFuncPersistentCollectionsStillConvertToSlices(t *testing.T) {
for _, tt := range []struct {
name string
arg Value
}{
{name: "vector", arg: NewArrayVector([]Value{Int(1), Int(2)})},
{name: "list", arg: NewList([]Value{Int(1), Int(2)})},
} {
t.Run(tt.name, func(t *testing.T) {
boxed, err := NativeFnType.Box(func(xs []int) int {
xs[0] = 9
return xs[0] + xs[1]
})
if err != nil {
t.Fatalf("Box: %v", err)
}
got, err := boxed.(*NativeFn).proxy([]Value{tt.arg})
if err != nil {
t.Fatalf("proxy: %v", err)
}
if got != Int(11) {
t.Fatalf("converted slice result = %v, want 11", got)
}
if first := tt.arg.(Sequable).Seq().First(); first != Int(1) {
t.Fatalf("persistent source was mutated through copied []int: %v", first)
}
})
}
}

// func() error must keep returning the error as a VALUE rather than throwing.
// Every reflect-boxed Close/Write/Flush has this shape, so peeling here would
// silently change how a large amount of existing interop behaves. The peel
Expand Down
15 changes: 15 additions & 0 deletions test/typed_array_compat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,21 @@ func TestTypedArraysAcceptEmptySeqableInputs(t *testing.T) {
}
}

func TestByteArrayMutationSurvivesGoInterop(t *testing.T) {
got, err := evalCoreCompat(`(do
(require '[io :as io])
(let [r (io/string-reader "ABC")
b (byte-array 3)
n (.Read r b)]
[n (vec b)]))`)
if err != nil {
t.Fatal(err)
}
if got.String() != "[3 [65 66 67]]" {
t.Fatalf("Go mutation must remain visible through the typed array: got %s", got)
}
}

func TestContainsRejectsUnsupportedCollectionsAndArrayKeys(t *testing.T) {
cases := map[string]string{
"array nil key": `(contains? (int-array [0 1 2]) nil)`,
Expand Down
Loading