From 0835176dac8908b483183172a6c39418c67a6b22 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Mon, 30 Dec 2024 08:14:43 +1300 Subject: [PATCH 01/14] cm: remove bool from Discriminant type constraint --- cm/variant.go | 2 +- cm/variant_test.go | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/cm/variant.go b/cm/variant.go index 24703641..af309d9b 100644 --- a/cm/variant.go +++ b/cm/variant.go @@ -6,7 +6,7 @@ import "unsafe" // Use bool for 2-case variant types, result, or option types, uint8 where there are 256 or // fewer cases, uint16 for up to 65,536 cases, or uint32 for anything greater. type Discriminant interface { - bool | uint8 | uint16 | uint32 + uint8 | uint16 | uint32 } // Variant represents a loosely-typed Component Model variant. diff --git a/cm/variant_test.go b/cm/variant_test.go index 9977790c..0978e45c 100644 --- a/cm/variant_test.go +++ b/cm/variant_test.go @@ -17,18 +17,18 @@ func TestVariantLayout(t *testing.T) { size uintptr offset uintptr }{ - {"variant { string; string }", Variant[bool, string, string]{}, sizePlusAlignOf[string](), ptrSize}, - {"variant { bool; string }", Variant[bool, string, bool]{}, sizePlusAlignOf[string](), ptrSize}, - {"variant { string; _ }", Variant[bool, string, string]{}, sizePlusAlignOf[string](), ptrSize}, - {"variant { _; _ }", Variant[bool, string, struct{}]{}, sizePlusAlignOf[string](), ptrSize}, - {"variant { u64; u64 }", Variant[bool, uint64, uint64]{}, 16, alignOf[uint64]()}, - {"variant { u32; u64 }", Variant[bool, uint64, uint32]{}, 16, alignOf[uint64]()}, - {"variant { u64; u32 }", Variant[bool, uint64, uint32]{}, 16, alignOf[uint64]()}, - {"variant { u8; u64 }", Variant[bool, uint64, uint8]{}, 16, alignOf[uint64]()}, - {"variant { u64; u8 }", Variant[bool, uint64, uint8]{}, 16, alignOf[uint64]()}, - {"variant { u8; u32 }", Variant[bool, uint32, uint8]{}, 8, alignOf[uint32]()}, - {"variant { u32; u8 }", Variant[bool, uint32, uint8]{}, 8, alignOf[uint32]()}, - {"variant { [9]u8, u64 }", Variant[bool, [9]byte, uint64]{}, 24, alignOf[uint64]()}, + {"variant { string; string }", Variant[uint8, string, string]{}, sizePlusAlignOf[string](), ptrSize}, + {"variant { bool; string }", Variant[uint8, string, bool]{}, sizePlusAlignOf[string](), ptrSize}, + {"variant { string; _ }", Variant[uint8, string, string]{}, sizePlusAlignOf[string](), ptrSize}, + {"variant { _; _ }", Variant[uint8, string, struct{}]{}, sizePlusAlignOf[string](), ptrSize}, + {"variant { u64; u64 }", Variant[uint8, uint64, uint64]{}, 16, alignOf[uint64]()}, + {"variant { u32; u64 }", Variant[uint8, uint64, uint32]{}, 16, alignOf[uint64]()}, + {"variant { u64; u32 }", Variant[uint8, uint64, uint32]{}, 16, alignOf[uint64]()}, + {"variant { u8; u64 }", Variant[uint8, uint64, uint8]{}, 16, alignOf[uint64]()}, + {"variant { u64; u8 }", Variant[uint8, uint64, uint8]{}, 16, alignOf[uint64]()}, + {"variant { u8; u32 }", Variant[uint8, uint32, uint8]{}, 8, alignOf[uint32]()}, + {"variant { u32; u8 }", Variant[uint8, uint32, uint8]{}, 8, alignOf[uint32]()}, + {"variant { [9]u8, u64 }", Variant[uint8, [9]byte, uint64]{}, 24, alignOf[uint64]()}, } for _, tt := range tests { From 5a7d6f9f691cd1e9c3549ac8ba1775382874e3fb Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Mon, 30 Dec 2024 08:33:51 +1300 Subject: [PATCH 02/14] cm: prototype reverse index for string -> enum or variant cases --- cm/index.go | 60 ++++++++++++++++++++++++++++++++++++++++++++++++ cm/index_test.go | 10 ++++++++ 2 files changed, 70 insertions(+) create mode 100644 cm/index.go create mode 100644 cm/index_test.go diff --git a/cm/index.go b/cm/index.go new file mode 100644 index 00000000..c11775d9 --- /dev/null +++ b/cm/index.go @@ -0,0 +1,60 @@ +package cm + +import ( + "errors" + "unsafe" +) + +// IndexFunc is a function that returns an integer index of v. +// Used to reverse a string into a [variant] or [enum] case. +// +// [enum]: https://component-model.bytecodealliance.org/design/wit.html#enums +// [variant]: https://component-model.bytecodealliance.org/design/wit.html#variants +type IndexFunc[I Discriminant, V comparable] func(v V) (i I, ok bool) + +// Index returns an [IndexFunc] that indexes the values slice. +// Panics on error. +func MustIndex[I Discriminant, V comparable](values []V) IndexFunc[I, V] { + f, err := Index[I, V](values) + if err != nil { + panic(err) + } + return f +} + +// Index returns an [IndexFunc] that indexes the values slice. +// Return an error if len(values) is too large to be indexed by I. +func Index[I Discriminant, V comparable](values []V) (IndexFunc[I, V], error) { + max := 1<<(unsafe.Sizeof(I(0))*8) - 1 + if len(values) <= linearScanThreshold { + return linearIndex[I, V](values).indexOf, nil + } + if len(values) > max { + return nil, errors.New("len(values) exceeded index type") + } + m := make(mapIndex[I, V], len(values)) + for i, v := range values { + m[v] = I(i) + } + return m.indexOf, nil +} + +const linearScanThreshold = 16 + +type linearIndex[I Discriminant, V comparable] []V + +func (idx linearIndex[I, V]) indexOf(v V) (i I, ok bool) { + for i := 0; i < len(idx); i++ { + if idx[i] == v { + return I(i), true + } + } + return 0, false +} + +type mapIndex[I Discriminant, V comparable] map[V]I + +func (idx mapIndex[I, V]) indexOf(v V) (i I, ok bool) { + i, ok = idx[v] + return +} diff --git a/cm/index_test.go b/cm/index_test.go new file mode 100644 index 00000000..8a3aa151 --- /dev/null +++ b/cm/index_test.go @@ -0,0 +1,10 @@ +package cm + +import "testing" + +func TestIndex(t *testing.T) { + // tests := []struct { + // name string + // values []string + // } +} From 90ce9cc56b253fe71ee9ce924969c92363710d77 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Mon, 30 Dec 2024 16:58:03 +1300 Subject: [PATCH 03/14] cm: return error for zero-length index --- cm/index.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cm/index.go b/cm/index.go index c11775d9..cb7f5313 100644 --- a/cm/index.go +++ b/cm/index.go @@ -25,10 +25,13 @@ func MustIndex[I Discriminant, V comparable](values []V) IndexFunc[I, V] { // Index returns an [IndexFunc] that indexes the values slice. // Return an error if len(values) is too large to be indexed by I. func Index[I Discriminant, V comparable](values []V) (IndexFunc[I, V], error) { - max := 1<<(unsafe.Sizeof(I(0))*8) - 1 + if len(values) == 0 { + return nil, errors.New("zero-length index") + } if len(values) <= linearScanThreshold { return linearIndex[I, V](values).indexOf, nil } + max := 1<<(unsafe.Sizeof(I(0))*8) - 1 if len(values) > max { return nil, errors.New("len(values) exceeded index type") } From 64278480f19c95bd41b23ad29154bd139d864a67 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Mon, 30 Dec 2024 17:25:57 +1300 Subject: [PATCH 04/14] cm: simplify to string -> int --- cm/index.go | 62 ++++++++++++++++-------------------------------- cm/index_test.go | 29 ++++++++++++++++++---- 2 files changed, 45 insertions(+), 46 deletions(-) diff --git a/cm/index.go b/cm/index.go index cb7f5313..4c43691b 100644 --- a/cm/index.go +++ b/cm/index.go @@ -1,63 +1,43 @@ package cm -import ( - "errors" - "unsafe" -) - -// IndexFunc is a function that returns an integer index of v. +// IndexFunc is a function that returns an integer index of s. // Used to reverse a string into a [variant] or [enum] case. // // [enum]: https://component-model.bytecodealliance.org/design/wit.html#enums // [variant]: https://component-model.bytecodealliance.org/design/wit.html#variants -type IndexFunc[I Discriminant, V comparable] func(v V) (i I, ok bool) - -// Index returns an [IndexFunc] that indexes the values slice. -// Panics on error. -func MustIndex[I Discriminant, V comparable](values []V) IndexFunc[I, V] { - f, err := Index[I, V](values) - if err != nil { - panic(err) - } - return f -} +type IndexFunc func(s string) int -// Index returns an [IndexFunc] that indexes the values slice. -// Return an error if len(values) is too large to be indexed by I. -func Index[I Discriminant, V comparable](values []V) (IndexFunc[I, V], error) { - if len(values) == 0 { - return nil, errors.New("zero-length index") +// Index returns an [IndexFunc] that indexes the strings slice. +func Index(strings []string) IndexFunc { + if len(strings) <= linearScanThreshold { + return linearIndex[string](strings).indexOf } - if len(values) <= linearScanThreshold { - return linearIndex[I, V](values).indexOf, nil + m := make(mapIndex[string], len(strings)) + for i, v := range strings { + m[v] = i } - max := 1<<(unsafe.Sizeof(I(0))*8) - 1 - if len(values) > max { - return nil, errors.New("len(values) exceeded index type") - } - m := make(mapIndex[I, V], len(values)) - for i, v := range values { - m[v] = I(i) - } - return m.indexOf, nil + return m.indexOf } const linearScanThreshold = 16 -type linearIndex[I Discriminant, V comparable] []V +type linearIndex[V comparable] []V -func (idx linearIndex[I, V]) indexOf(v V) (i I, ok bool) { +func (idx linearIndex[V]) indexOf(v V) int { for i := 0; i < len(idx); i++ { if idx[i] == v { - return I(i), true + return i } } - return 0, false + return -1 } -type mapIndex[I Discriminant, V comparable] map[V]I +type mapIndex[V comparable] map[V]int -func (idx mapIndex[I, V]) indexOf(v V) (i I, ok bool) { - i, ok = idx[v] - return +func (idx mapIndex[V]) indexOf(v V) int { + i, ok := idx[v] + if !ok { + return -1 + } + return i } diff --git a/cm/index_test.go b/cm/index_test.go index 8a3aa151..8b5f37e9 100644 --- a/cm/index_test.go +++ b/cm/index_test.go @@ -1,10 +1,29 @@ package cm -import "testing" +import ( + "strings" + "testing" +) func TestIndex(t *testing.T) { - // tests := []struct { - // name string - // values []string - // } + tests := []struct { + name string + strings []string + }{ + {"nil", nil}, + {"empty slice", []string{}}, + {"a b c", strings.SplitAfter("abc", "")}, + {"a b c d e f g", strings.SplitAfter("abcdefg", "")}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f := Index(tt.strings) + for want, s := range tt.strings { + got := f(s) + if got != want { + t.Errorf("f(%q): got %d, expected %d", s, got, want) + } + } + }) + } } From 10e85c60c640fffe2f10af55ac64e1975500683d Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Tue, 31 Dec 2024 17:59:31 +1300 Subject: [PATCH 05/14] wit/bindgen: implement encoding.TextUnmarshaler for enum types --- .../wasi/filesystem/v0.2.0/types/types.wit.go | 40 +++++++++++++++++++ .../sockets/v0.2.0/network/network.wit.go | 27 +++++++++++++ .../wasi/sockets/v0.2.0/tcp/tcp.wit.go | 14 +++++++ wit/bindgen/generator.go | 11 +++++ 4 files changed, 92 insertions(+) diff --git a/tests/generated/wasi/filesystem/v0.2.0/types/types.wit.go b/tests/generated/wasi/filesystem/v0.2.0/types/types.wit.go index a6783462..fdfe5904 100755 --- a/tests/generated/wasi/filesystem/v0.2.0/types/types.wit.go +++ b/tests/generated/wasi/filesystem/v0.2.0/types/types.wit.go @@ -29,6 +29,7 @@ package types import ( + "errors" "go.bytecodealliance.org/cm" wallclock "tests/generated/wasi/clocks/v0.2.0/wall-clock" "tests/generated/wasi/io/v0.2.0/streams" @@ -122,6 +123,19 @@ func (e DescriptorType) String() string { return stringsDescriptorType[e] } +var indexDescriptorType = cm.Index(stringsDescriptorType[:]) + +// UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum +// case. Returns an error if the supplied text is not one of the enum cases. +func (e *DescriptorType) UnmarshalText(text []byte) error { + v := indexDescriptorType(string(text)) + if v < 0 { + return errors.New("unknown enum case") + } + *e = DescriptorType(v) + return nil +} + // DescriptorFlags represents the flags "wasi:filesystem/types@0.2.0#descriptor-flags". // // Descriptor flags. @@ -561,6 +575,19 @@ func (e ErrorCode) String() string { return stringsErrorCode[e] } +var indexErrorCode = cm.Index(stringsErrorCode[:]) + +// UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum +// case. Returns an error if the supplied text is not one of the enum cases. +func (e *ErrorCode) UnmarshalText(text []byte) error { + v := indexErrorCode(string(text)) + if v < 0 { + return errors.New("unknown enum case") + } + *e = ErrorCode(v) + return nil +} + // Advice represents the enum "wasi:filesystem/types@0.2.0#advice". // // File or memory access pattern advisory information. @@ -615,6 +642,19 @@ func (e Advice) String() string { return stringsAdvice[e] } +var indexAdvice = cm.Index(stringsAdvice[:]) + +// UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum +// case. Returns an error if the supplied text is not one of the enum cases. +func (e *Advice) UnmarshalText(text []byte) error { + v := indexAdvice(string(text)) + if v < 0 { + return errors.New("unknown enum case") + } + *e = Advice(v) + return nil +} + // MetadataHashValue represents the record "wasi:filesystem/types@0.2.0#metadata-hash-value". // // A 128-bit hash value, split into parts because wasm doesn't have a diff --git a/tests/generated/wasi/sockets/v0.2.0/network/network.wit.go b/tests/generated/wasi/sockets/v0.2.0/network/network.wit.go index 5263ce67..f7cdf5ea 100755 --- a/tests/generated/wasi/sockets/v0.2.0/network/network.wit.go +++ b/tests/generated/wasi/sockets/v0.2.0/network/network.wit.go @@ -4,6 +4,7 @@ package network import ( + "errors" "go.bytecodealliance.org/cm" ) @@ -182,6 +183,19 @@ func (e ErrorCode) String() string { return stringsErrorCode[e] } +var indexErrorCode = cm.Index(stringsErrorCode[:]) + +// UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum +// case. Returns an error if the supplied text is not one of the enum cases. +func (e *ErrorCode) UnmarshalText(text []byte) error { + v := indexErrorCode(string(text)) + if v < 0 { + return errors.New("unknown enum case") + } + *e = ErrorCode(v) + return nil +} + // IPAddressFamily represents the enum "wasi:sockets/network@0.2.0#ip-address-family". // // enum ip-address-family { @@ -208,6 +222,19 @@ func (e IPAddressFamily) String() string { return stringsIPAddressFamily[e] } +var indexIPAddressFamily = cm.Index(stringsIPAddressFamily[:]) + +// UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum +// case. Returns an error if the supplied text is not one of the enum cases. +func (e *IPAddressFamily) UnmarshalText(text []byte) error { + v := indexIPAddressFamily(string(text)) + if v < 0 { + return errors.New("unknown enum case") + } + *e = IPAddressFamily(v) + return nil +} + // IPv4Address represents the tuple "wasi:sockets/network@0.2.0#ipv4-address". // // type ipv4-address = tuple diff --git a/tests/generated/wasi/sockets/v0.2.0/tcp/tcp.wit.go b/tests/generated/wasi/sockets/v0.2.0/tcp/tcp.wit.go index ff669440..9af68ff7 100755 --- a/tests/generated/wasi/sockets/v0.2.0/tcp/tcp.wit.go +++ b/tests/generated/wasi/sockets/v0.2.0/tcp/tcp.wit.go @@ -4,6 +4,7 @@ package tcp import ( + "errors" "go.bytecodealliance.org/cm" monotonicclock "tests/generated/wasi/clocks/v0.2.0/monotonic-clock" "tests/generated/wasi/io/v0.2.0/poll" @@ -82,6 +83,19 @@ func (e ShutdownType) String() string { return stringsShutdownType[e] } +var indexShutdownType = cm.Index(stringsShutdownType[:]) + +// UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum +// case. Returns an error if the supplied text is not one of the enum cases. +func (e *ShutdownType) UnmarshalText(text []byte) error { + v := indexShutdownType(string(text)) + if v < 0 { + return errors.New("unknown enum case") + } + *e = ShutdownType(v) + return nil +} + // TCPSocket represents the imported resource "wasi:sockets/tcp@0.2.0#tcp-socket". // // A TCP socket resource. diff --git a/wit/bindgen/generator.go b/wit/bindgen/generator.go index 220dcad3..df8a5b0a 100644 --- a/wit/bindgen/generator.go +++ b/wit/bindgen/generator.go @@ -850,6 +850,17 @@ func (g *generator) enumRep(file *gen.File, dir wit.Direction, e *wit.Enum, goNa stringio.Write(&b, "return ", stringsName, "[e]\n") b.WriteString("}\n\n") + indexName := file.DeclareName("index" + GoName(goName, true)) + stringio.Write(&b, "var ", indexName, " = ", file.Import(g.opts.cmPackage), ".Index(", stringsName, "[:])\n") + + b.WriteString(formatDocComments("UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum case. Returns an error if the supplied text is not one of the enum cases.", true)) + stringio.Write(&b, "func (e *", goName, ") UnmarshalText(text []byte) error {\n") + stringio.Write(&b, "v := ", indexName, "(string(text))\n") + stringio.Write(&b, "if v < 0 { return ", file.Import("errors"), ".New(\"unknown enum case\") }\n") + stringio.Write(&b, "*e = ", goName, "(v)\n") + stringio.Write(&b, "return nil\n") + b.WriteString("}\n\n") + return b.String() } From 66f15dd0f00e8134b9fd4171ff8912d559b6a1eb Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Wed, 1 Jan 2025 11:00:01 +1300 Subject: [PATCH 06/14] cm: remove comment about bool Discriminant --- cm/variant.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cm/variant.go b/cm/variant.go index af309d9b..d0def34b 100644 --- a/cm/variant.go +++ b/cm/variant.go @@ -3,8 +3,7 @@ package cm import "unsafe" // Discriminant is the set of types that can represent the tag or discriminator of a variant. -// Use bool for 2-case variant types, result, or option types, uint8 where there are 256 or -// fewer cases, uint16 for up to 65,536 cases, or uint32 for anything greater. +// Use uint8 where there are 256 or fewer cases, uint16 for up to 65,536 cases, or uint32 for anything greater. type Discriminant interface { uint8 | uint16 | uint32 } From 479fd2034ebb61c9e0786b303622f24459747206 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Wed, 1 Jan 2025 11:20:53 +1300 Subject: [PATCH 07/14] wit/bindgen: add MarshalText to enum types --- .../wasi/filesystem/v0.2.0/types/types.wit.go | 15 +++++++ .../sockets/v0.2.0/network/network.wit.go | 10 +++++ .../wasi/sockets/v0.2.0/tcp/tcp.wit.go | 5 +++ tests/json/json_test.go | 41 +++++++++++++++++++ wit/bindgen/generator.go | 5 +++ 5 files changed, 76 insertions(+) create mode 100644 tests/json/json_test.go diff --git a/tests/generated/wasi/filesystem/v0.2.0/types/types.wit.go b/tests/generated/wasi/filesystem/v0.2.0/types/types.wit.go index fdfe5904..94e50240 100755 --- a/tests/generated/wasi/filesystem/v0.2.0/types/types.wit.go +++ b/tests/generated/wasi/filesystem/v0.2.0/types/types.wit.go @@ -125,6 +125,11 @@ func (e DescriptorType) String() string { var indexDescriptorType = cm.Index(stringsDescriptorType[:]) +// MarshalText implements [encoding.TextMarshaler]. +func (e DescriptorType) MarshalText() ([]byte, error) { + return []byte(e.String()), nil +} + // UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum // case. Returns an error if the supplied text is not one of the enum cases. func (e *DescriptorType) UnmarshalText(text []byte) error { @@ -577,6 +582,11 @@ func (e ErrorCode) String() string { var indexErrorCode = cm.Index(stringsErrorCode[:]) +// MarshalText implements [encoding.TextMarshaler]. +func (e ErrorCode) MarshalText() ([]byte, error) { + return []byte(e.String()), nil +} + // UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum // case. Returns an error if the supplied text is not one of the enum cases. func (e *ErrorCode) UnmarshalText(text []byte) error { @@ -644,6 +654,11 @@ func (e Advice) String() string { var indexAdvice = cm.Index(stringsAdvice[:]) +// MarshalText implements [encoding.TextMarshaler]. +func (e Advice) MarshalText() ([]byte, error) { + return []byte(e.String()), nil +} + // UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum // case. Returns an error if the supplied text is not one of the enum cases. func (e *Advice) UnmarshalText(text []byte) error { diff --git a/tests/generated/wasi/sockets/v0.2.0/network/network.wit.go b/tests/generated/wasi/sockets/v0.2.0/network/network.wit.go index f7cdf5ea..e658e6f4 100755 --- a/tests/generated/wasi/sockets/v0.2.0/network/network.wit.go +++ b/tests/generated/wasi/sockets/v0.2.0/network/network.wit.go @@ -185,6 +185,11 @@ func (e ErrorCode) String() string { var indexErrorCode = cm.Index(stringsErrorCode[:]) +// MarshalText implements [encoding.TextMarshaler]. +func (e ErrorCode) MarshalText() ([]byte, error) { + return []byte(e.String()), nil +} + // UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum // case. Returns an error if the supplied text is not one of the enum cases. func (e *ErrorCode) UnmarshalText(text []byte) error { @@ -224,6 +229,11 @@ func (e IPAddressFamily) String() string { var indexIPAddressFamily = cm.Index(stringsIPAddressFamily[:]) +// MarshalText implements [encoding.TextMarshaler]. +func (e IPAddressFamily) MarshalText() ([]byte, error) { + return []byte(e.String()), nil +} + // UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum // case. Returns an error if the supplied text is not one of the enum cases. func (e *IPAddressFamily) UnmarshalText(text []byte) error { diff --git a/tests/generated/wasi/sockets/v0.2.0/tcp/tcp.wit.go b/tests/generated/wasi/sockets/v0.2.0/tcp/tcp.wit.go index 9af68ff7..e0d58090 100755 --- a/tests/generated/wasi/sockets/v0.2.0/tcp/tcp.wit.go +++ b/tests/generated/wasi/sockets/v0.2.0/tcp/tcp.wit.go @@ -85,6 +85,11 @@ func (e ShutdownType) String() string { var indexShutdownType = cm.Index(stringsShutdownType[:]) +// MarshalText implements [encoding.TextMarshaler]. +func (e ShutdownType) MarshalText() ([]byte, error) { + return []byte(e.String()), nil +} + // UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum // case. Returns an error if the supplied text is not one of the enum cases. func (e *ShutdownType) UnmarshalText(text []byte) error { diff --git a/tests/json/json_test.go b/tests/json/json_test.go new file mode 100644 index 00000000..bde73511 --- /dev/null +++ b/tests/json/json_test.go @@ -0,0 +1,41 @@ +package json_test + +import ( + "encoding/json" + "testing" + + wallclock "tests/generated/wasi/clocks/v0.2.0/wall-clock" + "tests/generated/wasi/filesystem/v0.2.0/types" +) + +func TestRecordJSON(t *testing.T) { + var dt wallclock.DateTime + _ = dt +} + +func TestEnumMarshalJSON(t *testing.T) { + tests := []struct { + name string + v any + want string + wantErr bool + }{ + {"nil", nil, `null`, false}, + {"descriptor-type(directory)", types.DescriptorTypeDirectory, `"directory"`, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := json.Marshal(tt.v) + if tt.wantErr && err == nil { + t.Errorf("json.Marshal(%v): expected error, got nil error", tt.v) + return + } else if !tt.wantErr && err != nil { + t.Errorf("json.Marshal(%v): expected no error, got error: %v", tt.v, err) + return + } + if string(got) != tt.want { + t.Errorf("json.Marshal(%v): %s, expected %s", tt.v, string(got), tt.want) + } + }) + } +} diff --git a/wit/bindgen/generator.go b/wit/bindgen/generator.go index df8a5b0a..0017058a 100644 --- a/wit/bindgen/generator.go +++ b/wit/bindgen/generator.go @@ -853,6 +853,11 @@ func (g *generator) enumRep(file *gen.File, dir wit.Direction, e *wit.Enum, goNa indexName := file.DeclareName("index" + GoName(goName, true)) stringio.Write(&b, "var ", indexName, " = ", file.Import(g.opts.cmPackage), ".Index(", stringsName, "[:])\n") + b.WriteString(formatDocComments("MarshalText implements [encoding.TextMarshaler].", true)) + stringio.Write(&b, "func (e ", goName, ") MarshalText() ([]byte, error) {\n") + stringio.Write(&b, "return []byte(e.String()), nil\n") + b.WriteString("}\n\n") + b.WriteString(formatDocComments("UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum case. Returns an error if the supplied text is not one of the enum cases.", true)) stringio.Write(&b, "func (e *", goName, ") UnmarshalText(text []byte) error {\n") stringio.Write(&b, "v := ", indexName, "(string(text))\n") From 718fa7c4f17538955ae6f5afeccf2f939e1bb561 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Wed, 1 Jan 2025 16:10:03 +1300 Subject: [PATCH 08/14] tests/json: test json.Marshal and Unmarshal --- tests/json/json_test.go | 66 +++++++++++++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 15 deletions(-) diff --git a/tests/json/json_test.go b/tests/json/json_test.go index bde73511..d66939ee 100644 --- a/tests/json/json_test.go +++ b/tests/json/json_test.go @@ -2,40 +2,76 @@ package json_test import ( "encoding/json" + "reflect" "testing" wallclock "tests/generated/wasi/clocks/v0.2.0/wall-clock" "tests/generated/wasi/filesystem/v0.2.0/types" ) -func TestRecordJSON(t *testing.T) { - var dt wallclock.DateTime - _ = dt -} - -func TestEnumMarshalJSON(t *testing.T) { +func TestJSON(t *testing.T) { tests := []struct { name string - v any - want string + json string + into any + want any wantErr bool }{ - {"nil", nil, `null`, false}, - {"descriptor-type(directory)", types.DescriptorTypeDirectory, `"directory"`, false}, + { + "nil", + `null`, + ptr(ptr("")), + ptr((*string)(nil)), + false, + }, + { + "descriptor-type(block-device)", + `"block-device"`, + ptr(types.DescriptorType(0)), + ptr(types.DescriptorTypeBlockDevice), + false, + }, + { + "descriptor-type(directory)", + `"directory"`, + ptr(types.DescriptorType(0)), + ptr(types.DescriptorTypeDirectory), + false, + }, + { + "datetime", + `{"seconds":1,"nanoseconds":2}`, + &wallclock.DateTime{}, + &wallclock.DateTime{Seconds: 1, Nanoseconds: 2}, + false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := json.Marshal(tt.v) + err := json.Unmarshal([]byte(tt.json), &tt.into) if tt.wantErr && err == nil { - t.Errorf("json.Marshal(%v): expected error, got nil error", tt.v) + t.Errorf("json.Unmarshal(%q): expected error, got nil error", tt.json) return } else if !tt.wantErr && err != nil { - t.Errorf("json.Marshal(%v): expected no error, got error: %v", tt.v, err) + t.Errorf("json.Unmarshal(%q): expected no error, got error: %v", tt.json, err) + return + } + if !reflect.DeepEqual(tt.want, tt.into) { + t.Errorf("json.Unmarshal(%q): resulting value different (%v != %v)", tt.json, tt.into, tt.want) return } - if string(got) != tt.want { - t.Errorf("json.Marshal(%v): %s, expected %s", tt.v, string(got), tt.want) + got, err := json.Marshal(tt.into) + if err != nil { + t.Error(err) + return + } + if string(got) != tt.json { + t.Errorf("json.Marshal(%v): %s, expected %s", tt.into, string(got), tt.json) } }) } } + +func ptr[T any](v T) *T { + return &v +} From 64df8a1aaff3473380d1124e027fed849bf4425e Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Wed, 1 Jan 2025 16:25:15 +1300 Subject: [PATCH 09/14] tests/json: add list tests --- tests/json/json_test.go | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/tests/json/json_test.go b/tests/json/json_test.go index d66939ee..6ff48433 100644 --- a/tests/json/json_test.go +++ b/tests/json/json_test.go @@ -7,6 +7,8 @@ import ( wallclock "tests/generated/wasi/clocks/v0.2.0/wall-clock" "tests/generated/wasi/filesystem/v0.2.0/types" + + "go.bytecodealliance.org/cm" ) func TestJSON(t *testing.T) { @@ -45,6 +47,27 @@ func TestJSON(t *testing.T) { &wallclock.DateTime{Seconds: 1, Nanoseconds: 2}, false, }, + { + "empty list", + `[]`, + &cm.List[uint8]{}, + &cm.List[uint8]{}, + false, + }, + { + "list of bool", + `[false,true,false]`, + &cm.List[bool]{}, + ptr(cm.ToList([]bool{false, true, false})), + false, + }, + { + "list of u32", + `[1,2,3]`, + &cm.List[uint32]{}, + ptr(cm.ToList([]uint32{1, 2, 3})), + false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -56,16 +79,15 @@ func TestJSON(t *testing.T) { t.Errorf("json.Unmarshal(%q): expected no error, got error: %v", tt.json, err) return } - if !reflect.DeepEqual(tt.want, tt.into) { - t.Errorf("json.Unmarshal(%q): resulting value different (%v != %v)", tt.json, tt.into, tt.want) - return - } got, err := json.Marshal(tt.into) if err != nil { t.Error(err) return } if string(got) != tt.json { + if !reflect.DeepEqual(tt.want, tt.into) { + t.Errorf("json.Unmarshal(%q): resulting value different (%v != %v)", tt.json, tt.into, tt.want) + } t.Errorf("json.Marshal(%v): %s, expected %s", tt.into, string(got), tt.json) } }) From a03d7826a54df027081c60607577ad6ab610c52b Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sat, 4 Jan 2025 08:25:38 +1300 Subject: [PATCH 10/14] all: update CHANGELOG --- CHANGELOG.md | 1 + cm/CHANGELOG.md | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dbd87a60..30e60802 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ### Added - Initial support for Component Model [async](https://github.com/WebAssembly/component-model/blob/main/design/mvp/Async.md) types `stream`, `future`, and `error-context`. +- Initial support for JSON serialization of WIT `list`, `enum`, and `record` types. - [`wasm-tools`](https://crates.io/crates/wasm-tools) is now vendored as a WebAssembly module, executed using [Wazero](https://wazero.io/). This allows package `wit` and `wit-bindgen-go` to run on any supported platform without needing to separately install `wasm-tools`. ### Changed diff --git a/cm/CHANGELOG.md b/cm/CHANGELOG.md index 5dcb3dd2..047acf03 100644 --- a/cm/CHANGELOG.md +++ b/cm/CHANGELOG.md @@ -7,7 +7,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ### Added - Initial support for Component Model [async](https://github.com/WebAssembly/component-model/blob/main/design/mvp/Async.md) types `stream`, `future`, and `error-context`. -- Initial support for JSON serialization of WIT types, starting with `list` and `record`. +- Initial support for JSON serialization of WIT `list`, `enum`, and `record` types. +- Added `cm.Index` helper function for JSON unmarshaling support of `enum` types. + +### Changed + +- Breaking: package `cm`: removed `bool` from `Discriminant` type constraint. It was not used by code generation. ## [v0.1.0] — 2024-12-14 From 1f472a813256ecef48f573f54ec1f39d144a241b Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sat, 4 Jan 2025 14:14:40 +1300 Subject: [PATCH 11/14] cm: replace Index with CaseUnmarshaler Moves implementation of TextUnmarshaler from generated code to package cm. --- cm/CHANGELOG.md | 2 +- cm/case.go | 51 ++++++++++++++++++++++++++++++++++++++++++++++++ cm/case_test.go | 34 ++++++++++++++++++++++++++++++++ cm/index.go | 43 ---------------------------------------- cm/index_test.go | 29 --------------------------- 5 files changed, 86 insertions(+), 73 deletions(-) create mode 100644 cm/case.go create mode 100644 cm/case_test.go delete mode 100644 cm/index.go delete mode 100644 cm/index_test.go diff --git a/cm/CHANGELOG.md b/cm/CHANGELOG.md index 047acf03..cc08b9e5 100644 --- a/cm/CHANGELOG.md +++ b/cm/CHANGELOG.md @@ -8,7 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), - Initial support for Component Model [async](https://github.com/WebAssembly/component-model/blob/main/design/mvp/Async.md) types `stream`, `future`, and `error-context`. - Initial support for JSON serialization of WIT `list`, `enum`, and `record` types. -- Added `cm.Index` helper function for JSON unmarshaling support of `enum` types. +- Added `cm.CaseUnmarshaler` helper for text and JSON unmarshaling of `enum` and `variant` types. ### Changed diff --git a/cm/case.go b/cm/case.go new file mode 100644 index 00000000..65ade494 --- /dev/null +++ b/cm/case.go @@ -0,0 +1,51 @@ +package cm + +import "errors" + +// CaseUnmarshaler returns an function that can unmarshal text into +// [variant] or [enum] case T. +// +// [enum]: https://component-model.bytecodealliance.org/design/wit.html#enums +// [variant]: https://component-model.bytecodealliance.org/design/wit.html#variants +func CaseUnmarshaler[T ~uint8 | ~uint16 | ~uint32](cases []string) func(v *T, text []byte) error { + if len(cases) <= linearScanThreshold { + return func(v *T, text []byte) error { + if len(text) == 0 { + return errEmpty + } + s := string(text) + for i := 0; i < len(cases); i++ { + if cases[i] == s { + *v = T(i) + return nil + } + } + return errNoMatchingCase + } + } + + m := make(map[string]T, len(cases)) + for i, v := range cases { + m[v] = T(i) + } + + return func(v *T, text []byte) error { + if len(text) == 0 { + return errEmpty + } + s := string(text) + c, ok := m[s] + if !ok { + return errNoMatchingCase + } + *v = c + return nil + } +} + +const linearScanThreshold = 16 + +var ( + errEmpty = errors.New("empty text") + errNoMatchingCase = errors.New("no matching case") +) diff --git a/cm/case_test.go b/cm/case_test.go new file mode 100644 index 00000000..4711684d --- /dev/null +++ b/cm/case_test.go @@ -0,0 +1,34 @@ +package cm + +import ( + "strings" + "testing" +) + +func TestCaseUnmarshaler(t *testing.T) { + tests := []struct { + name string + cases []string + }{ + {"nil", nil}, + {"empty slice", []string{}}, + {"a b c", strings.SplitAfter("abc", "")}, + {"a b c d e f g", strings.SplitAfter("abcdefg", "")}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f := CaseUnmarshaler[uint8](tt.cases) + for want, c := range tt.cases { + var got uint8 + err := f(&got, []byte(c)) + if err != nil { + t.Error(err) + return + } + if got != uint8(want) { + t.Errorf("f(%q): got %d, expected %d", c, got, want) + } + } + }) + } +} diff --git a/cm/index.go b/cm/index.go deleted file mode 100644 index 4c43691b..00000000 --- a/cm/index.go +++ /dev/null @@ -1,43 +0,0 @@ -package cm - -// IndexFunc is a function that returns an integer index of s. -// Used to reverse a string into a [variant] or [enum] case. -// -// [enum]: https://component-model.bytecodealliance.org/design/wit.html#enums -// [variant]: https://component-model.bytecodealliance.org/design/wit.html#variants -type IndexFunc func(s string) int - -// Index returns an [IndexFunc] that indexes the strings slice. -func Index(strings []string) IndexFunc { - if len(strings) <= linearScanThreshold { - return linearIndex[string](strings).indexOf - } - m := make(mapIndex[string], len(strings)) - for i, v := range strings { - m[v] = i - } - return m.indexOf -} - -const linearScanThreshold = 16 - -type linearIndex[V comparable] []V - -func (idx linearIndex[V]) indexOf(v V) int { - for i := 0; i < len(idx); i++ { - if idx[i] == v { - return i - } - } - return -1 -} - -type mapIndex[V comparable] map[V]int - -func (idx mapIndex[V]) indexOf(v V) int { - i, ok := idx[v] - if !ok { - return -1 - } - return i -} diff --git a/cm/index_test.go b/cm/index_test.go deleted file mode 100644 index 8b5f37e9..00000000 --- a/cm/index_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package cm - -import ( - "strings" - "testing" -) - -func TestIndex(t *testing.T) { - tests := []struct { - name string - strings []string - }{ - {"nil", nil}, - {"empty slice", []string{}}, - {"a b c", strings.SplitAfter("abc", "")}, - {"a b c d e f g", strings.SplitAfter("abcdefg", "")}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - f := Index(tt.strings) - for want, s := range tt.strings { - got := f(s) - if got != want { - t.Errorf("f(%q): got %d, expected %d", s, got, want) - } - } - }) - } -} From ccac9f1051e1b2d72441c3ae7bce993e20f6264b Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sat, 4 Jan 2025 14:15:19 +1300 Subject: [PATCH 12/14] wit/bindgen: replace cm.Index with CaseUnmarshaler Moves implementation of TextUnmarshaler from generated code to package cm. --- wit/bindgen/generator.go | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/wit/bindgen/generator.go b/wit/bindgen/generator.go index 0017058a..6d14a19e 100644 --- a/wit/bindgen/generator.go +++ b/wit/bindgen/generator.go @@ -838,7 +838,7 @@ func (g *generator) enumRep(file *gen.File, dir wit.Direction, e *wit.Enum, goNa } b.WriteString(")\n\n") - stringsName := file.DeclareName("strings" + GoName(goName, true)) + stringsName := file.DeclareName("_" + GoName(goName, true) + "Strings") stringio.Write(&b, "var ", stringsName, " = [", fmt.Sprintf("%d", len(e.Cases)), "]string {\n") for _, c := range e.Cases { stringio.Write(&b, `"`, c.Name, `"`, ",\n") @@ -850,22 +850,20 @@ func (g *generator) enumRep(file *gen.File, dir wit.Direction, e *wit.Enum, goNa stringio.Write(&b, "return ", stringsName, "[e]\n") b.WriteString("}\n\n") - indexName := file.DeclareName("index" + GoName(goName, true)) - stringio.Write(&b, "var ", indexName, " = ", file.Import(g.opts.cmPackage), ".Index(", stringsName, "[:])\n") - b.WriteString(formatDocComments("MarshalText implements [encoding.TextMarshaler].", true)) stringio.Write(&b, "func (e ", goName, ") MarshalText() ([]byte, error) {\n") stringio.Write(&b, "return []byte(e.String()), nil\n") b.WriteString("}\n\n") + unmarshalName := file.DeclareName("_" + GoName(goName, true) + "UnmarshalCase") + b.WriteString(formatDocComments("UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum case. Returns an error if the supplied text is not one of the enum cases.", true)) stringio.Write(&b, "func (e *", goName, ") UnmarshalText(text []byte) error {\n") - stringio.Write(&b, "v := ", indexName, "(string(text))\n") - stringio.Write(&b, "if v < 0 { return ", file.Import("errors"), ".New(\"unknown enum case\") }\n") - stringio.Write(&b, "*e = ", goName, "(v)\n") - stringio.Write(&b, "return nil\n") + stringio.Write(&b, "return ", unmarshalName, "(e, text)\n") b.WriteString("}\n\n") + stringio.Write(&b, "var ", unmarshalName, " = ", file.Import(g.opts.cmPackage), ".CaseUnmarshaler[", goName, "](", stringsName, "[:])\n") + return b.String() } @@ -936,7 +934,7 @@ func (g *generator) variantRep(file *gen.File, dir wit.Direction, t *wit.TypeDef } } - stringsName := file.DeclareName("strings" + GoName(goName, true)) + stringsName := file.DeclareName("_" + GoName(goName, true) + "Strings") stringio.Write(&b, "var ", stringsName, " = [", fmt.Sprintf("%d", len(v.Cases)), "]string {\n") for _, c := range v.Cases { stringio.Write(&b, `"`, c.Name, `"`, ",\n") From f42f71a8183ce93d1c27c2681c580263e61ef9bc Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sat, 4 Jan 2025 14:15:29 +1300 Subject: [PATCH 13/14] tests/generated: regenerate --- .../wasi/filesystem/v0.2.0/types/types.wit.go | 50 +++++++------------ .../wasi/io/v0.2.0/streams/streams.wit.go | 4 +- .../sockets/v0.2.0/network/network.wit.go | 39 ++++++--------- .../wasi/sockets/v0.2.0/tcp/tcp.wit.go | 16 ++---- 4 files changed, 38 insertions(+), 71 deletions(-) diff --git a/tests/generated/wasi/filesystem/v0.2.0/types/types.wit.go b/tests/generated/wasi/filesystem/v0.2.0/types/types.wit.go index 94e50240..f8252d54 100755 --- a/tests/generated/wasi/filesystem/v0.2.0/types/types.wit.go +++ b/tests/generated/wasi/filesystem/v0.2.0/types/types.wit.go @@ -29,7 +29,6 @@ package types import ( - "errors" "go.bytecodealliance.org/cm" wallclock "tests/generated/wasi/clocks/v0.2.0/wall-clock" "tests/generated/wasi/io/v0.2.0/streams" @@ -107,7 +106,7 @@ const ( DescriptorTypeSocket ) -var stringsDescriptorType = [8]string{ +var _DescriptorTypeStrings = [8]string{ "unknown", "block-device", "character-device", @@ -120,11 +119,9 @@ var stringsDescriptorType = [8]string{ // String implements [fmt.Stringer], returning the enum case name of e. func (e DescriptorType) String() string { - return stringsDescriptorType[e] + return _DescriptorTypeStrings[e] } -var indexDescriptorType = cm.Index(stringsDescriptorType[:]) - // MarshalText implements [encoding.TextMarshaler]. func (e DescriptorType) MarshalText() ([]byte, error) { return []byte(e.String()), nil @@ -133,14 +130,11 @@ func (e DescriptorType) MarshalText() ([]byte, error) { // UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum // case. Returns an error if the supplied text is not one of the enum cases. func (e *DescriptorType) UnmarshalText(text []byte) error { - v := indexDescriptorType(string(text)) - if v < 0 { - return errors.New("unknown enum case") - } - *e = DescriptorType(v) - return nil + return _DescriptorTypeUnmarshalCase(e, text) } +var _DescriptorTypeUnmarshalCase = cm.CaseUnmarshaler[DescriptorType](_DescriptorTypeStrings[:]) + // DescriptorFlags represents the flags "wasi:filesystem/types@0.2.0#descriptor-flags". // // Descriptor flags. @@ -345,7 +339,7 @@ func (self *NewTimestamp) Timestamp() *DateTime { return cm.Case[DateTime](self, 2) } -var stringsNewTimestamp = [3]string{ +var _NewTimestampStrings = [3]string{ "no-change", "now", "timestamp", @@ -353,7 +347,7 @@ var stringsNewTimestamp = [3]string{ // String implements [fmt.Stringer], returning the variant case name of v. func (v NewTimestamp) String() string { - return stringsNewTimestamp[v.Tag()] + return _NewTimestampStrings[v.Tag()] } // DirectoryEntry represents the record "wasi:filesystem/types@0.2.0#directory-entry". @@ -535,7 +529,7 @@ const ( ErrorCodeCrossDevice ) -var stringsErrorCode = [37]string{ +var _ErrorCodeStrings = [37]string{ "access", "would-block", "already", @@ -577,11 +571,9 @@ var stringsErrorCode = [37]string{ // String implements [fmt.Stringer], returning the enum case name of e. func (e ErrorCode) String() string { - return stringsErrorCode[e] + return _ErrorCodeStrings[e] } -var indexErrorCode = cm.Index(stringsErrorCode[:]) - // MarshalText implements [encoding.TextMarshaler]. func (e ErrorCode) MarshalText() ([]byte, error) { return []byte(e.String()), nil @@ -590,14 +582,11 @@ func (e ErrorCode) MarshalText() ([]byte, error) { // UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum // case. Returns an error if the supplied text is not one of the enum cases. func (e *ErrorCode) UnmarshalText(text []byte) error { - v := indexErrorCode(string(text)) - if v < 0 { - return errors.New("unknown enum case") - } - *e = ErrorCode(v) - return nil + return _ErrorCodeUnmarshalCase(e, text) } +var _ErrorCodeUnmarshalCase = cm.CaseUnmarshaler[ErrorCode](_ErrorCodeStrings[:]) + // Advice represents the enum "wasi:filesystem/types@0.2.0#advice". // // File or memory access pattern advisory information. @@ -638,7 +627,7 @@ const ( AdviceNoReuse ) -var stringsAdvice = [6]string{ +var _AdviceStrings = [6]string{ "normal", "sequential", "random", @@ -649,11 +638,9 @@ var stringsAdvice = [6]string{ // String implements [fmt.Stringer], returning the enum case name of e. func (e Advice) String() string { - return stringsAdvice[e] + return _AdviceStrings[e] } -var indexAdvice = cm.Index(stringsAdvice[:]) - // MarshalText implements [encoding.TextMarshaler]. func (e Advice) MarshalText() ([]byte, error) { return []byte(e.String()), nil @@ -662,14 +649,11 @@ func (e Advice) MarshalText() ([]byte, error) { // UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum // case. Returns an error if the supplied text is not one of the enum cases. func (e *Advice) UnmarshalText(text []byte) error { - v := indexAdvice(string(text)) - if v < 0 { - return errors.New("unknown enum case") - } - *e = Advice(v) - return nil + return _AdviceUnmarshalCase(e, text) } +var _AdviceUnmarshalCase = cm.CaseUnmarshaler[Advice](_AdviceStrings[:]) + // MetadataHashValue represents the record "wasi:filesystem/types@0.2.0#metadata-hash-value". // // A 128-bit hash value, split into parts because wasm doesn't have a diff --git a/tests/generated/wasi/io/v0.2.0/streams/streams.wit.go b/tests/generated/wasi/io/v0.2.0/streams/streams.wit.go index 48336ed2..e3a381b7 100755 --- a/tests/generated/wasi/io/v0.2.0/streams/streams.wit.go +++ b/tests/generated/wasi/io/v0.2.0/streams/streams.wit.go @@ -64,14 +64,14 @@ func (self *StreamError) Closed() bool { return self.Tag() == 1 } -var stringsStreamError = [2]string{ +var _StreamErrorStrings = [2]string{ "last-operation-failed", "closed", } // String implements [fmt.Stringer], returning the variant case name of v. func (v StreamError) String() string { - return stringsStreamError[v.Tag()] + return _StreamErrorStrings[v.Tag()] } // InputStream represents the imported resource "wasi:io/streams@0.2.0#input-stream". diff --git a/tests/generated/wasi/sockets/v0.2.0/network/network.wit.go b/tests/generated/wasi/sockets/v0.2.0/network/network.wit.go index e658e6f4..a37b5b9a 100755 --- a/tests/generated/wasi/sockets/v0.2.0/network/network.wit.go +++ b/tests/generated/wasi/sockets/v0.2.0/network/network.wit.go @@ -4,7 +4,6 @@ package network import ( - "errors" "go.bytecodealliance.org/cm" ) @@ -154,7 +153,7 @@ const ( ErrorCodePermanentResolverFailure ) -var stringsErrorCode = [21]string{ +var _ErrorCodeStrings = [21]string{ "unknown", "access-denied", "not-supported", @@ -180,11 +179,9 @@ var stringsErrorCode = [21]string{ // String implements [fmt.Stringer], returning the enum case name of e. func (e ErrorCode) String() string { - return stringsErrorCode[e] + return _ErrorCodeStrings[e] } -var indexErrorCode = cm.Index(stringsErrorCode[:]) - // MarshalText implements [encoding.TextMarshaler]. func (e ErrorCode) MarshalText() ([]byte, error) { return []byte(e.String()), nil @@ -193,14 +190,11 @@ func (e ErrorCode) MarshalText() ([]byte, error) { // UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum // case. Returns an error if the supplied text is not one of the enum cases. func (e *ErrorCode) UnmarshalText(text []byte) error { - v := indexErrorCode(string(text)) - if v < 0 { - return errors.New("unknown enum case") - } - *e = ErrorCode(v) - return nil + return _ErrorCodeUnmarshalCase(e, text) } +var _ErrorCodeUnmarshalCase = cm.CaseUnmarshaler[ErrorCode](_ErrorCodeStrings[:]) + // IPAddressFamily represents the enum "wasi:sockets/network@0.2.0#ip-address-family". // // enum ip-address-family { @@ -217,18 +211,16 @@ const ( IPAddressFamilyIPv6 ) -var stringsIPAddressFamily = [2]string{ +var _IPAddressFamilyStrings = [2]string{ "ipv4", "ipv6", } // String implements [fmt.Stringer], returning the enum case name of e. func (e IPAddressFamily) String() string { - return stringsIPAddressFamily[e] + return _IPAddressFamilyStrings[e] } -var indexIPAddressFamily = cm.Index(stringsIPAddressFamily[:]) - // MarshalText implements [encoding.TextMarshaler]. func (e IPAddressFamily) MarshalText() ([]byte, error) { return []byte(e.String()), nil @@ -237,14 +229,11 @@ func (e IPAddressFamily) MarshalText() ([]byte, error) { // UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum // case. Returns an error if the supplied text is not one of the enum cases. func (e *IPAddressFamily) UnmarshalText(text []byte) error { - v := indexIPAddressFamily(string(text)) - if v < 0 { - return errors.New("unknown enum case") - } - *e = IPAddressFamily(v) - return nil + return _IPAddressFamilyUnmarshalCase(e, text) } +var _IPAddressFamilyUnmarshalCase = cm.CaseUnmarshaler[IPAddressFamily](_IPAddressFamilyStrings[:]) + // IPv4Address represents the tuple "wasi:sockets/network@0.2.0#ipv4-address". // // type ipv4-address = tuple @@ -283,14 +272,14 @@ func (self *IPAddress) IPv6() *IPv6Address { return cm.Case[IPv6Address](self, 1) } -var stringsIPAddress = [2]string{ +var _IPAddressStrings = [2]string{ "ipv4", "ipv6", } // String implements [fmt.Stringer], returning the variant case name of v. func (v IPAddress) String() string { - return stringsIPAddress[v.Tag()] + return _IPAddressStrings[v.Tag()] } // IPv4SocketAddress represents the record "wasi:sockets/network@0.2.0#ipv4-socket-address". @@ -359,12 +348,12 @@ func (self *IPSocketAddress) IPv6() *IPv6SocketAddress { return cm.Case[IPv6SocketAddress](self, 1) } -var stringsIPSocketAddress = [2]string{ +var _IPSocketAddressStrings = [2]string{ "ipv4", "ipv6", } // String implements [fmt.Stringer], returning the variant case name of v. func (v IPSocketAddress) String() string { - return stringsIPSocketAddress[v.Tag()] + return _IPSocketAddressStrings[v.Tag()] } diff --git a/tests/generated/wasi/sockets/v0.2.0/tcp/tcp.wit.go b/tests/generated/wasi/sockets/v0.2.0/tcp/tcp.wit.go index e0d58090..cace8342 100755 --- a/tests/generated/wasi/sockets/v0.2.0/tcp/tcp.wit.go +++ b/tests/generated/wasi/sockets/v0.2.0/tcp/tcp.wit.go @@ -4,7 +4,6 @@ package tcp import ( - "errors" "go.bytecodealliance.org/cm" monotonicclock "tests/generated/wasi/clocks/v0.2.0/monotonic-clock" "tests/generated/wasi/io/v0.2.0/poll" @@ -72,7 +71,7 @@ const ( ShutdownTypeBoth ) -var stringsShutdownType = [3]string{ +var _ShutdownTypeStrings = [3]string{ "receive", "send", "both", @@ -80,11 +79,9 @@ var stringsShutdownType = [3]string{ // String implements [fmt.Stringer], returning the enum case name of e. func (e ShutdownType) String() string { - return stringsShutdownType[e] + return _ShutdownTypeStrings[e] } -var indexShutdownType = cm.Index(stringsShutdownType[:]) - // MarshalText implements [encoding.TextMarshaler]. func (e ShutdownType) MarshalText() ([]byte, error) { return []byte(e.String()), nil @@ -93,14 +90,11 @@ func (e ShutdownType) MarshalText() ([]byte, error) { // UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum // case. Returns an error if the supplied text is not one of the enum cases. func (e *ShutdownType) UnmarshalText(text []byte) error { - v := indexShutdownType(string(text)) - if v < 0 { - return errors.New("unknown enum case") - } - *e = ShutdownType(v) - return nil + return _ShutdownTypeUnmarshalCase(e, text) } +var _ShutdownTypeUnmarshalCase = cm.CaseUnmarshaler[ShutdownType](_ShutdownTypeStrings[:]) + // TCPSocket represents the imported resource "wasi:sockets/tcp@0.2.0#tcp-socket". // // A TCP socket resource. From ca608fcc884942843d61fdb8827d03a7c7e34d9b Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Sun, 19 Jan 2025 14:30:34 -0800 Subject: [PATCH 14/14] wit/bindgen: predeclare MarshalText and UnmarshalText methods --- wit/bindgen/generator.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/wit/bindgen/generator.go b/wit/bindgen/generator.go index 6d14a19e..9b72961b 100644 --- a/wit/bindgen/generator.go +++ b/wit/bindgen/generator.go @@ -574,7 +574,9 @@ func (g *generator) declareTypeDef(file *gen.File, dir wit.Direction, t *wit.Typ // Predeclare reserved methods. switch t.Kind.(type) { case *wit.Enum: - decl.scope.DeclareName("String") // For fmt.Stringer + decl.scope.DeclareName("String") // For fmt.Stringer + decl.scope.DeclareName("MarshalText") // For encoding.TextMarshaler + decl.scope.DeclareName("UnmarshalText") // For encoding.TextUnmarshaler case *wit.Variant: decl.scope.DeclareName("Tag") // Method on cm.Variant decl.scope.DeclareName("String") // For fmt.Stringer