Composition articles and compiler notes mixed informal wiring guidance with normative host syntax.
Native dependency injection - Flow and algorithm
Platform spec article
Native dependency injection - Flow and algorithm
Spec standingStandard
-
Sibling articles defer to this hub for MUST/SHOULD authority.
Context
Decision
This feature hub must own normative MUST/SHOULD contract text. Sibling articles must not redefine hub requirements and should link here for authority.
Consequences
FAQ locked decisions migrate to ADRs; articles retain examples and troubleshooting tables only.
Verification anchors
/platform-spec/language-meta/composition/dependency-injection/;
verify:trudoc. -
Backends do not perform runtime service lookup for app DI.
Context
Runtime DI containers hide wiring and conflict with the project's explicit composition goals (distinct from Rust pipeline IoC in D-INC-0002).
Decision
The reference compiler must fully resolve the app composition graph at compile time. Backends must not perform runtime service lookup for
host/registry/scopewiring.Consequences
Lowering emits ctor wiring and scope enter/leave; execution hosts activate frozen graphs only.
Verification anchors
compiler/crates/beskid_analysis(plannedcompositionmodule);compiler/crates/beskid_codegen; Flow and algorithm. -
Constructor parameter inject is rejected (E1712).
Context
Constructor injection churns signatures across inheritance and complicates lowering.
Decision
injectmust apply to fields on ordinary types only. Constructor-parameterinjectmust be rejected (E1712).Consequences
Uniform type headers and a single slot-lowering story for injected fields.
Verification anchors
-
Global registrations use registry block name registry.
Context
Early drafts considered alternate container keywords.
Decision
The global registration block must be spelled
registry(locked for v0.2+).Consequences
Parser, diagnostics, and docs use one keyword; no alias
containerin Standard conformance.Verification anchors
Grammar and semantic snapshots when composition lands.
-
Resolution walks innermost named scope toward global.
Context
Authors need predictable scope boundaries for web and console activation patterns.
Decision
Construct Rule Global Merged registryof the launched host after inheritance chainNamed scopeTree under global; per-activation unless single/transientStack Fiber-local scope stack during withResolution Walk innermost → global; global::andparent::qualifiers as specifiedConsequences
Execution backends maintain scope enter/leave with
with; plural inject collects at lowest matching level.Verification anchors
-
Singular inject must be unique at the resolution level.
Context
Apps register multiple implementations of one contract (for example two
Storage).Decision
Multiple implementations must use
inject Contract[](or concreteT[]). Singularinject Contractmust be unique at the resolution level (E1705 when ambiguous).Consequences
Deterministic registration merge order defines array element order.
Verification anchors
-
Exactly one launch per process invocation.
Context
Libraries ship reusable composition roots; executables pick one entry host per run.
Decision
Projects may declare many named
hosttypes. Each process run must use exactly onelaunch Host(args)on an executable target (E1702 for duplicate launch on one path).Consequences
Manifest
apptargets name the launched host; multi-app repos use separate targets, one launch each run.Verification anchors
-
Libraries may declare host types but not launch.
Context
Test and library packages attempted to embed process entry via launch.
Decision
Libproject targets may declarehosttypes for reuse butlaunchis forbidden (E1711). Only app/test host targets maylaunch.Consequences
Consumers reference library hosts from their own app targets or approved harness entries.
Verification anchors
FAQ.
-
Resolves after semantic.snapshot before mod.analyze.
Context
Compiler mods need a frozen composition snapshot but must not mutate app graphs.
Decision
Pipeline phase
composition.resolvemust run aftersemantic.snapshotand beforemod.analyze(see Stage ordering).Consequences
Mods query read-only snapshots; app graph build stays in analysis.
Verification anchors
compiler/crates/beskid_pipeline; Flow and algorithm. -
Compiler Mod type forbids host blocks on mod projects.
Context
Compiler mods and app DI share syntax keywords but different planes.
Decision
Modprojects must not declare apphostblocks or alter app composition graphs. Mod registration uses contractregistrations[]per Compiler Mod SDK.Consequences
Clear boundary between Pipeline composition (Rust) and app DI (Beskid).
Verification anchors
- Native dependency injection - Contracts and edge cases Lifetime rules, plural inject, host override, fail-closed guarantees, E17xx diagnostics.
- Native dependency injection - Design model host, registry, scope hierarchy, global scope, field inject, array inject, dispose, and launch.
- Native dependency injection - Examples Reference fixtures for hosts, plural inject, scope dispose, and library hosts.
- Native dependency injection - FAQ and troubleshooting Locked design decisions, troubleshooting, and v0.3 follow-ups.
- Native dependency injection - Flow and algorithm Host-chain merge, global scope, composition.resolve, plural inject lowering.
- Native dependency injection - Verification and traceability Tests, snapshot versioning, and implementation checklist.
0 revisions (git unavailable at build; counts may be empty)
No commits recorded for this path.
Full tree: run pnpm verify:platform-spec-layout (writes src/generated/platform-spec-layout-report.json).
Compile pipeline placement
Section titled “Compile pipeline placement”flowchart LR parse[parse] mods[mod phases optional] semantic[semantic.snapshot] comp[composition.resolve] modar[mod.analyze rewrite] lower[lower.ready lower codegen] parse --> mods --> semantic --> comp --> modar --> lower
| Id | Constant (planned) | Emitter |
|---|---|---|
composition.resolve | COMPOSITION_RESOLVE | beskid_analysis composition pass |
composition.resolve algorithm (normative)
Section titled “composition.resolve algorithm (normative)”- Resolve launch host — From the
app(application) target entry, determine thelaunch Hostexpression and theHosttype. RejectLibtargets containinglaunch(E1711). Reject multiplelaunchon the entry path (E1702). - Collect host declarations — All named
hosttypes in the compilation; verify the launched type exists. - Merge host chain — Walk
: ParentHostfrom base (corelib) to launched host; merge eachregistry. Launched host overrides duplicate keys. Reject lifetime kind changes on override (E1713). - Build scope tree — Attach top-level
scopenodes to global; nest childscopedeclarations under parents. - Build service graph — Nodes for registrations; edges from field
inject,init,dispose,startupparameters. Support array edges (plural inject aggregates multiple registration nodes). - Validate inject sites — Singular vs plural rules;
global::/parent::; forbid constructorinject(E1712). - Validate lifetimes and activations — Global vs named scope;
withnesting; parent scope rule. - Freeze binding plan — Field slot indices, array wiring order, scope enter (
init) / leave (dispose) hooks. - Export composition snapshot — Versioned; read-only for mods after
semantic.snapshot.
Resolution container (singular vs plural)
Section titled “Resolution container (singular vs plural)”For each inject site the compiler chooses a resolution container:
- Default: start at innermost active named scope on the stack (compile-time: the scope context of the field or hook); walk toward global.
global::: use merged globalregistryonly.parent::: skip innermost; start one level up.
Singular: exactly one registration at the chosen level (first matching level when walking). Plural T[]: collect all registrations for that contract (or self-bind type) at the first level that has ≥1 match, in deterministic registration order.
Lowering
Section titled “Lowering”- Global
single/transient— Process-wide factories for thelaunchlifetime. - Field
inject— Initialize fields before user constructors run (order respects dependency graph). with— Push scope frame →init→ body →dispose→ pop.launch— Build global container →startup→ base host run entry.
JIT and AOT share one plan.
Runtime scope stack
Section titled “Runtime scope stack”flowchart TB global[Global registry merged host chain] scopeA[Named scope A] scopeB[Named scope B nested] global --> scopeA --> scopeB
Fiber-local stack: [ Global, … named scopes ]. No runtime lookup by name; frame indices from the binding plan.
LSP / incremental
Section titled “LSP / incremental”Re-run composition.resolve when host, registry, scope, with, field inject, startup, init, dispose, launch, or registered type shapes change.
Package graph
Section titled “Package graph”Lib assemblies may export host types. The app host **launch**es; CompilePlan supplies types for registrations. Library registry entries merge only through : ParentHost, not automatically into unrelated app hosts.