Skip to content

Latest commit

 

History

History
92 lines (74 loc) · 4.53 KB

architecture.md

File metadata and controls

92 lines (74 loc) · 4.53 KB

Architecture

wire is a one-way compiler:

Go packages -> DSL -> module graph -> injector plan -> Go source

Layout

Path Responsibility
wire.go, doc.go The declaration API. No runtime behavior.
example_test.go Executable examples of every declaration form.
cmd/wire The executable; it only calls internal/command.
internal/command Command line parsing, diagnostic and plan output, exit codes.
internal/engine The pipeline: load, parse, plan, group variants, render, verify.
internal/load Two-phase package loading and per-file build constraints.
internal/dsl Recognizes API calls and produces the model.
internal/model The intermediate model shared by later stages.
internal/graph Module instantiation, binding nodes, resolution.
internal/plan Injector and scope contracts, topological order.
internal/generate Go source rendering and the per-file runtime helpers.
internal/artifact Working-tree operations: stale files, ownership, atomic writes, checks, diffs.
internal/diff Unified diff rendering.
internal/buildconstraint Build constraint specialization, combination, variant naming.
internal/diagnostic Diagnostic values and their human and JSON encodings.
internal/typeindex A map keyed by go/types identity.
internal/integration End-to-end fixtures driven through the built binary.
docs/ This document and the diagnostic reference.

Front end

internal/load loads in two phases under the wireinject tag: an import-graph pass finds every package that imports the wire API, then a second pass type-checks those and the requested roots from source while every other dependency comes from export data. Build constraints of files that were not parsed are derived on demand; standard library declarations count as unconditional, so tags such as goexperiment.* never reach generated files.

internal/dsl recognizes API methods by their go/types objects, not by spelling, and rejects declarations whose build constraint does not require wireinject. It produces the model without resolving anything.

Graph and plan

internal/graph instantiates each included module as a lexical boundary. Resolution checks local bindings, child exports, then the parent; results are cached per visibility context. Overrides shadow a type in their module and decorators apply afterward.

Providers, values, bindings, decorators, collections, inputs, scope factories, struct bindings, and field bindings are explicit node kinds. Every directive is validated and given its node when the module is instantiated; the resolver only wires nodes. Exports are checked for ownership; their dependencies are resolved when an injector or scope uses them. Missing-binding and cycle diagnostics name the consumer and, for cycles, the full path. Every code is listed in docs/diagnostics.md.

internal/plan validates injector and scope contracts, adds seeds, resolves outputs, and produces a stable topological order. Order is the only execution plan.

Generation and lifecycle

internal/generate emits constructor calls in dependency order. Each file declares its helpers once: a cleanup runner, a panic-rollback guard, a lifecycle type, and, with scopes, a scope tracker; helper names carry the variant suffix so co-active files never collide. Generated code has no dependency on this module.

Cleanups are armed as soon as their provider returns and run in reverse on failure or panic; a failing provider owns its partial result. Cleanup is idempotent, joins errors with errors.Join, has no context, and delays a cleanup panic until the remaining resources closed.

Scope factories build sequentially but may be called concurrently. The generated tracker coordinates caller release with parent shutdown: it cancels active builds, waits for build and release calls, and closes committed scopes in reverse completion order. Scope contexts stay live until scope cleanup.

Pipeline and filesystem boundary

internal/engine groups injectors by package and derived build constraint, renders each variant, and type-checks the result through an overlay before anything is written. Unconditional graphs use wire_gen.go, conditional graphs wire_gen_<hash>.go; inactive variants are left untouched.

internal/artifact owns every interaction with the working tree; internal/diff renders unified diffs; internal/command parses the command line, prints diagnostics and plans as text, JSON, or a Graphviz digraph, and maps results to exit codes.