Skip to content

Commit c18a8d4

Browse files
committed
compiler: factor out utility types for processing Go sources and errors.
This is the first step in reducing complexity of the compiler.Compile function. The new `sources` type represents all inputs into the package compilation and simplifies extracting useful information out of them. It is designed to have little business logic of its own and serves as a convenient bridge to other packages like go/types or astrewrite. The ErrorList type is extended with utility methods that reduce verbosity of the calling code. (cherry picked from commit cb5cf57)
1 parent 038d281 commit c18a8d4

File tree

5 files changed

+230
-121
lines changed

5 files changed

+230
-121
lines changed

compiler/compiler.go

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,22 +33,6 @@ func init() {
3333
}
3434
}
3535

36-
type ErrorList []error
37-
38-
func (err ErrorList) Error() string {
39-
if len(err) == 0 {
40-
return "<no errors>"
41-
}
42-
return fmt.Sprintf("%s (and %d more errors)", err[0].Error(), len(err[1:]))
43-
}
44-
45-
func (err ErrorList) Normalize() error {
46-
if len(err) == 0 {
47-
return nil
48-
}
49-
return err
50-
}
51-
5236
// Archive contains intermediate build outputs of a single package.
5337
//
5438
// This is a logical equivalent of an object file in traditional compilers.

compiler/errors.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package compiler
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
)
7+
8+
// ErrTooManyErrors is added to the ErrorList by the Trim method.
9+
var ErrTooManyErrors = errors.New("too many errors")
10+
11+
// ErrorList wraps multiple errors as a single error.
12+
type ErrorList []error
13+
14+
func (errs ErrorList) Error() string {
15+
if len(errs) == 0 {
16+
return "<no errors>"
17+
}
18+
return fmt.Sprintf("%s (and %d more errors)", errs[0].Error(), len(errs[1:]))
19+
}
20+
21+
// ErrOrNil returns nil if ErrorList is empty, or the error otherwise.
22+
func (errs ErrorList) ErrOrNil() error {
23+
if len(errs) == 0 {
24+
return nil
25+
}
26+
return errs
27+
}
28+
29+
// Append an error to the list.
30+
//
31+
// If err is an instance of ErrorList, the lists are concatenated together,
32+
// otherwise err is appended at the end of the list. If err is nil, the list is
33+
// returned unmodified.
34+
//
35+
// err := DoStuff()
36+
// errList := errList.Append(err)
37+
func (errs ErrorList) Append(err error) ErrorList {
38+
if err == nil {
39+
return errs
40+
}
41+
if err, ok := err.(ErrorList); ok {
42+
return append(errs, err...)
43+
}
44+
return append(errs, err)
45+
}
46+
47+
// AppendDistinct is similar to Append, but doesn't append the error if it has
48+
// the same message as the last error on the list.
49+
func (errs ErrorList) AppendDistinct(err error) ErrorList {
50+
if l := len(errs); l > 0 {
51+
if prev := errs[l-1]; prev != nil && err.Error() == prev.Error() {
52+
return errs // The new error is the same as the last one, skip it.
53+
}
54+
}
55+
56+
return errs.Append(err)
57+
}
58+
59+
// Trim the error list if it has more than limit errors. If the list is trimmed,
60+
// all extraneous errors are replaced with a single ErrTooManyErrors, making the
61+
// returned ErrorList length of limit+1.
62+
func (errs ErrorList) Trim(limit int) ErrorList {
63+
if len(errs) <= limit {
64+
return errs
65+
}
66+
67+
return append(errs[:limit], ErrTooManyErrors)
68+
}

compiler/linkname.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ func parseGoLinknames(fset *token.FileSet, pkgPath string, file *ast.File) ([]Go
112112
}
113113
}
114114

115-
return directives, errs.Normalize()
115+
return directives, errs.ErrOrNil()
116116
}
117117

118118
// goLinknameSet is a utility that enables quick lookup of whether a decl is

compiler/package.go

Lines changed: 38 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import (
1818
"github.com/gopherjs/gopherjs/compiler/internal/typeparams"
1919
"github.com/gopherjs/gopherjs/compiler/typesutil"
2020
"github.com/gopherjs/gopherjs/internal/experiments"
21-
"github.com/neelance/astrewrite"
2221
"golang.org/x/tools/go/gcexportdata"
2322
"golang.org/x/tools/go/types/typeutil"
2423
)
@@ -108,34 +107,20 @@ type flowData struct {
108107
endCase int
109108
}
110109

110+
// ImportContext provides access to information about imported packages.
111111
type ImportContext struct {
112+
// Mapping for an absolute import path to the package type information.
112113
Packages map[string]*types.Package
113-
Import func(string) (*Archive, error)
114-
}
115-
116-
// packageImporter implements go/types.Importer interface.
117-
type packageImporter struct {
118-
importContext *ImportContext
119-
importError *error // A pointer to importError in Compile.
120-
}
121-
122-
func (pi packageImporter) Import(path string) (*types.Package, error) {
123-
if path == "unsafe" {
124-
return types.Unsafe, nil
125-
}
126-
127-
a, err := pi.importContext.Import(path)
128-
if err != nil {
129-
if *pi.importError == nil {
130-
// If import failed, show first error of import only (https://github.com/gopherjs/gopherjs/issues/119).
131-
*pi.importError = err
132-
}
133-
return nil, err
134-
}
135-
136-
return pi.importContext.Packages[a.ImportPath], nil
114+
// Import returns a previously compiled Archive for a dependency package. If
115+
// the Import() call was successful, the corresponding entry must be added to
116+
// the Packages map.
117+
Import func(importPath string) (*Archive, error)
137118
}
138119

120+
// Compile the provided Go sources as a single package.
121+
//
122+
// Import path must be the absolute import path for a package. Provided sources
123+
// are always sorted by name to ensure reproducible JavaScript output.
139124
func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, importContext *ImportContext, minify bool) (_ *Archive, err error) {
140125
defer func() {
141126
e := recover()
@@ -152,89 +137,29 @@ func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, impor
152137
err = bailout(fmt.Errorf("unexpected compiler panic while building package %q: %v", importPath, e))
153138
}()
154139

155-
// Files must be in the same order to get reproducible JS
156-
sort.Slice(files, func(i, j int) bool {
157-
return fileSet.File(files[i].Pos()).Name() > fileSet.File(files[j].Pos()).Name()
158-
})
159-
160-
typesInfo := &types.Info{
161-
Types: make(map[ast.Expr]types.TypeAndValue),
162-
Defs: make(map[*ast.Ident]types.Object),
163-
Uses: make(map[*ast.Ident]types.Object),
164-
Implicits: make(map[ast.Node]types.Object),
165-
Selections: make(map[*ast.SelectorExpr]*types.Selection),
166-
Scopes: make(map[ast.Node]*types.Scope),
167-
Instances: make(map[*ast.Ident]types.Instance),
168-
}
169-
170-
var errList ErrorList
171-
172-
// Extract all go:linkname compiler directives from the package source.
173-
goLinknames := []GoLinkname{}
174-
for _, file := range files {
175-
found, err := parseGoLinknames(fileSet, importPath, file)
176-
if err != nil {
177-
if errs, ok := err.(ErrorList); ok {
178-
errList = append(errList, errs...)
179-
} else {
180-
errList = append(errList, err)
181-
}
182-
}
183-
goLinknames = append(goLinknames, found...)
184-
}
140+
srcs := sources{
141+
ImportPath: importPath,
142+
Files: files,
143+
FileSet: fileSet,
144+
}.Sort()
185145

186-
var importError error
187-
var previousErr error
188-
config := &types.Config{
189-
Context: types.NewContext(),
190-
Importer: packageImporter{
191-
importContext: importContext,
192-
importError: &importError,
193-
},
194-
Sizes: sizes32,
195-
Error: func(err error) {
196-
if previousErr != nil && previousErr.Error() == err.Error() {
197-
return
198-
}
199-
errList = append(errList, err)
200-
previousErr = err
201-
},
202-
}
203-
typesPkg, err := config.Check(importPath, fileSet, files, typesInfo)
204-
if importError != nil {
205-
return nil, importError
206-
}
207-
if errList != nil {
208-
if len(errList) > 10 {
209-
pos := token.NoPos
210-
if last, ok := errList[9].(types.Error); ok {
211-
pos = last.Pos
212-
}
213-
errList = append(errList[:10], types.Error{Fset: fileSet, Pos: pos, Msg: "too many errors"})
214-
}
215-
return nil, errList
216-
}
146+
tContext := types.NewContext()
147+
typesInfo, typesPkg, err := srcs.TypeCheck(importContext, tContext)
217148
if err != nil {
218149
return nil, err
219150
}
220151
if genErr := typeparams.RequiresGenericsSupport(typesInfo); genErr != nil && !experiments.Env.Generics {
221152
return nil, fmt.Errorf("package %s requires generics support (https://github.com/gopherjs/gopherjs/issues/1013): %w", importPath, genErr)
222153
}
223-
importContext.Packages[importPath] = typesPkg
154+
importContext.Packages[srcs.ImportPath] = typesPkg
224155

225-
exportData := new(bytes.Buffer)
226-
if err := gcexportdata.Write(exportData, nil, typesPkg); err != nil {
227-
return nil, fmt.Errorf("failed to write export data: %v", err)
228-
}
229-
encodedFileSet := new(bytes.Buffer)
230-
if err := fileSet.Write(json.NewEncoder(encodedFileSet).Encode); err != nil {
156+
// Extract all go:linkname compiler directives from the package source.
157+
goLinknames, err := srcs.ParseGoLinknames()
158+
if err != nil {
231159
return nil, err
232160
}
233161

234-
simplifiedFiles := make([]*ast.File, len(files))
235-
for i, file := range files {
236-
simplifiedFiles[i] = astrewrite.Simplify(file, typesInfo, false)
237-
}
162+
srcs = srcs.Simplified(typesInfo)
238163

239164
isBlocking := func(f *types.Func) bool {
240165
archive, err := importContext.Import(f.Pkg().Path())
@@ -251,31 +176,31 @@ func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, impor
251176
}
252177

253178
tc := typeparams.Collector{
254-
TContext: config.Context,
179+
TContext: tContext,
255180
Info: typesInfo,
256181
Instances: &typeparams.PackageInstanceSets{},
257182
}
258-
tc.Scan(typesPkg, simplifiedFiles...)
183+
tc.Scan(typesPkg, srcs.Files...)
259184
instancesByObj := map[types.Object][]typeparams.Instance{}
260185
for _, inst := range tc.Instances.Pkg(typesPkg).Values() {
261186
instancesByObj[inst.Object] = append(instancesByObj[inst.Object], inst)
262187
}
263188

264-
pkgInfo := analysis.AnalyzePkg(simplifiedFiles, fileSet, typesInfo, typesPkg, isBlocking)
189+
pkgInfo := analysis.AnalyzePkg(srcs.Files, fileSet, typesInfo, typesPkg, isBlocking)
265190
funcCtx := &funcContext{
266191
FuncInfo: pkgInfo.InitFuncInfo,
267192
pkgCtx: &pkgContext{
268193
Info: pkgInfo,
269194
additionalSelections: make(map[*ast.SelectorExpr]typesutil.Selection),
270195

271-
typesCtx: config.Context,
196+
typesCtx: tContext,
272197
pkgVars: make(map[string]string),
273198
varPtrNames: make(map[*types.Var]string),
274199
escapingVars: make(map[*types.Var]bool),
275200
indentation: 1,
276201
dependencies: make(map[types.Object]bool),
277202
minify: minify,
278-
fileSet: fileSet,
203+
fileSet: srcs.FileSet,
279204
instanceSet: tc.Instances,
280205
},
281206
allVars: make(map[string]int),
@@ -315,7 +240,7 @@ func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, impor
315240

316241
var functions []*ast.FuncDecl
317242
var vars []*types.Var
318-
for _, file := range simplifiedFiles {
243+
for _, file := range srcs.Files {
319244
for _, decl := range file.Decls {
320245
switch d := decl.(type) {
321246
case *ast.FuncDecl:
@@ -669,8 +594,17 @@ func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, impor
669594
return nil, funcCtx.pkgCtx.errList
670595
}
671596

597+
exportData := new(bytes.Buffer)
598+
if err := gcexportdata.Write(exportData, nil, typesPkg); err != nil {
599+
return nil, fmt.Errorf("failed to write export data: %w", err)
600+
}
601+
encodedFileSet := new(bytes.Buffer)
602+
if err := srcs.FileSet.Write(json.NewEncoder(encodedFileSet).Encode); err != nil {
603+
return nil, err
604+
}
605+
672606
return &Archive{
673-
ImportPath: importPath,
607+
ImportPath: srcs.ImportPath,
674608
Name: typesPkg.Name(),
675609
Imports: importedPaths,
676610
ExportData: exportData.Bytes(),

0 commit comments

Comments
 (0)