From 18a08f00f7df3352af1f40f60e61618645fd586a Mon Sep 17 00:00:00 2001 From: Moses Narrow <36607567+0pcom@users.noreply.github.com> Date: Tue, 14 Jul 2026 16:57:16 -0500 Subject: [PATCH] reflect: implement MakeChan TinyGo had MakeMap/MakeSlice but not MakeChan. Implement it via the runtime chanMake primitive (mirroring MakeMap), so packages that call reflect.MakeChan (e.g. github.com/ugorji/go/codec used by gin) compile and work. --- src/internal/reflectlite/value.go | 23 ++++++++++ src/reflect/value.go | 5 +++ src/reflect/value_test.go | 75 +++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+) diff --git a/src/internal/reflectlite/value.go b/src/internal/reflectlite/value.go index b353b1a92a..18ebe3df71 100644 --- a/src/internal/reflectlite/value.go +++ b/src/internal/reflectlite/value.go @@ -2208,6 +2208,9 @@ func hashmapMake(keySize, valueSize uintptr, sizeHint uintptr, alg uint8) unsafe //go:linkname hashmapMakeReflect runtime.hashmapMakeReflect func hashmapMakeReflect(keySize, valueSize, sizeHint uintptr, keyType unsafe.Pointer) unsafe.Pointer +//go:linkname chanMake runtime.chanMake +func chanMake(elementSize uintptr, bufSize uintptr) unsafe.Pointer + // MakeMapWithSize creates a new map with the specified type and initial space // for approximately n elements. func MakeMapWithSize(typ Type, n int) Value { @@ -2254,6 +2257,26 @@ func MakeMap(typ Type) Value { return MakeMapWithSize(typ, 8) } +// MakeChan creates a new channel with the specified type and buffer size. +func MakeChan(typ Type, size int) Value { + if typ.Kind() != Chan { + panic(&ValueError{Method: "MakeChan", Kind: typ.Kind()}) + } + if size < 0 { + panic("reflect.MakeChan: negative buffer size") + } + if typ.(*RawType).ChanDir() != BothDir { + panic("reflect.MakeChan: unidirectional channel type") + } + elem := typ.Elem().(*RawType) + ch := chanMake(elem.Size(), uintptr(size)) + return Value{ + typecode: typ.(*RawType), + value: ch, + flags: valueFlagExported, + } +} + func (v Value) Call(in []Value) []Value { panic("unimplemented: (reflect.Value).Call()") } diff --git a/src/reflect/value.go b/src/reflect/value.go index cf6952a770..66926698b0 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -201,6 +201,11 @@ func MakeMapWithSize(typ Type, n int) Value { return Value{reflectlite.MakeMapWithSize(toRawType(typ), n)} } +// MakeChan creates a new channel with the specified type and buffer size. +func MakeChan(typ Type, buffer int) Value { + return Value{reflectlite.MakeChan(toRawType(typ), buffer)} +} + func (v Value) Call(in []Value) []Value { panic("unimplemented: (reflect.Value).Call()") } diff --git a/src/reflect/value_test.go b/src/reflect/value_test.go index a3a4fba19a..957ba8a6ae 100644 --- a/src/reflect/value_test.go +++ b/src/reflect/value_test.go @@ -994,6 +994,81 @@ func TestTypeAssertPanic(t *testing.T) { }) } +func TestTinyMakeChan(t *testing.T) { + // Value.Send and Value.Recv are not implemented yet, so the channel is + // exercised through Interface(): that proves MakeChan returns a working + // channel rather than merely a value of the right kind. + t.Run("buffered", func(t *testing.T) { + v := MakeChan(TypeOf(make(chan int)), 2) + if got, want := v.Kind(), Chan; got != want { + t.Errorf("Kind()=%v, want %v", got, want) + } + if got, want := v.Cap(), 2; got != want { + t.Errorf("Cap()=%v, want %v", got, want) + } + if got, want := v.Len(), 0; got != want { + t.Errorf("Len()=%v, want %v", got, want) + } + + ch, ok := v.Interface().(chan int) + if !ok { + t.Fatalf("Interface() is %T, want chan int", v.Interface()) + } + ch <- 1 + ch <- 2 + if got, want := v.Len(), 2; got != want { + t.Errorf("Len()=%v after two sends, want %v", got, want) + } + if got, want := <-ch, 1; got != want { + t.Errorf("<-ch=%v, want %v", got, want) + } + if got, want := <-ch, 2; got != want { + t.Errorf("<-ch=%v, want %v", got, want) + } + }) + + t.Run("unbuffered", func(t *testing.T) { + v := MakeChan(TypeOf(make(chan string)), 0) + if got, want := v.Cap(), 0; got != want { + t.Errorf("Cap()=%v, want %v", got, want) + } + ch, ok := v.Interface().(chan string) + if !ok { + t.Fatalf("Interface() is %T, want chan string", v.Interface()) + } + go func() { ch <- "hello" }() + if got, want := <-ch, "hello"; got != want { + t.Errorf("<-ch=%q, want %q", got, want) + } + }) + + // The three cases below rely on recovering from a panic, which wasm + // cannot do yet without exceptions. + // TODO: drop this skip once tinygo-org/tinygo#5550 lands. + if runtime.GOOS == "wasip1" { + t.Skip("skipping panic cases: panic/recover on wasm needs #5550") + } + + t.Run("not a channel", func(t *testing.T) { + defer func() { recover() }() + MakeChan(TypeOf(0), 0) + t.Fatalf("MakeChan did not panic on a non-channel type") + }) + + t.Run("negative buffer", func(t *testing.T) { + defer func() { recover() }() + MakeChan(TypeOf(make(chan int)), -1) + t.Fatalf("MakeChan did not panic on a negative buffer size") + }) + + t.Run("directional", func(t *testing.T) { + defer func() { recover() }() + var recvOnly <-chan int + MakeChan(TypeOf(recvOnly), 0) + t.Fatalf("MakeChan did not panic on a unidirectional channel type") + }) +} + // Functions needed by all_test.go func IsRO(v Value) bool {