Source File
gen.go
Belonging Package
go.pact.im/x/plumb/internal/gen
// Package gen is the orchestrator of plumb's pure core: it runs the discover →// solve → emit pipeline over loaded, type-checked packages to produce generated// injector source, performing no I/O, holding no global state, and consulting no// clock or randomness. Its output is a deterministic function of its inputs.// The phases live in sibling packages (discover, solve, emit) over shared leaves// (diag, gotypes); this package only wires them together.package genimport ()// Options configures a single generation run. The cli builds it from flags; gen// distributes the fields each phase needs.type Options struct {// ImportPath is the destination package's import path. It decides what is// referenced unqualified (the destination) and what is imported and// qualified (everything else). Required and never empty by the time the// core runs.ImportPath string// PackageName is the identifier emitted in the generated `package <name>`// clause. Required and never empty by the time the core runs.PackageName string// OutputBase is the base name of the file being written (e.g.// "plumb_gen.go"), used to identify the file being overwritten for the// same-package function-name collision check. Empty when writing to stdout,// in which case that check is skipped.OutputBase string}// Generate is the pure entry point: it analyzes the loaded packages, resolves// every set, and returns the generated source and report. It performs no I/O.// The returned error, when non-nil, is always a *diag.Error with a source// position where one applies.func ( Options, []*discover.Package) (*emit.Result, error) {// PackageName and ImportPath are required; the cli always resolves them, so an// empty value is an internal-caller invariant violation, not a user error.if .PackageName == "" {panic("plumb: gen.Options.PackageName is required")}if .ImportPath == "" {panic("plumb: gen.Options.ImportPath is required")}, := discover.Analyze(, .ImportPath, .OutputBase)if != nil {return nil,}if len() == 0 {return nil, diag.Errorf(token.Position{}, diag.ErrNoDirectives, "nothing to generate")}:= map[string][]*discover.Provider{}for , := range {[.SetName] = append([.SetName], )}, := buildDestInfo(, )if != nil {return nil,}:= types.NewContext()var []*solve.Planfor , := range slices.Sorted(maps.Keys()) {, := solve.Set(, [], .ImportPath, .OutputBase, , )if != nil {return nil,}= append(, )}return emit.File(.ImportPath, .PackageName, , , ), nil}// buildDestInfo gathers what the emitter needs to know about the destination// package: whether it was scanned, its name, and the file each top-level// identifier is declared in (for the collision safeguards). It also rejects a// destination declaration that shadows a predeclared identifier, a// whole-destination property, so it is checked here once rather than per set.func ( Options, []*discover.Package) (*solve.DestInfo, *diag.Error) {:= &solve.DestInfo{PkgName: .PackageName, Names: map[string]string{}, Imports: map[string]string{}}for , := range {if .PkgPath != .ImportPath || .Types == nil {continue}.Scanned = true:= .Types.Scope()for , := range .Names() {:= .Lookup():= filepath.Base(diag.PosIn(.Fset, .Pos()).Filename)if == .OutputBase {// The file plumb is about to overwrite declares only generated// injectors, which no injector body references. Recording their names// as reserved would make a local or lifted parameter derived from a// set name (e.g. a "server" local in set "server") free on the first// generation but reserved once the output file exists, breaking// idempotent regeneration. Skip them here: gen owns output-file// exclusion (the collision check trusts these maps and does not// re-filter), and import aliases re-add the set names independently// (assignAliases).continue}// The generated file spells predeclared identifiers unqualified (error and// nil in fallible wiring, basic-type names in signatures) and no// qualification can restore a shadowed builtin, so a destination that// declares a universe name at top level is rejected, regardless of whether// any one set's signature happens to spell it. scope.Names() is sorted, so// the reported name is deterministic; anchoring at the declaration points at// the offending source directly.if gotypes.IsUniverseName() {return nil, diag.Errorf(diag.PosIn(.Fset, .Pos()), diag.ErrDestShadowsPredeclared, "destination package %q declares %s (%s), shadowing the predeclared identifier; rename the declaration", .ImportPath, , )}.Names[] =}collectImportQualifiers(, .OutputBase, .Imports)}return , nil}// collectImportQualifiers records, for each import qualifier used by a hand-written// file in the destination package, the base name of a file that uses it (a hint for// the collision diagnostic). The file plumb will overwrite (outputBase) is skipped// entirely: plumb rewrites its imports, so a qualifier used only there can never// collide with a generated set name; only one a hand-written sibling still uses// can. This makes gen the sole owner of output-file exclusion, for import// qualifiers as for top-level names (see buildDestInfo). Among the eligible files// the first base in sorted order wins, so the result never depends on the loader's// file-iteration order.func ( *discover.Package, string, map[string]string) {:= map[string]string{}for , := range .Types.Imports() {[.Path()] = .Name()}:= slices.SortedFunc(slices.Values(.Syntax), func(, *ast.File) int {return strings.Compare(discover.FileBase(, ), discover.FileBase(, ))})for , := range {:= discover.FileBase(, )if == {continue}for , := range .Imports {:= ""if .Name != nil {if .Name.Name == "_" || .Name.Name == "." {continue // blank/dot imports introduce no qualifier}= .Name.Name} else {, := strconv.Unquote(.Path.Value)= []}if == "" {continue}if , := []; ! {[] =}}}}
The pages are generated with Golds v0.8.4. (GOOS=linux GOARCH=amd64)