Skip to content
Merged
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
1 change: 0 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ generated: clean json
clean:
rm -rf ./generated/*
rm -f internal/wasmtools/wasm-tools.wasm
rm -f internal/wasmtools/wasm-tools.wasm.gz

# tests/generated writes generated Go code to the tests directory
.PHONY: tests/generated
Expand Down
8 changes: 8 additions & 0 deletions cm/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),

- Mutating methods `SetOK` and `SetErr` on `result` types (`Result[Shape, OK, Err]`).

### Changed

- Breaking: `BoolResult` is now represented as a `uint8` rather than a `bool`. This fixes an issue with TinyGo where `bool` values are treated distinctly from `uint8`. See [#344](https://github.com/bytecodealliance/go-modules/issues/344) for more information.

### Fixed

- [#344](https://github.com/bytecodealliance/go-modules/issues/344): the memory representation of `option` and `result` now use `uint8` instead of `bool` for the discriminator. LLVM optimizes `bool` values into a single bit, which breaks WIT variants where the associated types share memory.

## [v0.2.2] — 2025-03-16

### Fixed
Expand Down
12 changes: 6 additions & 6 deletions cm/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,30 +18,30 @@ func None[T any]() Option[T] {
func Some[T any](v T) Option[T] {
return Option[T]{
option: option[T]{
isSome: true,
isSome: 1,
some: v,
},
}
}

// option represents the internal representation of a Component Model option type.
// The first byte is a bool representing none or some,
// The first byte is a byte representing the none or some case,
// followed by storage for the associated type T.
type option[T any] struct {
_ HostLayout
isSome bool
isSome uint8
some T
}

// None returns true if o represents the none case.
func (o *option[T]) None() bool {
return !o.isSome
return o.isSome == 0
}

// Some returns a non-nil *T if o represents the some case,
// or nil if o represents the none case.
func (o *option[T]) Some() *T {
if o.isSome {
if o.isSome == 1 {
return &o.some
}
return nil
Expand All @@ -51,7 +51,7 @@ func (o *option[T]) Some() *T {
// or the zero value of T if o represents the none case.
// This does not have a pointer receiver, so it can be chained.
func (o option[T]) Value() T {
if !o.isSome {
if o.isSome == 0 {
var zero T
return zero
}
Expand Down
18 changes: 9 additions & 9 deletions cm/result.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import "unsafe"

const (
// ResultOK represents the OK case of a result.
ResultOK = false
ResultOK = 0

// ResultErr represents the error case of a result.
ResultErr = true
ResultErr = 1
)

// BoolResult represents a result with no OK or error type.
// False represents the OK case and true represents the error case.
type BoolResult bool
type BoolResult uint8

// Result represents a result sized to hold the Shape type.
// The size of the Shape type must be greater than or equal to the size of OK and Err types.
Expand All @@ -33,7 +33,7 @@ type AnyResult[Shape, OK, Err any] interface {
// result represents the internal representation of a Component Model result type.
type result[Shape, OK, Err any] struct {
_ HostLayout
isErr bool
isErr uint8
_ [0]OK
_ [0]Err
data Shape // [unsafe.Sizeof(*(*Shape)(unsafe.Pointer(nil)))]byte
Expand Down Expand Up @@ -76,20 +76,20 @@ func (r *result[Shape, OK, Err]) SetErr(err Err) {
// IsOK returns true if r represents the OK case.
func (r *result[Shape, OK, Err]) IsOK() bool {
r.validate()
return !r.isErr
return r.isErr == ResultOK
}

// IsErr returns true if r represents the error case.
func (r *result[Shape, OK, Err]) IsErr() bool {
r.validate()
return r.isErr
return r.isErr == ResultErr
}

// OK returns a non-nil *OK pointer if r represents the OK case.
// If r represents an error, then it returns nil.
func (r *result[Shape, OK, Err]) OK() *OK {
r.validate()
if r.isErr {
if r.isErr == ResultErr {
return nil
}
return (*OK)(unsafe.Pointer(&r.data))
Expand All @@ -99,7 +99,7 @@ func (r *result[Shape, OK, Err]) OK() *OK {
// If r represents the OK case, then it returns nil.
func (r *result[Shape, OK, Err]) Err() *Err {
r.validate()
if !r.isErr {
if r.isErr == ResultOK {
return nil
}
return (*Err)(unsafe.Pointer(&r.data))
Expand All @@ -109,7 +109,7 @@ func (r *result[Shape, OK, Err]) Err() *Err {
// or (zero value of OK, Err, true) if r represents the error case.
// This does not have a pointer receiver, so it can be chained.
func (r result[Shape, OK, Err]) Result() (ok OK, err Err, isErr bool) {
if r.isErr {
if r.isErr == ResultErr {
return ok, *(*Err)(unsafe.Pointer(&r.data)), true
}
return *(*OK)(unsafe.Pointer(&r.data)), err, false
Expand Down
39 changes: 38 additions & 1 deletion cm/result_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func TestResultLayout(t *testing.T) {
size uintptr
offset uintptr
}{
{"result", BoolResult(false), 1, 0},
{"result", BoolResult(0), 1, 0},
{"ok", BoolResult(ResultOK), 1, 0},
{"err", BoolResult(ResultErr), 1, 0},

Expand Down Expand Up @@ -350,3 +350,40 @@ func TestIssue284NotTinyGo(t *testing.T) {
}
}
}

func TestIssue344Option(t *testing.T) {
type T Result[Option[uint64], uint64, Option[uint64]]

want := uint64(2)
v := T(OK[Result[Option[uint64], uint64, Option[uint64]]](want))
got := *v.OK()

if got != want {
t.Errorf("*v.OK(): %v, expected %v", got, want)
}
}

func TestIssue344Result(t *testing.T) {
type R Result[uint64, uint64, bool]
type T Result[R, uint64, R]

want := uint64(2)
v := T(OK[T](want))
got := *v.OK()

if got != want {
t.Errorf("*v.OK(): %v, expected %v", got, want)
}
}

func TestIssue344BoolResult(t *testing.T) {
type T Result[BoolResult, uint8, BoolResult]

want := uint8(2)
v := T(OK[T](want))
got := *v.OK()

if got != want {
t.Errorf("*v.OK(): %v, expected %v", got, want)
}
}
15 changes: 15 additions & 0 deletions testdata/issues/issue344.wit
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package issues:issue344;

world w {
import i;
}

interface i {
type a = result<u64, tuple<option<u64>>>;
type b = result<u64, tuple<result<u64, bool>>>;
type c = result<result, u8>;

fa: func() -> a;
fb: func() -> b;
fc: func() -> c;
}
145 changes: 145 additions & 0 deletions testdata/issues/issue344.wit.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
{
"worlds": [
{
"name": "w",
"imports": {
"interface-0": {
"interface": {
"id": 0
}
}
},
"exports": {},
"package": 0
}
],
"interfaces": [
{
"name": "i",
"types": {
"a": 2,
"b": 5,
"c": 7
},
"functions": {
"fa": {
"name": "fa",
"kind": "freestanding",
"params": [],
"result": 2
},
"fb": {
"name": "fb",
"kind": "freestanding",
"params": [],
"result": 5
},
"fc": {
"name": "fc",
"kind": "freestanding",
"params": [],
"result": 7
}
},
"package": 0
}
],
"types": [
{
"name": null,
"kind": {
"option": "u64"
},
"owner": null
},
{
"name": null,
"kind": {
"tuple": {
"types": [
0
]
}
},
"owner": null
},
{
"name": "a",
"kind": {
"result": {
"ok": "u64",
"err": 1
}
},
"owner": {
"interface": 0
}
},
{
"name": null,
"kind": {
"result": {
"ok": "u64",
"err": "bool"
}
},
"owner": null
},
{
"name": null,
"kind": {
"tuple": {
"types": [
3
]
}
},
"owner": null
},
{
"name": "b",
"kind": {
"result": {
"ok": "u64",
"err": 4
}
},
"owner": {
"interface": 0
}
},
{
"name": null,
"kind": {
"result": {
"ok": null,
"err": null
}
},
"owner": null
},
{
"name": "c",
"kind": {
"result": {
"ok": 6,
"err": "u8"
}
},
"owner": {
"interface": 0
}
}
],
"packages": [
{
"name": "issues:issue344",
"interfaces": {
"i": 0
},
"worlds": {
"w": 0
}
}
]
}
14 changes: 14 additions & 0 deletions testdata/issues/issue344.wit.json.golden.wit
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package issues:issue344;

interface i {
type a = result<u64, tuple<option<u64>>>;
type b = result<u64, tuple<result<u64, bool>>>;
type c = result<result, u8>;
fa: func() -> a;
fb: func() -> b;
fc: func() -> c;
}

world w {
import i;
}
2 changes: 1 addition & 1 deletion tests/generated/wasi/cli/v0.2.0/exit/exit.wit.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 1 addition & 5 deletions tests/generated/wasi/cli/v0.2.0/run/run.wasm.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading