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

Open
#394 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
48/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Stale
Tech stack
go, wasm

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:

  1. Within same package (option<borrow<resource>> in same interface): ✅ Generates correctly
  2. Cross-package record fields (borrow<resource> without option): ✅ Generates correctly
  3. 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from bytecodealliance/go-modules

All issues in bytecodealliance/go-modules

Similar issues

More Go issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.