1package loader
2
3import (
4	"errors"
5	"fmt"
6	"go/ast"
7	"go/parser"
8	"go/scanner"
9	"go/token"
10	"go/types"
11	"log"
12	"os"
13
14	"golang.org/x/tools/go/gcexportdata"
15	"golang.org/x/tools/go/packages"
16)
17
18// Graph resolves patterns and returns packages with all the
19// information required to later load type information, and optionally
20// syntax trees.
21//
22// The provided config can set any setting with the exception of Mode.
23func Graph(cfg packages.Config, patterns ...string) ([]*packages.Package, error) {
24	cfg.Mode = packages.NeedName | packages.NeedImports | packages.NeedDeps | packages.NeedExportsFile | packages.NeedFiles | packages.NeedCompiledGoFiles | packages.NeedTypesSizes
25	pkgs, err := packages.Load(&cfg, patterns...)
26	if err != nil {
27		return nil, err
28	}
29	fset := token.NewFileSet()
30	packages.Visit(pkgs, nil, func(pkg *packages.Package) {
31		pkg.Fset = fset
32	})
33
34	n := 0
35	for _, pkg := range pkgs {
36		if len(pkg.CompiledGoFiles) == 0 && len(pkg.Errors) == 0 && pkg.PkgPath != "unsafe" {
37			// If a package consists only of test files, then
38			// go/packages incorrectly(?) returns an empty package for
39			// the non-test variant. Get rid of those packages. See
40			// #646.
41			//
42			// Do not, however, skip packages that have errors. Those,
43			// too, may have no files, but we want to print the
44			// errors.
45			continue
46		}
47		pkgs[n] = pkg
48		n++
49	}
50	return pkgs[:n], nil
51}
52
53// LoadFromExport loads a package from export data. All of its
54// dependencies must have been loaded already.
55func LoadFromExport(pkg *packages.Package) error {
56	pkg.IllTyped = true
57	for path, pkg := range pkg.Imports {
58		if pkg.Types == nil {
59			return fmt.Errorf("dependency %q hasn't been loaded yet", path)
60		}
61	}
62	if pkg.ExportFile == "" {
63		return fmt.Errorf("no export data for %q", pkg.ID)
64	}
65	f, err := os.Open(pkg.ExportFile)
66	if err != nil {
67		return err
68	}
69	defer f.Close()
70
71	r, err := gcexportdata.NewReader(f)
72	if err != nil {
73		return err
74	}
75
76	view := make(map[string]*types.Package)  // view seen by gcexportdata
77	seen := make(map[*packages.Package]bool) // all visited packages
78	var visit func(pkgs map[string]*packages.Package)
79	visit = func(pkgs map[string]*packages.Package) {
80		for _, pkg := range pkgs {
81			if !seen[pkg] {
82				seen[pkg] = true
83				view[pkg.PkgPath] = pkg.Types
84				visit(pkg.Imports)
85			}
86		}
87	}
88	visit(pkg.Imports)
89	tpkg, err := gcexportdata.Read(r, pkg.Fset, view, pkg.PkgPath)
90	if err != nil {
91		return err
92	}
93	pkg.Types = tpkg
94	pkg.IllTyped = false
95	return nil
96}
97
98// LoadFromSource loads a package from source. All of its dependencies
99// must have been loaded already.
100func LoadFromSource(pkg *packages.Package) error {
101	pkg.IllTyped = true
102	pkg.Types = types.NewPackage(pkg.PkgPath, pkg.Name)
103
104	// OPT(dh): many packages have few files, much fewer than there
105	// are CPU cores. Additionally, parsing each individual file is
106	// very fast. A naive parallel implementation of this loop won't
107	// be faster, and tends to be slower due to extra scheduling,
108	// bookkeeping and potentially false sharing of cache lines.
109	pkg.Syntax = make([]*ast.File, len(pkg.CompiledGoFiles))
110	for i, file := range pkg.CompiledGoFiles {
111		f, err := parser.ParseFile(pkg.Fset, file, nil, parser.ParseComments)
112		if err != nil {
113			pkg.Errors = append(pkg.Errors, convertError(err)...)
114			return err
115		}
116		pkg.Syntax[i] = f
117	}
118	pkg.TypesInfo = &types.Info{
119		Types:      make(map[ast.Expr]types.TypeAndValue),
120		Defs:       make(map[*ast.Ident]types.Object),
121		Uses:       make(map[*ast.Ident]types.Object),
122		Implicits:  make(map[ast.Node]types.Object),
123		Scopes:     make(map[ast.Node]*types.Scope),
124		Selections: make(map[*ast.SelectorExpr]*types.Selection),
125	}
126
127	importer := func(path string) (*types.Package, error) {
128		if path == "unsafe" {
129			return types.Unsafe, nil
130		}
131		if path == "C" {
132			// go/packages doesn't tell us that cgo preprocessing
133			// failed. When we subsequently try to parse the package,
134			// we'll encounter the raw C import.
135			return nil, errors.New("cgo preprocessing failed")
136		}
137		imp := pkg.Imports[path]
138		if imp == nil {
139			return nil, nil
140		}
141		if len(imp.Errors) > 0 {
142			return nil, imp.Errors[0]
143		}
144		return imp.Types, nil
145	}
146	tc := &types.Config{
147		Importer: importerFunc(importer),
148		Error: func(err error) {
149			pkg.Errors = append(pkg.Errors, convertError(err)...)
150		},
151	}
152	err := types.NewChecker(tc, pkg.Fset, pkg.Types, pkg.TypesInfo).Files(pkg.Syntax)
153	if err != nil {
154		return err
155	}
156	pkg.IllTyped = false
157	return nil
158}
159
160func convertError(err error) []packages.Error {
161	var errs []packages.Error
162	// taken from go/packages
163	switch err := err.(type) {
164	case packages.Error:
165		// from driver
166		errs = append(errs, err)
167
168	case *os.PathError:
169		// from parser
170		errs = append(errs, packages.Error{
171			Pos:  err.Path + ":1",
172			Msg:  err.Err.Error(),
173			Kind: packages.ParseError,
174		})
175
176	case scanner.ErrorList:
177		// from parser
178		for _, err := range err {
179			errs = append(errs, packages.Error{
180				Pos:  err.Pos.String(),
181				Msg:  err.Msg,
182				Kind: packages.ParseError,
183			})
184		}
185
186	case types.Error:
187		// from type checker
188		errs = append(errs, packages.Error{
189			Pos:  err.Fset.Position(err.Pos).String(),
190			Msg:  err.Msg,
191			Kind: packages.TypeError,
192		})
193
194	default:
195		// unexpected impoverished error from parser?
196		errs = append(errs, packages.Error{
197			Pos:  "-",
198			Msg:  err.Error(),
199			Kind: packages.UnknownError,
200		})
201
202		// If you see this error message, please file a bug.
203		log.Printf("internal error: error %q (%T) without position", err, err)
204	}
205	return errs
206}
207
208type importerFunc func(path string) (*types.Package, error)
209
210func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }
211