Skip to content

Library: split di.go into six files and dedupe the hooks - #47

Merged
floatdrop merged 1 commit into
mainfrom
split-library
Sep 10, 2026
Merged

Library: split di.go into six files and dedupe the hooks#47
floatdrop merged 1 commit into
mainfrom
split-library

Conversation

@floatdrop

Copy link
Copy Markdown
Owner

Why

di.go was 2447 lines with 842 of comment, and the instance lifecycle (start, stopIfNeeded, drainIfNeeded, stop) sat 1200 lines away from the Start, Stop and drain that drive it. This came out of a review of the file for simplification, comment bloat and organisation.

The split

Code moved verbatim by line range first (that state builds and passes the suite), then the edits went on top.

File Holds
di.go package doc, keys, errors, events, Scope, modules, Test, WithScope/FromContext
binding.go binding, Provide/Value/Wire/Wrap, the Binding handle
state.go state, freeze, deriveEager, the parent-chain readers
resolve.go resolver, graph, get/resolve/await, the build step, Get and friends
lifecycle.go phase machine, hooks, once, Start, Stop, the drain sweep
run.go Run, Shutdown, signals

callsite counts frames, not files, and scripts/generatorgap.go already indexes by file, so neither needed a change. CLAUDE.md, docs/DESIGN.md and the explain.go header describe the layout.

Simplifications

  • state.report replaces the six hand-built emit(Event{...}) lines.
  • Wire and Wrap share function (signature check, same case order and messages), params and Scope.arguments.
  • instance.paired and instance.owes are the one statement of the owed predicate, used by both the drain and stop steps.
  • once.wait returns (finished, err).
  • Two unreachable branches removed: the nil start context in startIfRunning (runContext never reports running with a nil context) and the continue in deriveEager for a key absent from the index (every non-group binding in all is indexed). Both had zero coverage.
  • construct builds its view with one literal.

One behaviour fix

instance.start called OnStart directly rather than through callHook, so a panicking start hook emitted no EventStart, while a panicking drain or stop hook emitted its event with the error. It now goes through callHook, which also removes the duplicate recover block in startClaimed. TestPanickingStartHookIsObserved pins it and fails against a clone of 80895d2 with observed 0 start events, want 1. CHANGELOG has an Unreleased entry; no signature changed.

Comments

Removed: history told as narrative ("used to", "was how that showed"), the five restatements of the no-Stop-from-a-hook rule (kept in full on Stop), and comments that restated the code. Kept: every invariant comment and the public godoc. Net comment lines went from 842 to 798; the file headers and new helper docs added about 45 back.

Left out on purpose: an ancestors() iterator over the nine parent-chain walks, because it would sit on the warm lookup/isStopped path and should be benchmarked first.

Verification

test -z "$(gofmt -l .)" && go vet ./... && go test -race -count=1 ./... \
  && golangci-lint run ./... \
  && go run github.com/campoy/embedmd@v1.0.0 -d README.md

passes as one chain; cd examples && go vet ./... && go test ./... passes; scripts/generatorgap.go -floor 90 reports 93.1% locally. Benchmarks not run: the warm Get path is untouched.

🤖 Generated with Claude Code

di.go was 2447 lines, a third of them comments, with the instance lifecycle
1200 lines away from the Start and Stop that drive it. The library is now
di.go (package doc, keys, events, Scope, modules, Test), binding.go
(registration), state.go (the registry and freeze), resolve.go (the
resolution path, both cycle detectors, the build step), lifecycle.go (the
phase machine, hooks, Start and Stop) and run.go (Run and Shutdown). Code
moved verbatim; the edits came after.

Simplifications: one state.report helper replaces six hand-built event
lines; Wire and Wrap share the signature check, parameter keys and argument
loop; instance.paired and instance.owes are the one statement of the owed
predicate for both the drain and stop steps; once.wait returns its error
last; two unreachable branches are gone (a nil start context, an eager key
missing from the index).

One behaviour fix rode along. instance.start called OnStart directly rather
than through callHook, so a panicking start hook emitted no EventStart while
a panicking drain or stop hook emitted its event with the error. It now goes
through callHook, which also removes the duplicate recover block in
startClaimed. TestPanickingStartHookIsObserved pins it and fails against
80895d2 with "observed 0 start events, want 1".

Comments: the history told as narrative, the five restatements of the
no-Stop-from-a-hook rule and the comments that restated code are gone; the
invariant comments and the public godoc stay. CLAUDE.md, docs/DESIGN.md and
the explain.go header describe the new layout.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@floatdrop
floatdrop merged commit d5d418c into main Sep 10, 2026
4 checks passed
@floatdrop
floatdrop deleted the split-library branch September 10, 2026 12:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant