Skip to content

feat(go): emit BINDS edges for google/wire wire.Bind - #2452

Draft
tthayer wants to merge 1 commit into
DeusData:mainfrom
tthayer:feat/2444-wire-bind-edges
Draft

tthayer wants to merge 1 commit into
DeusData:mainfrom
tthayer:feat/2444-wire-bind-edges

Conversation

@tthayer

@tthayer tthayer commented Sep 30, 2026 •

Copy link
Copy Markdown

Part (1) of #2444. Draft pending design sign-off on the issue (new edge type).

Problem

wire.Bind(new(I), new(T)) is the only place a Wire codebase records which concrete type satisfies an interface for a given provider set or injector. The graph has no edge for it:

  • IMPLEMENTS/OVERRIDE say T can satisfy I.
  • CALLS into wire_gen.go constructors say what a binary constructs.

Neither says which provider set selects T for I. That is what separates *localWriter from *s3Writer in the issue's example.

Change

In Go files that import github.com/google/wire (aliases honored; dot/blank imports skipped), each <local>.Bind(new(I), new(T)) emits:

(provider-set Variable | injector Function) -BINDS-> T
{"strategy":"wire_bind","interfaces":["<QN>",...],"unresolved_interfaces":["io.Writer",...]}
  • Source: the innermost package-level Variable whose span contains the call (var Set = wire.NewSet(...), multi-line OK), else the enclosing Function (wire.Build in an injector).
  • Target: T (new(*T) or new(T), bare or pkg.T) resolved exactly through the file's package or import map to a type-like node. There is no fallback: if T is not in the graph, no edge is emitted and a debug skip is logged.
  • Arguments: each must be new([*]Ident) or new([*]pkg.Ident). Anything else (generics, parenthesized, non-new) is skipped and logged.
  • One edge per (source, T): interfaces bound to the same pair are merged into sorted, de-duplicated arrays, because edges dedupe on (source, target, type). Interfaces not in the graph go in unresolved_interfaces as written.
  • Emit sites: next to the env CONFIGURES helper at the sequential, parallel and incremental sites. No new pass, no schema change.
  • Allocation: tracked allocator only. yyjson is given a cbm_alloc-backed yyjson_alc, so lint-memory-core.py baselines are unchanged.

Tests

In tests/test_pipeline.c:

  • pipeline_wire_bind_edges_sequential_parallel_incremental: same-package LocalSet, cross-package new(store.ObjectWriter), new(*store.S3Writer), and an injector wire.Build(wire.Bind(...)). Asserts identical edges for CBM_INDEX_SINGLE_THREAD=1, CBM_WORKERS=4 (padded past the parallel threshold) and incremental reindex.
  • pipeline_wire_bind_multi_interface_single_edge: one set binds *T to an in-repo interface twice plus io.Writer. Expects 1 edge, the QN in interfaces, and io.Writer in unresolved_interfaces.
  • pipeline_wire_bind_negative_controls: each of the following yields 0 edges:
    • no wire import, with a local wire value
    • di "github.com/google/wire" called as wire.Bind (while di.Bind yields 1)
    • non-new arguments
    • concrete type not in the repo
    • the same text in a non-Go file

BINDS is added to the edge-type lists in test_edge_structural.c, test_lang_contract.c and test_convergence_probe.c.

Results (darwin arm64):

  • With the emit call removed, all 3 new tests fail.
  • scripts/test.sh --suites pipeline,edge_structural,lang_contract,convergence_probe,incremental: 586 passed, 0 failed.
  • All 145 suites (--suites): 8254 passed, 0 failed, 10 skipped. Default scripts/test.sh stops at step 0x on the scoop manifest pin (pkg/scoop/codebase-memory-mcp.json at 0.11.0), a static check this diff does not touch (no pkg/ changes).
  • lint-memory-core.py, clang-format, lint-tidy-diff and cppcheck on the changed files: clean.

Measured on google/wire's own testdata

internal/wire/testdata is skipped by discovery (testdata), so the 14 cases that call wire.Bind were copied out and indexed one by one, with main (80eb92a) and this branch as a paired control.

  • Nodes and other edge types: identical, 229 nodes on both. Edges went from 478 to 483, and that +5 is entirely BINDS.

  • Edges emitted (5): InterfaceBinding, ImportedInterfaceBinding, InterfaceBindingReuse (injector source), ReturnArgumentAsInterface (fmt.Stringer, reported as unresolved) and ExampleWithMocks.

  • Correctly no edge (5): the concrete type is external or builtin in BindInterfaceWithValue (*os.File), MultipleBindings (*strings.Reader) and InterfaceBindingDoesntImplement (string); InterfaceBindingInvalidArg0 passes non-new args; InterfaceBindingNotEnoughArgs passes one arg.

  • Missed (4), both pre-existing Go extraction bugs that this PR does not fix:

    The grouped-var fix is fix(go): extract Variables from grouped var ( ... ) declarations #2453. Both fixes change Go extraction beyond Wire.

On a private Go monorepo (368 wire.Bind calls, all shaped new(pkg.I), new(*pkg.T)), this branch emits 340 BINDS edges: 292 from provider-set variables and 48 from injector functions.

What this does not do

Size

+579/−4. 236 lines are the test in test_pipeline.c, and 329 are pass_definitions.c. The source side is one cohesive feature, so I did not split it further.

Written with Claude Code (Anthropic). I reviewed the change and am accountable for it.

wire.Bind(new(I), new(T)) records that a provider set or injector binds
interface I to concrete T. That fact lives only in the injector source,
so the graph could not say which provider set selects which
implementation.

For Go files that import github.com/google/wire (aliases honored, dot and
blank imports skipped), each <local>.Bind call whose two args are
new([*]Ident) or new([*]pkg.Ident) emits a BINDS edge:

- source: the innermost package-level Variable containing the call
  (var Set = wire.NewSet(...)), else the enclosing Function (injector)
- target: T, resolved exactly to a type-like node via the file's
  package or import map; no edge when T is not in the graph
- props: {"strategy":"wire_bind","interfaces":[QN...],
  "unresolved_interfaces":[raw...]}, sorted and deduplicated

All interfaces bound to the same (source, target) share one edge,
because edges dedupe on (source, target, type).

Emitted next to the env CONFIGURES helper at the sequential, parallel
and incremental sites.

Refs DeusData#2444

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Tony Thayer-Osborne <962968+tthayer@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown

Thanks for opening this — it has been seen, and it is queued.

This note is automated, but it is not a brush-off: it exists so you know where your PR stands instead of having to guess from silence.

Current review status: working through a backlog. 0.9.1-rc.1 is out, so the release freeze that held reviews is over — but it left a large queue of open pull requests behind it, and we are reading through them oldest-first. The background is in discussion #1144.

What that means for this PR, concretely:

  • It will not be closed for inactivity. No stale bot touches pull requests here.
  • It may still sit a while before a human reads it. That is on us, not on you.
  • Older PRs are read first, so a recent one is not being skipped — it is behind a queue.

Things that will genuinely speed it up whenever review does happen:

  • Keep it rebased on main — the tree is moving quickly right now, and a conflicting branch cannot be reviewed as the diff you intended.
  • Get CI green, or say which failures you believe are pre-existing.
  • Keep the change to one claim. Bundled features and refactors get split before they get merged, which costs you a round trip.
  • Every commit needs a sign-off (git commit -s) — CI enforces DCO.

If this fixes a bug, a reproduction we can run is worth more than a description of the symptom.

Thanks for contributing, and sorry in advance for the wait.

@DeusData DeusData added enhancement New feature or request parsing/quality Graph extraction bugs, false positives, missing edges priority/normal Standard review queue; useful PR with ordinary maintainer urgency. labels Oct 1, 2026
@DeusData

DeusData commented Oct 1, 2026

Copy link
Copy Markdown
Owner

Thank you for the detailed coverage measurements, explicit unresolved cases, and for keeping this draft pending the decision in #2444. We need more time to review the proposed BINDS graph contract and its indexing semantics. There is no design approval or merge commitment yet. The separate Go extraction fix #2453 can be reviewed independently. The queue is quite full, and we appreciate your patience while we give community contributions the review they deserve.

@tthayer
tthayer marked this pull request as draft October 1, 2026 19:38

This branch has not been deployed

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

Labels

enhancement New feature or request parsing/quality Graph extraction bugs, false positives, missing edges priority/normal Standard review queue; useful PR with ordinary maintainer urgency.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants