Skip to content

Commit 2693ad9

Browse files
Updating parseAndAugment to use new directives
1 parent befdb46 commit 2693ad9

File tree

4 files changed

+391
-115
lines changed

4 files changed

+391
-115
lines changed

build/build.go

Lines changed: 193 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,22 @@ func ImportDir(dir string, mode build.ImportMode, installSuffix string, buildTag
117117
return pkg, nil
118118
}
119119

120+
// overrideInfo is used by parseAndAugment methods to manage
121+
// directives and how the overlay and original are merged.
122+
type overrideInfo struct {
123+
// KeepOriginal indicates that the original code should be kept
124+
// but the identifier will be prefixed by `_gopherjs_original_foo`.
125+
keepOriginal bool
126+
127+
// pruneFuncBody indicates that the body of the function should
128+
// be removed to prevent statements that are invalid in GopherJS.
129+
pruneFuncBody bool
130+
131+
// purgeMethods indicates that this info is for a struct and
132+
// if a method has this struct as a receiver should also be removed.
133+
purgeMethods bool
134+
}
135+
120136
// parseAndAugment parses and returns all .go files of given pkg.
121137
// Standard Go library packages are augmented with files in compiler/natives folder.
122138
// If isTest is true and pkg.ImportPath has no _test suffix, package is built for running internal tests.
@@ -125,31 +141,55 @@ func ImportDir(dir string, mode build.ImportMode, installSuffix string, buildTag
125141
// The native packages are augmented by the contents of natives.FS in the following way.
126142
// The file names do not matter except the usual `_test` suffix. The files for
127143
// native overrides get added to the package (even if they have the same name
128-
// as an existing file from the standard library). For function identifiers that exist
129-
// in the original AND the overrides AND that include the following directive in their comment:
130-
// //gopherjs:keep-original, the original identifier in the AST gets prefixed by
131-
// `_gopherjs_original_`. For other identifiers that exist in the original AND the overrides,
132-
// the original identifier gets replaced by `_`. New identifiers that don't exist in original
133-
// package get added.
144+
// as an existing file from the standard library).
145+
//
146+
// - For function identifiers that exist in the original and the overrides and have the
147+
// directive `gopherjs:keep-original`, the original identifier in the AST gets
148+
// prefixed by `_gopherjs_original_`.
149+
// - For functions that exist in the original and the overrides and have the
150+
// directive `gopherjs:prune-original`, the original function body in the AST
151+
// is removed. This removes code that is invalid in GopherJS.
152+
// - For identifiers that exist in the original and the overrides
153+
// and have the directive `gopherjs:purge`, both the original and override are removed.
154+
// - Otherwise for identifiers that exist in the original and the overrides,
155+
// the original identifier is removed.
156+
// - New identifiers that don't exist in original package get added.
134157
func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *token.FileSet) ([]*ast.File, []JSFile, error) {
135-
var files []*ast.File
158+
jsFiles, overlayFiles := parseOverlayFiles(xctx, pkg, isTest, fileSet)
136159

137-
type overrideInfo struct {
138-
keepOriginal bool
139-
pruneOriginal bool
160+
originalFiles, err := parserOriginalFiles(pkg, fileSet)
161+
if err != nil {
162+
return nil, nil, err
140163
}
164+
141165
replacedDeclNames := make(map[string]overrideInfo)
166+
for _, file := range overlayFiles {
167+
augmentOverlayFile(file, replacedDeclNames)
168+
pruneImports(file)
169+
}
170+
delete(replacedDeclNames, "init")
142171

172+
for _, file := range originalFiles {
173+
augmentOriginalImports(pkg.ImportPath, file)
174+
augmentOriginalFile(file, replacedDeclNames)
175+
pruneImports(file)
176+
}
177+
178+
return append(overlayFiles, originalFiles...), jsFiles, nil
179+
}
180+
181+
// parseOverlayFiles loads and parses overlay files
182+
// to augment the original files with.
183+
func parseOverlayFiles(xctx XContext, pkg *PackageData, isTest bool, fileSet *token.FileSet) ([]JSFile, []*ast.File) {
143184
isXTest := strings.HasSuffix(pkg.ImportPath, "_test")
144185
importPath := pkg.ImportPath
145186
if isXTest {
146187
importPath = importPath[:len(importPath)-5]
147188
}
148189

149-
jsFiles := []JSFile{}
150-
190+
var jsFiles []JSFile
191+
var files []*ast.File
151192
nativesContext := overlayCtx(xctx.Env())
152-
153193
if nativesPkg, err := nativesContext.Import(importPath, "", 0); err == nil {
154194
jsFiles = nativesPkg.JSFiles
155195
names := nativesPkg.GoFiles
@@ -159,6 +199,7 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke
159199
if isXTest {
160200
names = nativesPkg.XTestGoFiles
161201
}
202+
162203
for _, name := range names {
163204
fullPath := path.Join(nativesPkg.Dir, name)
164205
r, err := nativesContext.bctx.OpenFile(fullPath)
@@ -173,42 +214,25 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke
173214
panic(err)
174215
}
175216
r.Close()
176-
for _, decl := range file.Decls {
177-
switch d := decl.(type) {
178-
case *ast.FuncDecl:
179-
k := astutil.FuncKey(d)
180-
replacedDeclNames[k] = overrideInfo{
181-
keepOriginal: astutil.KeepOriginal(d),
182-
pruneOriginal: astutil.PruneOriginal(d),
183-
}
184-
case *ast.GenDecl:
185-
switch d.Tok {
186-
case token.TYPE:
187-
for _, spec := range d.Specs {
188-
replacedDeclNames[spec.(*ast.TypeSpec).Name.Name] = overrideInfo{}
189-
}
190-
case token.VAR, token.CONST:
191-
for _, spec := range d.Specs {
192-
for _, name := range spec.(*ast.ValueSpec).Names {
193-
replacedDeclNames[name.Name] = overrideInfo{}
194-
}
195-
}
196-
}
197-
}
198-
}
217+
199218
files = append(files, file)
200219
}
201220
}
202-
delete(replacedDeclNames, "init")
203221

222+
return jsFiles, files
223+
}
224+
225+
// parserOriginalFiles loads and parses the original files to augment.
226+
func parserOriginalFiles(pkg *PackageData, fileSet *token.FileSet) ([]*ast.File, error) {
227+
var files []*ast.File
204228
var errList compiler.ErrorList
205229
for _, name := range pkg.GoFiles {
206230
if !filepath.IsAbs(name) { // name might be absolute if specified directly. E.g., `gopherjs build /abs/file.go`.
207231
name = filepath.Join(pkg.Dir, name)
208232
}
209233
r, err := buildutil.OpenFile(pkg.bctx, name)
210234
if err != nil {
211-
return nil, nil, err
235+
return nil, err
212236
}
213237
file, err := parser.ParseFile(fileSet, name, r, parser.ParseComments)
214238
r.Close()
@@ -226,68 +250,155 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke
226250
continue
227251
}
228252

229-
switch pkg.ImportPath {
230-
case "crypto/rand", "encoding/gob", "encoding/json", "expvar", "go/token", "log", "math/big", "math/rand", "regexp", "time":
231-
for _, spec := range file.Imports {
232-
path, _ := strconv.Unquote(spec.Path.Value)
233-
if path == "sync" {
234-
if spec.Name == nil {
235-
spec.Name = ast.NewIdent("sync")
253+
files = append(files, file)
254+
}
255+
256+
if errList != nil {
257+
return nil, errList
258+
}
259+
return files, nil
260+
}
261+
262+
// augmentOverlayFile is the part of parseAndAugment that processes
263+
// an overlay file AST to collect information such as compiler directives and
264+
// perform any initial augmentation needed to the overlay.
265+
func augmentOverlayFile(file *ast.File, replacedDeclNames map[string]overrideInfo) {
266+
for i, decl := range file.Decls {
267+
purgeDecl := astutil.Purge(decl)
268+
switch d := decl.(type) {
269+
case *ast.FuncDecl:
270+
k := astutil.FuncKey(d)
271+
replacedDeclNames[k] = overrideInfo{
272+
keepOriginal: astutil.KeepOriginal(d),
273+
pruneFuncBody: astutil.PruneOriginal(d),
274+
}
275+
case *ast.GenDecl:
276+
for j, spec := range d.Specs {
277+
purgeSpec := purgeDecl || astutil.Purge(spec)
278+
switch d.Tok {
279+
case token.TYPE:
280+
replacedDeclNames[spec.(*ast.TypeSpec).Name.Name] = overrideInfo{
281+
purgeMethods: purgeSpec,
236282
}
237-
spec.Path.Value = `"github.com/gopherjs/gopherjs/nosync"`
283+
case token.VAR, token.CONST:
284+
for _, name := range spec.(*ast.ValueSpec).Names {
285+
replacedDeclNames[name.Name] = overrideInfo{}
286+
}
287+
}
288+
if purgeSpec {
289+
d.Specs[j] = nil
238290
}
239291
}
292+
d.Specs = astutil.Squeeze(d.Specs)
293+
if len(d.Specs) == 0 {
294+
file.Decls[i] = nil
295+
}
296+
}
297+
if purgeDecl {
298+
file.Decls[i] = nil
240299
}
300+
}
301+
file.Decls = astutil.Squeeze(file.Decls)
302+
}
241303

242-
for _, decl := range file.Decls {
243-
switch d := decl.(type) {
244-
case *ast.FuncDecl:
245-
k := astutil.FuncKey(d)
246-
if info, ok := replacedDeclNames[k]; ok {
247-
if info.pruneOriginal {
248-
// Prune function bodies, since it may contain code invalid for
249-
// GopherJS and pin unwanted imports.
250-
d.Body = nil
251-
}
252-
if info.keepOriginal {
253-
// Allow overridden function calls
254-
// The standard library implementation of foo() becomes _gopherjs_original_foo()
255-
d.Name.Name = "_gopherjs_original_" + d.Name.Name
256-
} else {
257-
d.Name = ast.NewIdent("_")
258-
}
304+
// augmentOriginalImports is the part of parseAndAugment that processes
305+
// an original file AST to modify the imports for that file.
306+
func augmentOriginalImports(importPath string, file *ast.File) {
307+
switch importPath {
308+
case "crypto/rand", "encoding/gob", "encoding/json", "expvar", "go/token", "log", "math/big", "math/rand", "regexp", "time":
309+
for _, spec := range file.Imports {
310+
path, _ := strconv.Unquote(spec.Path.Value)
311+
if path == "sync" {
312+
if spec.Name == nil {
313+
spec.Name = ast.NewIdent("sync")
259314
}
260-
case *ast.GenDecl:
315+
spec.Path.Value = `"github.com/gopherjs/gopherjs/nosync"`
316+
}
317+
}
318+
}
319+
}
320+
321+
// augmentOriginalFile is the part of parseAndAugment that processes
322+
// an original file AST to augment the source code using the directives
323+
// from the overlay.
324+
func augmentOriginalFile(file *ast.File, replacedDeclNames map[string]overrideInfo) {
325+
for i, decl := range file.Decls {
326+
switch d := decl.(type) {
327+
case *ast.FuncDecl:
328+
k := astutil.FuncKey(d)
329+
if info, ok := replacedDeclNames[k]; ok {
330+
if info.pruneFuncBody {
331+
// Prune function bodies, since it may contain code invalid for
332+
// GopherJS and pin unwanted imports.
333+
d.Body = nil
334+
}
335+
if info.keepOriginal {
336+
// Allow overridden function calls
337+
// The standard library implementation of foo() becomes _gopherjs_original_foo()
338+
d.Name.Name = "_gopherjs_original_" + d.Name.Name
339+
} else {
340+
file.Decls[i] = nil
341+
}
342+
} else if recvKey := astutil.FuncReceiverKey(d); len(recvKey) > 0 {
343+
// check if the receiver has been purged, if so, remove the method too.
344+
if info, ok := replacedDeclNames[recvKey]; ok && info.purgeMethods {
345+
file.Decls[i] = nil
346+
}
347+
}
348+
case *ast.GenDecl:
349+
for j, spec := range d.Specs {
261350
switch d.Tok {
262351
case token.TYPE:
263-
for _, spec := range d.Specs {
264-
s := spec.(*ast.TypeSpec)
265-
if _, ok := replacedDeclNames[s.Name.Name]; ok {
266-
s.Name = ast.NewIdent("_")
267-
s.Type = &ast.StructType{Struct: s.Pos(), Fields: &ast.FieldList{}}
268-
s.TypeParams = nil
269-
}
352+
if _, ok := replacedDeclNames[spec.(*ast.TypeSpec).Name.Name]; ok {
353+
d.Specs[j] = nil
270354
}
271355
case token.VAR, token.CONST:
272-
for _, spec := range d.Specs {
273-
s := spec.(*ast.ValueSpec)
274-
for i, name := range s.Names {
275-
if _, ok := replacedDeclNames[name.Name]; ok {
276-
s.Names[i] = ast.NewIdent("_")
277-
}
356+
s := spec.(*ast.ValueSpec)
357+
for k, name := range s.Names {
358+
if _, ok := replacedDeclNames[name.Name]; ok {
359+
s.Names[k] = nil
278360
}
279361
}
362+
s.Names = astutil.Squeeze(s.Names)
363+
if len(s.Names) == 0 {
364+
d.Specs[j] = nil
365+
}
280366
}
281367
}
368+
d.Specs = astutil.Squeeze(d.Specs)
369+
if len(d.Specs) == 0 {
370+
file.Decls[i] = nil
371+
}
282372
}
373+
}
374+
file.Decls = astutil.Squeeze(file.Decls)
375+
}
283376

284-
files = append(files, file)
377+
// pruneImports will remove any unused imports from the file.
378+
//
379+
// This will not remove any unnamed (`.`) or unused (`_`) imports.
380+
func pruneImports(file *ast.File) {
381+
unused := make(map[string]int, len(file.Imports))
382+
for i, in := range file.Imports {
383+
if name := astutil.ImportName(in); len(name) > 0 {
384+
unused[name] = i
385+
}
285386
}
286387

287-
if errList != nil {
288-
return nil, nil, errList
388+
ast.Walk(astutil.NewCallbackVisitor(func(n ast.Node) bool {
389+
if sel, ok := n.(*ast.SelectorExpr); ok {
390+
if id, ok := sel.X.(*ast.Ident); ok && id.Obj == nil {
391+
delete(unused, id.Name)
392+
}
393+
}
394+
return len(unused) > 0
395+
}), file)
396+
397+
for _, i := range unused {
398+
file.Imports[i] = nil
289399
}
290-
return files, jsFiles, nil
400+
401+
file.Imports = astutil.Squeeze(file.Imports)
291402
}
292403

293404
// Options controls build process behavior.
@@ -678,7 +789,7 @@ func (s *Session) BuildPackage(pkg *PackageData) (*compiler.Archive, error) {
678789
archive := s.buildCache.LoadArchive(pkg.ImportPath)
679790
if archive != nil && !pkg.SrcModTime.After(archive.BuildTime) {
680791
if err := archive.RegisterTypes(s.Types); err != nil {
681-
panic(fmt.Errorf("Failed to load type information from %v: %w", archive, err))
792+
panic(fmt.Errorf("failed to load type information from %v: %w", archive, err))
682793
}
683794
s.UpToDateArchives[pkg.ImportPath] = archive
684795
// Existing archive is up to date, no need to build it from scratch.

0 commit comments

Comments
 (0)