// Package gopackages is the real package-loading boundary: it drives the Go // toolchain's package loader to turn patterns into the typed packages the pure // core consumes. It is deliberately thin and kept out of the core so the core // stays a pure, deterministic function of already-loaded inputs.
package gopackages import ( ) // Result is the outcome of loading. type Result struct { Packages []*discover.Package // TypeErrors are the tolerated type-checking errors (e.g. a stale generated // file whose old wiring no longer compiles). They are collected rather than // fatal so regeneration still works; a caller may surface them as warnings. // Parse errors and other structural failures are returned as the error result // instead. This mirrors packagestest.Loaded.TypeErrors. TypeErrors []error } // loadMode is the smallest set of fields the core needs. NeedTypes|NeedTypesInfo // resolve provider and signature types (cross-package types come from export // data), which is all the core consumes, the same resolution model the // in-memory test loader uses. We deliberately omit NeedImports|NeedDeps: they // would type-check the entire transitive dependency graph in process (an order of // magnitude slower on dependency-heavy packages) only to keep type errors and // broken-dependency errors in distinct Error.Kinds. The classifier below does not // rely on that distinction, so the dependency type-check is pure cost. const loadMode = packages.NeedName | packages.NeedSyntax | packages.NeedTypes | packages.NeedTypesInfo // Load resolves and type-checks the packages matching patterns, rooted at dir. // A structural failure is fatal: a broken module or missing package (reported by // the go command), or a parse error that could hide a //plumb: directive. // Everything else, notably a type error, is tolerated and collected in // Result.TypeErrors: regenerating over a stale generated file whose old wiring no // longer type-checks must still work, and an unresolved import surfaces precisely // downstream as an invalid-type diagnostic if it reaches the generated output. func ( []string, string) (*Result, error) { if len() == 0 { = []string{"."} } := token.NewFileSet() := &packages.Config{ Mode: loadMode, Dir: , Fset: , ParseFile: parseFile, } , := packages.Load(, ...) if != nil { return nil, fmt.Errorf("loading packages: %w", ) } := &Result{} for , := range { for , := range .Errors { // A parse error could hide a //plumb: directive, so the package cannot // be scanned safely: fatal. Every other error (a type error, including, // without NeedDeps, an unresolved-import "could not import") is tolerated // and collected; a stale generated file about to be overwritten is the // motivating case. An unresolved import that actually reaches the output // is caught by the core's invalid-type check. // // A per-package structural error (packages.ListError) is also tolerated // here, not treated as fatal: whole-load breakage (a broken module, no // package matching the patterns) surfaces either through the returned // err above or the empty-res.Packages check below, which is the “load as // a whole” failure plumb treats as fatal. A ListError attached to one of // several otherwise-usable packages should not abort generation. if .Kind == packages.ParseError { return nil, fmt.Errorf("loading package %q: %w", .PkgPath, ) } .TypeErrors = append(.TypeErrors, ) } // Expected to be populated for every root under NeedTypes|NeedTypesInfo: // go/packages assigns Types and TypesInfo unconditionally, and the sole // nil-TypesInfo path (sources missing) carries a ParseError the fatal branch // above catches first. But this rests on an external tool's documented // behavior, not a plumb invariant, so an unexpected nil here is a fatal load // failure, not a broken internal invariant: skipping silently would hide // every directive in the package, so fail on the returned-error side (the // same side as "no packages matched" below) rather than crash on user input. if .Types == nil || .TypesInfo == nil { return nil, fmt.Errorf("package %q loaded without type information", .PkgPath) } .Packages = append(.Packages, &discover.Package{ PkgPath: .PkgPath, Name: .Name, Fset: , Syntax: .Syntax, Types: .Types, Info: .TypesInfo, }) } if len(.Packages) == 0 { return nil, fmt.Errorf("no packages matched %v", ) } return , nil } // parseFile parses a source file for go/packages. SkipObjectResolution drops the // unused ast.Object graph; ParseComments keeps the //plumb: directives. func ( *token.FileSet, string, []byte) (*ast.File, error) { const = parser.ParseComments | parser.SkipObjectResolution return parser.ParseFile(, , , ) }