bytecodealliance / bytecodealliance/go-modules

Incorrect code is generated for `option<borrow<resource>>` in some `record`s

Open
#394 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Go
Stars
148
Forks
20
PR merge metrics
No merged PRs in 30d

Description

## Summary

wit-bindgen-go generates incorrect lift functions when a record containing `option>` is defined in one interface and used as a function parameter in another interface. The lift function incorrectly returns `cm.Option[cm.Rep]` instead of `cm.Option[ResourceType]`, causing a type mismatch compilation error.

## Environment

- **wit-bindgen-go**: Latest from `go.bytecodealliance.org/cmd/wit-bindgen-go` (as of 2025-10-15)
- **go-modules**: commit `55a8715`
- **Go**: 1.23+
- **TinyGo**: 0.34.0 (wasip2 target)

## Minimal Reproduction

### WIT Definition

```wit
package my:test@0.1.0;

interface protocol {
use wasi:io/streams@0.2.3.{output-stream};

record client-context {
output: option>,
}
}

interface tools {
use protocol.{client-context};

do-something: func(ctx: client-context);
}

world test {
export tools;
}
```

### Generated Code (Incorrect)

**File: `my/test/protocol/protocol.wit.go`**
```go
type ClientContext struct {
_ cm.HostLayout `json:"-"`
Output cm.Option[OutputStream] `json:"output"`
}
```

**File: `my/test/tools/abi.go`**
```go
func lift_OptionBorrowOutputStream(f0 uint32, f1 uint32) (v cm.Option[cm.Rep]) {
if f0 == 0 {
return
}
return (cm.Option[cm.Rep])(cm.Some[cm.Rep](cm.Reinterpret[cm.Rep]((uint32)(f1))))
}

func lift_ClientContext(f0 uint32, f1 uint32) (v protocol.ClientContext) {
v.Output = lift_OptionBorrowOutputStream(f0, f1) // ❌ Type mismatch!
return
}
```

### Compilation Error

```
my/test/tools/abi.go:18:13: cannot use lift_OptionBorrowOutputStream(f0, f1)
(value of struct type cm.Option[cm.Rep]) as cm.Option[protocol.OutputStream]
value in assignment
```

## Expected Behavior

The lift function should return the concrete resource type, not `cm.Rep`:

```go
func lift_OptionBorrowOutputStream(f0 uint32, f1 uint32) (v cm.Option[protocol.OutputStream]) {
if f0 == 0 {
return
}
return (cm.Option[protocol.OutputStream])(cm.Some[protocol.OutputStream](
cm.Reinterpret[protocol.OutputStream]((uint32)(f1))))
}
```

## Analysis

The bug occurs due to cross-interface type resolution:

1. **Within same package** (`option>` in same interface): ✅ Generates correctly
2. **Cross-package record fields** (`borrow` without option): ✅ Generates correctly
3. **Cross-package with option wrapper** (`option>`): ❌ Bug triggered

The codegen correctly handles:
- Function parameters with `option>` → `cm.Option[cm.Rep]`
- Record fields with `borrow` → `ResourceType`

But fails when combining both patterns in a cross-interface scenario.

## Impact

This bug affects the [wasmcp](https://github.com/wasmcp/wasmcp/tree/main) MCP framework where `ClientContext` (defined in `wasmcp:mcp/protocol`) contains `output: option>` and is used across multiple capability interfaces.

**Example:** `wasmcp:mcp/tools-capability` interface uses `client-context` from the protocol interface:

```wit
interface protocol {
record client-context {
identity-claims: option,
session-id: option,
output: option>,
}
}
```

```wit
interface tools-capability {
use protocol.{client-context, ...};

list-tools: func(request: list-tools-request, client: client-context) -> list-tools-result;
call-tool: func(request: call-tool-request, client: client-context) -> option;
}
```

This prevents building any Go-based MCP tools capability using wit-bindgen-go.

## Workaround

The only current workaround is to manually patch the generated `abi.go` file after each generation:

```go
-func lift_OptionBorrowOutputStream(f0 uint32, f1 uint32) (v cm.Option[cm.Rep]) {
+func lift_OptionBorrowOutputStream(f0 uint32, f1 uint32) (v cm.Option[protocol.OutputStream]) {
if f0 == 0 {
return
}
- return (cm.Option[cm.Rep])(cm.Some[cm.Rep](cm.Reinterpret[cm.Rep]((uint32)(f1))))
+ return (cm.Option[protocol.OutputStream])(cm.Some[protocol.OutputStream](
+ cm.Reinterpret[protocol.OutputStream]((uint32)(f1))))
}
```

This is fragile and breaks on every regeneration.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.