Incorrect code is generated for `option<borrow<resource>>` in some `record`s
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 48/100
Research direction
Start with the WIT reproduction and compare the generated my/test/protocol/protocol.wit.go and my/test/tools/abi.go files, then trace wit-bindgen-go’s cross-interface type-resolution path. Regenerate the bindings and confirm that the lift function uses the concrete resource type and that the example compiles without manually patching abi.go.
Written by the indexing model from the issue text.
Description
Summary
wit-bindgen-go generates incorrect lift functions when a record containing option<borrow<resource>> 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
package my:test@0.1.0;
interface protocol {
use wasi:io/streams@0.2.3.{output-stream};
record client-context {
output: option<borrow<output-stream>>,
}
}
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
type ClientContext struct {
_ cm.HostLayout `json:"-"`
Output cm.Option[OutputStream] `json:"output"`
}
File: my/test/tools/abi.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:
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:
- Within same package (
option<borrow<resource>>in same interface): ✅ Generates correctly - Cross-package record fields (
borrow<resource>without option): ✅ Generates correctly - Cross-package with option wrapper (
option<borrow<resource>>): ❌ Bug triggered
The codegen correctly handles:
- Function parameters with
option<borrow<resource>>→cm.Option[cm.Rep] - Record fields with
borrow<resource>→ResourceType
But fails when combining both patterns in a cross-interface scenario.
Impact
This bug affects the wasmcp MCP framework where ClientContext (defined in wasmcp:mcp/protocol) contains output: option<borrow<output-stream>> and is used across multiple capability interfaces.
Example: wasmcp:mcp/tools-capability interface uses client-context from the protocol interface:
interface protocol {
record client-context {
identity-claims: option<string>,
session-id: option<string>,
output: option<borrow<output-stream>>,
}
}
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<call-tool-result>;
}
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:
-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.
- Dominant language
- Go
- Stars
- 148
- Forks
- 20
- PR merge metrics
- No merged PRs in 30d
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from bytecodealliance/go-modules
-
Difficulty 2/5 1-3 hours Newbie friendliness 48/100
bytecodealliance/go-modules#428 · 1 reaction ·
-
Difficulty 3/5 1-2 days Newbie friendliness 45/100
bytecodealliance/go-modules#411 ·
-
Difficulty 4/5 3-5 days Newbie friendliness 45/100
bytecodealliance/go-modules#410 ·
-
Difficulty 3/5 1-2 days Newbie friendliness 38/100
bytecodealliance/go-modules#377 · 1 comment ·
-
Difficulty 3/5 1-2 days Newbie friendliness 38/100
bytecodealliance/go-modules#371 · 3 comments ·
All issues in bytecodealliance/go-modules
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
Difficulty 1/5 Under an hour Newbie friendliness 84/100
-
enhancement needs triage
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
-
kind/cleanup
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
kubernetes-sigs/kueue#15947 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
sympozium-ai/sympozium#627 ·