Skip to content

Incorrect code is generated for option<borrow<resource>> in some records #394

@bowlofarugula

Description

@bowlofarugula

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions