Skip to content

go: Wire (google/wire) dependency-injection bindings are not modeled, so interface calls can't be resolved per binary #2444

Open

Description

@tthayer

What problem does this solve?

In Go codebases that use google/wire for dependency injection, the concrete type behind an interface is chosen at compile time in the Wire injector (wire.go with //go:build wireinject) and written out in the generated wire_gen.go. The graph has no view of that binding, so questions like "can binary X reach this concrete method?" can't be answered.

Example shape:

// store.go
type ObjectWriter interface{ WriteFile(ctx context.Context, name string, r io.Reader) error }

type localWriter struct{ root string }
func (w *localWriter) WriteFile(ctx context.Context, name string, r io.Reader) error { /* os.Create under w.root */ }

type s3Writer struct{ /* ... */ }
func (w *s3Writer) WriteFile(ctx context.Context, name string, r io.Reader) error { /* ... */ }

// handler.go
type Handler struct{ w ObjectWriter }
func (h *Handler) Upload(ctx context.Context, r io.Reader) error { return h.w.WriteFile(ctx, "x", r) }

// cmd/api/wire.go  (//go:build wireinject)
var set = wire.NewSet(newLocalWriter, wire.Bind(new(ObjectWriter), new(*localWriter)), NewHandler)
func InitializeAPI() (*Handler, error) { wire.Build(set); return nil, nil }

// cmd/api/wire_gen.go (generated)
func InitializeAPI() (*Handler, error) {
    w := newLocalWriter()
    h := NewHandler(w)
    return h, nil
}

Today:

  • Handler.Upload gets a CALLS edge to the interface member ObjectWriter.WriteFile, and localWriter.WriteFile gets an OVERRIDE edge to it.
  • An inbound trace_path from localWriter.WriteFile returns 0 callers, because the caller edge lands on the interface member and trace_path doesn't hop back across OVERRIDE (this is the part Title: Go interface calls with multiple implementations do not produce candidate concrete CALLS edges #1114 covers).
  • Even with that hop, the graph can't tell which binary binds ObjectWriter to *localWriter rather than *s3Writer. That fact lives only in wire.Bind(...) in the injector and in the constructor order in wire_gen.go.

In practice, for a monorepo with many cmd/* binaries sharing packages, the question "which services write to the local disk?" requires reading wire_gen.go by hand. The graph can show that a service constructs newLocalWriter, but not that its handlers dispatch to it.

Proposed solution

Wire-aware extraction for Go:

  1. Parse wire.Bind(new(I), new(T)) in injector files and provider sets, and emit an edge such as BINDS from the provider set / injector to T, with I recorded as a property. That records a per-injector "interface I is satisfied by T here" fact.
  2. Link injectors to their entry point: wire_gen.go's InitializeX → the constructors it calls (these are ordinary CALLS today, so this mostly exists already), plus main → InitializeX.
  3. Binding-aware trace_path: when an inbound or outbound traversal hits an interface member, an option such as resolve_bindings: true narrows the candidate concrete implementers to those bound in the injector reachable from the traversal's entry point. Without the option, it falls back to all implementers (as Title: Go interface calls with multiple implementations do not produce candidate concrete CALLS edges #1114 proposes).

Even (1) alone would make "which binaries bind *localWriter" a single Cypher query.

Good OSS test beds:

  • google/wire's own _tutorial and internal/wire/testdata
  • google/go-cloud samples (the guestbook sample uses Wire injectors per cloud provider, binding the same interfaces to different concrete types per binary)

Alternatives considered

  • Generic interface → all-implementers fan-out (Title: Go interface calls with multiple implementations do not produce candidate concrete CALLS edges #1114). Necessary but not sufficient. With several implementers it over-approximates, so every binary appears to reach every implementation.
  • Relying on CALLS edges into wire_gen.go constructors. This shows what a binary constructs, but not that a given handler's interface call dispatches to it. Stitching the two together takes several hand-written Cypher queries per interface.
  • gopls / pointer analysis. Would resolve the dispatch precisely but is much heavier than parsing wire.Bind / provider sets.

Related: a method call on a function's return value (f(x).Method(...)) also produces no CALLS edge in our Go index. This is common with Wire-provided factory functions. Happy to file that separately if it isn't already tracked.

Confirmations

Activity

  1. added
    cypherCypher query language parser/executor bugs
    parsing/qualityGraph extraction bugs, false positives, missing edges
    on Sep 30, 2026
  2. added
    enhancementNew feature or request
    priority/normalStandard review queue; useful PR with ordinary maintainer urgency.
    and removed
    cypherCypher query language parser/executor bugs
    on Oct 1, 2026
  3. DeusData commented on Oct 1, 2026

    @DeusData
    Owner

    Thank you for the detailed example and for separating Wire binding information from general interface/override traversal. We have seen draft #2452 and the separate grouped-variable fix in #2453. A new BINDS relationship changes the graph contract and needs a focused design review before we can approve it. We need more time for that review; please keep #2452 as a draft in the meantime. This is an acknowledgement of the proposal, not an integration commitment, and the standalone extraction bug can be reviewed independently.

  4. DeusData commented on Oct 19, 2026

    @DeusData
    Owner

    Thank you again for the Wire examples. The per-injector binding contract remains a design decision, and I have no new approval or integration timeline to report. The distinction from general interface fan-out and the separate extraction fix remains useful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestparsing/qualityGraph extraction bugs, false positives, missing edgespriority/normalStandard review queue; useful PR with ordinary maintainer urgency.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions