From 82034adb465df44aa89dce3dd8c1f5e7f1a3a35a Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Thu, 20 Feb 2025 09:55:00 -0700 Subject: [PATCH 1/3] using Sources instead of ParsedPackage --- build/build.go | 134 ++++++----------- build/context.go | 41 +---- compiler/compiler_test.go | 17 ++- compiler/decls.go | 3 +- .../{errors.go => errorList/errorList.go} | 2 +- compiler/jsFile/jsFile.go | 55 +++++++ compiler/linkname.go | 3 +- compiler/package.go | 65 +++++--- compiler/sources.go | 123 --------------- compiler/sources/errorCollectingImporter.go | 23 +++ compiler/sources/sources.go | 141 ++++++++++++++++++ tool.go | 3 +- 12 files changed, 333 insertions(+), 277 deletions(-) rename compiler/{errors.go => errorList/errorList.go} (98%) create mode 100644 compiler/jsFile/jsFile.go delete mode 100644 compiler/sources.go create mode 100644 compiler/sources/errorCollectingImporter.go create mode 100644 compiler/sources/sources.go diff --git a/build/build.go b/build/build.go index 62b65d0b2..0bd32aff8 100644 --- a/build/build.go +++ b/build/build.go @@ -27,6 +27,9 @@ import ( "github.com/fsnotify/fsnotify" "github.com/gopherjs/gopherjs/compiler" "github.com/gopherjs/gopherjs/compiler/astutil" + "github.com/gopherjs/gopherjs/compiler/errorList" + "github.com/gopherjs/gopherjs/compiler/jsFile" + "github.com/gopherjs/gopherjs/compiler/sources" "github.com/gopherjs/gopherjs/internal/testmain" log "github.com/sirupsen/logrus" @@ -163,7 +166,7 @@ type overrideInfo struct { // - Otherwise for identifiers that exist in the original and the overrides, // the original is removed. // - New identifiers that don't exist in original package get added. -func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *token.FileSet) ([]*ast.File, []JSFile, error) { +func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *token.FileSet) ([]*ast.File, []jsFile.JSFile, error) { jsFiles, overlayFiles := parseOverlayFiles(xctx, pkg, isTest, fileSet) originalFiles, err := parserOriginalFiles(pkg, fileSet) @@ -192,7 +195,7 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke // parseOverlayFiles loads and parses overlay files // to augment the original files with. -func parseOverlayFiles(xctx XContext, pkg *PackageData, isTest bool, fileSet *token.FileSet) ([]JSFile, []*ast.File) { +func parseOverlayFiles(xctx XContext, pkg *PackageData, isTest bool, fileSet *token.FileSet) ([]jsFile.JSFile, []*ast.File) { isXTest := strings.HasSuffix(pkg.ImportPath, "_test") importPath := pkg.ImportPath if isXTest { @@ -238,7 +241,7 @@ func parseOverlayFiles(xctx XContext, pkg *PackageData, isTest bool, fileSet *to // parserOriginalFiles loads and parses the original files to augment. func parserOriginalFiles(pkg *PackageData, fileSet *token.FileSet) ([]*ast.File, error) { var files []*ast.File - var errList compiler.ErrorList + var errList errorList.ErrorList for _, name := range pkg.GoFiles { if !filepath.IsAbs(name) { // name might be absolute if specified directly. E.g., `gopherjs build /abs/file.go`. name = filepath.Join(pkg.Dir, name) @@ -622,18 +625,11 @@ func (o *Options) PrintSuccess(format string, a ...interface{}) { fmt.Fprintf(os.Stderr, format, a...) } -// JSFile represents a *.inc.js file metadata and content. -type JSFile struct { - Path string // Full file path for the build context the file came from. - ModTime time.Time - Content []byte -} - // PackageData is an extension of go/build.Package with additional metadata // GopherJS requires. type PackageData struct { *build.Package - JSFiles []JSFile + JSFiles []jsFile.JSFile // IsTest is true if the package is being built for running tests. IsTest bool SrcModTime time.Time @@ -743,43 +739,6 @@ func (p *PackageData) InstallPath() string { return p.PkgObj } -// ParsedPackage is the results from building a package before it is compiled. -type ParsedPackage struct { - ImportPath string // import path of package ("" if unknown) - Dir string // directory containing package sources - - // GoFiles is the parsed and augmented Go AST files for the package. - GoFiles []*ast.File - FileSet *token.FileSet - JSFiles []JSFile -} - -// Imports calculates the import paths of the package's dependencies -// based on all the imports in the augmented files. -// -// The given skip paths will not be returned in the results. -// This will not return any `*_test` packages in the results. -func (p *ParsedPackage) Imports(skip ...string) []string { - seen := make(map[string]struct{}) - for _, s := range skip { - seen[s] = struct{}{} - } - imports := []string{} - for _, file := range p.GoFiles { - for _, imp := range file.Imports { - path := strings.Trim(imp.Path.Value, `"`) - if _, ok := seen[path]; !ok { - if !strings.HasSuffix(path, "_test") { - imports = append(imports, path) - } - seen[path] = struct{}{} - } - } - } - sort.Strings(imports) - return imports -} - // Session manages internal state GopherJS requires to perform a build. // // This is the main interface to GopherJS build system. Session lifetime is @@ -793,10 +752,11 @@ type Session struct { // compilation when we're looking up parsed packages. importPaths map[string]map[string]string - // parsePackage is a map of parsed packages that have been built and augmented. + // sources is a map of parsed packages that have been built and augmented. // This is keyed using resolved import paths. This is used to avoid // rebuilding and augmenting packages that are imported by several packages. - parsedPackages map[string]*ParsedPackage + // These sources haven't been sorted nor simplified yet. + sources map[string]*sources.Sources // Binary archives produced during the current session and assumed to be // up to date with input sources and dependencies. In the -w ("watch") mode @@ -813,7 +773,7 @@ func NewSession(options *Options) (*Session, error) { s := &Session{ options: options, importPaths: make(map[string]map[string]string), - parsedPackages: make(map[string]*ParsedPackage), + sources: make(map[string]*sources.Sources), UpToDateArchives: make(map[string]*compiler.Archive), } s.xctx = NewBuildContext(s.InstallSuffix(), s.options.BuildTags) @@ -931,7 +891,7 @@ func (s *Session) BuildFiles(filenames []string, pkgObj string, cwd string) erro if err != nil { return fmt.Errorf("failed to stat %s: %w", file, err) } - pkg.JSFiles = append(pkg.JSFiles, JSFile{ + pkg.JSFiles = append(pkg.JSFiles, jsFile.JSFile{ Path: filepath.Join(pkg.Dir, filepath.Base(file)), ModTime: info.ModTime(), Content: content, @@ -954,13 +914,13 @@ func (s *Session) BuildProject(pkg *PackageData) (*compiler.Archive, error) { // ensure that runtime for gopherjs is imported pkg.Imports = append(pkg.Imports, `runtime`) - // Build the project to get the parsed packages. - var parsed *ParsedPackage + // Build the project to get the sources for the parsed packages. + var srcs *sources.Sources var err error if pkg.IsTest { - parsed, err = s.buildTestPackage(pkg) + srcs, err = s.buildTestPackage(pkg) } else { - parsed, err = s.buildPackages(pkg) + srcs, err = s.buildPackages(pkg) } if err != nil { return nil, err @@ -972,10 +932,10 @@ func (s *Session) BuildProject(pkg *PackageData) (*compiler.Archive, error) { // flatten, blocking, etc. information and check types to get the type info // with all the instances for all generics in the whole project. - return s.compilePackages(parsed) + return s.compilePackages(srcs) } -func (s *Session) buildTestPackage(pkg *PackageData) (*ParsedPackage, error) { +func (s *Session) buildTestPackage(pkg *PackageData) (*sources.Sources, error) { _, err := s.buildPackages(pkg.TestPackage()) if err != nil { return nil, err @@ -994,30 +954,30 @@ func (s *Session) buildTestPackage(pkg *PackageData) (*ParsedPackage, error) { return nil, fmt.Errorf("failed to generate testmain package for %s: %w", pkg.ImportPath, err) } - // Create a parsed package for the testmain package. - parsed := &ParsedPackage{ + // Create the sources for parsed package for the testmain package. + srcs := &sources.Sources{ ImportPath: mainPkg.ImportPath, Dir: mainPkg.Dir, - GoFiles: []*ast.File{mainFile}, + Files: []*ast.File{mainFile}, FileSet: fset, } // Import dependencies for the testmain package. - for _, importedPkgPath := range parsed.Imports() { + for _, importedPkgPath := range srcs.Imports() { _, _, err := s.buildImportPathWithSrcDir(importedPkgPath, pkg.Dir) if err != nil { return nil, err } } - return parsed, nil + return srcs, nil } -// buildImportPathWithSrcDir builds the parsed package specified by the import path. +// buildImportPathWithSrcDir builds the sources for a package specified by the import path. // // Relative import paths are interpreted relative to the passed srcDir. If // srcDir is empty, current working directory is assumed. -func (s *Session) buildImportPathWithSrcDir(path, srcDir string) (*PackageData, *ParsedPackage, error) { +func (s *Session) buildImportPathWithSrcDir(path, srcDir string) (*PackageData, *sources.Sources, error) { pkg, err := s.xctx.Import(path, srcDir, 0) if s.Watcher != nil && pkg != nil { // add watch even on error s.Watcher.Add(pkg.Dir) @@ -1026,13 +986,13 @@ func (s *Session) buildImportPathWithSrcDir(path, srcDir string) (*PackageData, return nil, nil, err } - parsed, err := s.buildPackages(pkg) + srcs, err := s.buildPackages(pkg) if err != nil { return nil, nil, err } s.cacheImportPath(path, srcDir, pkg.ImportPath) - return pkg, parsed, nil + return pkg, srcs, nil } // cacheImportPath stores the import path for the build package so we can look @@ -1073,9 +1033,9 @@ var getExeModTime = func() func() time.Time { } }() -func (s *Session) buildPackages(pkg *PackageData) (*ParsedPackage, error) { - if parsed, ok := s.parsedPackages[pkg.ImportPath]; ok { - return parsed, nil +func (s *Session) buildPackages(pkg *PackageData) (*sources.Sources, error) { + if srcs, ok := s.sources[pkg.ImportPath]; ok { + return srcs, nil } if exeModTime := getExeModTime(); exeModTime.After(pkg.SrcModTime) { @@ -1114,52 +1074,52 @@ func (s *Session) buildPackages(pkg *PackageData) (*ParsedPackage, error) { files = append(files, embed) } - parsed := &ParsedPackage{ + srcs := &sources.Sources{ ImportPath: pkg.ImportPath, Dir: pkg.Dir, - GoFiles: files, + Files: files, FileSet: fileSet, JSFiles: append(pkg.JSFiles, overlayJsFiles...), } - s.parsedPackages[pkg.ImportPath] = parsed + s.sources[pkg.ImportPath] = srcs // Import dependencies from the augmented files, // whilst skipping any that have been already imported. - for _, importedPkgPath := range parsed.Imports(pkg.Imports...) { + for _, importedPkgPath := range srcs.Imports(pkg.Imports...) { _, _, err := s.buildImportPathWithSrcDir(importedPkgPath, pkg.Dir) if err != nil { return nil, err } } - return parsed, nil + return srcs, nil } -func (s *Session) compilePackages(pkg *ParsedPackage) (*compiler.Archive, error) { - if archive, ok := s.UpToDateArchives[pkg.ImportPath]; ok { +func (s *Session) compilePackages(srcs *sources.Sources) (*compiler.Archive, error) { + if archive, ok := s.UpToDateArchives[srcs.ImportPath]; ok { return archive, nil } importContext := &compiler.ImportContext{ - Packages: s.Types, - Import: s.ImportResolverFor(pkg.Dir), + Packages: s.Types, + ImportArchive: s.ImportResolverFor(srcs.Dir), } - archive, err := compiler.Compile(pkg.ImportPath, pkg.GoFiles, pkg.FileSet, importContext, s.options.Minify) + archive, err := compiler.Compile(*srcs, importContext, s.options.Minify) if err != nil { return nil, err } - for _, jsFile := range pkg.JSFiles { + for _, jsFile := range srcs.JSFiles { archive.IncJSCode = append(archive.IncJSCode, []byte("\t(function() {\n")...) archive.IncJSCode = append(archive.IncJSCode, jsFile.Content...) archive.IncJSCode = append(archive.IncJSCode, []byte("\n\t}).call($global);\n")...) } if s.options.Verbose { - fmt.Println(pkg.ImportPath) + fmt.Println(srcs.ImportPath) } - s.UpToDateArchives[pkg.ImportPath] = archive + s.UpToDateArchives[srcs.ImportPath] = archive return archive, nil } @@ -1197,12 +1157,12 @@ func (s *Session) ImportResolverFor(srcDir string) func(string) (*compiler.Archi return archive, nil } - // The archive hasn't been compiled yet so compile it with the parsed package. - if parsed, ok := s.parsedPackages[importPath]; ok { - return s.compilePackages(parsed) + // The archive hasn't been compiled yet so compile it with the sources. + if srcs, ok := s.sources[importPath]; ok { + return s.compilePackages(srcs) } - return nil, fmt.Errorf(`parsed package for %q not found`, importPath) + return nil, fmt.Errorf(`sources for %q not found`, importPath) } } diff --git a/build/context.go b/build/context.go index 316bfb2bb..657300839 100644 --- a/build/context.go +++ b/build/context.go @@ -4,7 +4,6 @@ import ( "fmt" "go/build" "go/token" - "io" "net/http" "os" "os/exec" @@ -16,6 +15,7 @@ import ( _ "github.com/gopherjs/gopherjs/build/versionhack" // go/build release tags hack. "github.com/gopherjs/gopherjs/compiler" "github.com/gopherjs/gopherjs/compiler/gopherjspkg" + "github.com/gopherjs/gopherjs/compiler/jsFile" "github.com/gopherjs/gopherjs/compiler/natives" "golang.org/x/tools/go/buildutil" ) @@ -91,7 +91,7 @@ func (sc simpleCtx) Import(importPath string, srcDir string, mode build.ImportMo if err != nil { return nil, err } - jsFiles, err := jsFilesFromDir(&sc.bctx, pkg.Dir) + jsFiles, err := jsFile.JSFilesFromDir(&sc.bctx, pkg.Dir) if err != nil { return nil, fmt.Errorf("failed to enumerate .inc.js files in %s: %w", pkg.Dir, err) } @@ -440,40 +440,3 @@ func updateImports(sources []string, importPos map[string][]token.Position) (new sort.Strings(newImports) return newImports, newImportPos } - -// jsFilesFromDir finds and loads any *.inc.js packages in the build context -// directory. -func jsFilesFromDir(bctx *build.Context, dir string) ([]JSFile, error) { - files, err := buildutil.ReadDir(bctx, dir) - if err != nil { - return nil, err - } - var jsFiles []JSFile - for _, file := range files { - if !strings.HasSuffix(file.Name(), ".inc.js") || file.IsDir() { - continue - } - if file.Name()[0] == '_' || file.Name()[0] == '.' { - continue // Skip "hidden" files that are typically ignored by the Go build system. - } - - path := buildutil.JoinPath(bctx, dir, file.Name()) - f, err := buildutil.OpenFile(bctx, path) - if err != nil { - return nil, fmt.Errorf("failed to open %s from %v: %w", path, bctx, err) - } - defer f.Close() - - content, err := io.ReadAll(f) - if err != nil { - return nil, fmt.Errorf("failed to read %s from %v: %w", path, bctx, err) - } - - jsFiles = append(jsFiles, JSFile{ - Path: path, - ModTime: file.ModTime(), - Content: content, - }) - } - return jsFiles, nil -} diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index fe75880b1..4c1dd4dba 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -12,6 +12,7 @@ import ( "golang.org/x/tools/go/packages" "github.com/gopherjs/gopherjs/compiler/internal/dce" + "github.com/gopherjs/gopherjs/compiler/sources" "github.com/gopherjs/gopherjs/internal/srctesting" ) @@ -681,7 +682,7 @@ func compileProject(t *testing.T, root *packages.Package, minify bool) map[strin var importContext *ImportContext importContext = &ImportContext{ Packages: map[string]*types.Package{}, - Import: func(path string) (*Archive, error) { + ImportArchive: func(path string) (*Archive, error) { // find in local cache if a, ok := archiveCache[path]; ok { return a, nil @@ -693,8 +694,14 @@ func compileProject(t *testing.T, root *packages.Package, minify bool) map[strin } importContext.Packages[path] = pkg.Types + srcs := sources.Sources{ + ImportPath: path, + Files: pkg.Syntax, + FileSet: pkg.Fset, + } + // compile package - a, err := Compile(path, pkg.Syntax, pkg.Fset, importContext, minify) + a, err := Compile(srcs, importContext, minify) if err != nil { return nil, err } @@ -703,7 +710,7 @@ func compileProject(t *testing.T, root *packages.Package, minify bool) map[strin }, } - _, err := importContext.Import(root.PkgPath) + _, err := importContext.ImportArchive(root.PkgPath) if err != nil { t.Fatal(`failed to compile:`, err) } @@ -737,7 +744,7 @@ func reloadCompiledProject(t *testing.T, archives map[string]*Archive, rootPkgPa var importContext *ImportContext importContext = &ImportContext{ Packages: map[string]*types.Package{}, - Import: func(path string) (*Archive, error) { + ImportArchive: func(path string) (*Archive, error) { // find in local cache if a, ok := reloadCache[path]; ok { return a, nil @@ -757,7 +764,7 @@ func reloadCompiledProject(t *testing.T, archives map[string]*Archive, rootPkgPa }, } - _, err := importContext.Import(rootPkgPath) + _, err := importContext.ImportArchive(rootPkgPath) if err != nil { t.Fatal(`failed to reload archives:`, err) } diff --git a/compiler/decls.go b/compiler/decls.go index e292ad07c..0694181f6 100644 --- a/compiler/decls.go +++ b/compiler/decls.go @@ -16,6 +16,7 @@ import ( "github.com/gopherjs/gopherjs/compiler/internal/dce" "github.com/gopherjs/gopherjs/compiler/internal/symbol" "github.com/gopherjs/gopherjs/compiler/internal/typeparams" + "github.com/gopherjs/gopherjs/compiler/sources" "github.com/gopherjs/gopherjs/compiler/typesutil" ) @@ -81,7 +82,7 @@ func (d *Decl) Dce() *dce.Info { // topLevelObjects extracts package-level variables, functions and named types // from the package AST. -func (fc *funcContext) topLevelObjects(srcs sources) (vars []*types.Var, functions []*ast.FuncDecl, typeNames typesutil.TypeNames) { +func (fc *funcContext) topLevelObjects(srcs sources.Sources) (vars []*types.Var, functions []*ast.FuncDecl, typeNames typesutil.TypeNames) { if !fc.isRoot() { panic(bailout(fmt.Errorf("functionContext.discoverObjects() must be only called on the package-level context"))) } diff --git a/compiler/errors.go b/compiler/errorList/errorList.go similarity index 98% rename from compiler/errors.go rename to compiler/errorList/errorList.go index 48aed48ec..531a0f4e0 100644 --- a/compiler/errors.go +++ b/compiler/errorList/errorList.go @@ -1,4 +1,4 @@ -package compiler +package errorList import ( "errors" diff --git a/compiler/jsFile/jsFile.go b/compiler/jsFile/jsFile.go new file mode 100644 index 000000000..b8ae9421f --- /dev/null +++ b/compiler/jsFile/jsFile.go @@ -0,0 +1,55 @@ +package jsFile + +import ( + "fmt" + "go/build" + "io" + "strings" + "time" + + "golang.org/x/tools/go/buildutil" +) + +// JSFile represents a *.inc.js file metadata and content. +type JSFile struct { + Path string // Full file path for the build context the file came from. + ModTime time.Time + Content []byte +} + +// JSFilesFromDir finds and loads any *.inc.js packages in the build context +// directory. +func JSFilesFromDir(bctx *build.Context, dir string) ([]JSFile, error) { + files, err := buildutil.ReadDir(bctx, dir) + if err != nil { + return nil, err + } + var jsFiles []JSFile + for _, file := range files { + if !strings.HasSuffix(file.Name(), ".inc.js") || file.IsDir() { + continue + } + if file.Name()[0] == '_' || file.Name()[0] == '.' { + continue // Skip "hidden" files that are typically ignored by the Go build system. + } + + path := buildutil.JoinPath(bctx, dir, file.Name()) + f, err := buildutil.OpenFile(bctx, path) + if err != nil { + return nil, fmt.Errorf("failed to open %s from %v: %w", path, bctx, err) + } + defer f.Close() + + content, err := io.ReadAll(f) + if err != nil { + return nil, fmt.Errorf("failed to read %s from %v: %w", path, bctx, err) + } + + jsFiles = append(jsFiles, JSFile{ + Path: path, + ModTime: file.ModTime(), + Content: content, + }) + } + return jsFiles, nil +} diff --git a/compiler/linkname.go b/compiler/linkname.go index c4f15a23e..3a9b47934 100644 --- a/compiler/linkname.go +++ b/compiler/linkname.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/gopherjs/gopherjs/compiler/astutil" + "github.com/gopherjs/gopherjs/compiler/errorList" "github.com/gopherjs/gopherjs/compiler/internal/symbol" ) @@ -38,7 +39,7 @@ type GoLinkname struct { // words, it can only "import" an external function implementation into the // local scope). func parseGoLinknames(fset *token.FileSet, pkgPath string, file *ast.File) ([]GoLinkname, error) { - var errs ErrorList = nil + var errs errorList.ErrorList = nil var directives []GoLinkname isUnsafe := astutil.ImportsUnsafe(file) diff --git a/compiler/package.go b/compiler/package.go index ba018a66b..2039758f0 100644 --- a/compiler/package.go +++ b/compiler/package.go @@ -7,12 +7,15 @@ import ( "go/types" "strings" + "golang.org/x/tools/go/types/typeutil" + + "github.com/gopherjs/gopherjs/compiler/errorList" "github.com/gopherjs/gopherjs/compiler/internal/analysis" "github.com/gopherjs/gopherjs/compiler/internal/dce" "github.com/gopherjs/gopherjs/compiler/internal/typeparams" + "github.com/gopherjs/gopherjs/compiler/sources" "github.com/gopherjs/gopherjs/compiler/typesutil" "github.com/gopherjs/gopherjs/internal/experiments" - "golang.org/x/tools/go/types/typeutil" ) // pkgContext maintains compiler context for a specific package. @@ -35,7 +38,7 @@ type pkgContext struct { indentation int minify bool fileSet *token.FileSet - errList ErrorList + errList errorList.ErrorList instanceSet *typeparams.PackageInstanceSets } @@ -114,7 +117,7 @@ type funcContext struct { funcLitCounter int } -func newRootCtx(tContext *types.Context, srcs sources, typesInfo *types.Info, typesPkg *types.Package, isBlocking func(typeparams.Instance) bool, minify bool) *funcContext { +func newRootCtx(tContext *types.Context, srcs sources.Sources, typesInfo *types.Info, typesPkg *types.Package, isBlocking func(typeparams.Instance) bool, minify bool) *funcContext { tc := typeparams.Collector{ TContext: tContext, Info: typesInfo, @@ -159,10 +162,10 @@ type flowData struct { type ImportContext struct { // Mapping for an absolute import path to the package type information. Packages map[string]*types.Package - // Import returns a previously compiled Archive for a dependency package. If - // the Import() call was successful, the corresponding entry must be added to - // the Packages map. - Import func(importPath string) (*Archive, error) + // ImportArchive returns a previously compiled Archive for a dependency + // package. If the Import() call was successful, the corresponding entry + // must be added to the Packages map. + ImportArchive func(importPath string) (*Archive, error) } // isBlocking returns true if an _imported_ function is blocking. It will panic @@ -178,7 +181,7 @@ func (ic *ImportContext) isBlocking(inst typeparams.Instance) bool { panic(bailout(fmt.Errorf("can't determine if instance %v is blocking: instance isn't for a function object", inst))) } - archive, err := ic.Import(f.Pkg().Path()) + archive, err := ic.ImportArchive(f.Pkg().Path()) if err != nil { panic(err) } @@ -192,11 +195,39 @@ func (ic *ImportContext) isBlocking(inst typeparams.Instance) bool { panic(bailout(fmt.Errorf("can't determine if function %s is blocking: decl not found in package archive", fullName))) } +// Import implements go/types.Importer interface for ImportContext. +func (ic *ImportContext) Import(path string) (*types.Package, error) { + if path == "unsafe" { + return types.Unsafe, nil + } + + // By importing the archive, the package will compile if it hasn't been + // compiled yet and the package will be added to the Packages map. + a, err := ic.ImportArchive(path) + if err != nil { + return nil, err + } + + return ic.Packages[a.ImportPath], nil +} + +// parseAllGoLinknames extracts all //go:linkname compiler directive from the sources. +func parseAllGoLinknames(s sources.Sources) ([]GoLinkname, error) { + goLinknames := []GoLinkname{} + var errs errorList.ErrorList + for _, file := range s.Files { + found, err := parseGoLinknames(s.FileSet, s.ImportPath, file) + errs = errs.Append(err) + goLinknames = append(goLinknames, found...) + } + return goLinknames, errs.ErrOrNil() +} + // Compile the provided Go sources as a single package. // // Import path must be the absolute import path for a package. Provided sources // are always sorted by name to ensure reproducible JavaScript output. -func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, importContext *ImportContext, minify bool) (_ *Archive, err error) { +func Compile(srcs sources.Sources, importContext *ImportContext, minify bool) (_ *Archive, err error) { defer func() { e := recover() if e == nil { @@ -204,32 +235,28 @@ func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, impor } if fe, ok := bailingOut(e); ok { // Orderly bailout, return whatever clues we already have. - fmt.Fprintf(fe, `building package %q`, importPath) + fmt.Fprintf(fe, `building package %q`, srcs.ImportPath) err = fe return } // Some other unexpected panic, catch the stack trace and return as an error. - err = bailout(fmt.Errorf("unexpected compiler panic while building package %q: %v", importPath, e)) + err = bailout(fmt.Errorf("unexpected compiler panic while building package %q: %v", srcs.ImportPath, e)) }() - srcs := sources{ - ImportPath: importPath, - Files: files, - FileSet: fileSet, - }.Sort() + srcs.Sort() tContext := types.NewContext() - typesInfo, typesPkg, err := srcs.TypeCheck(importContext, tContext) + typesInfo, typesPkg, err := srcs.TypeCheck(importContext, sizes32, tContext) if err != nil { return nil, err } if genErr := typeparams.RequiresGenericsSupport(typesInfo); genErr != nil && !experiments.Env.Generics { - return nil, fmt.Errorf("package %s requires generics support (https://github.com/gopherjs/gopherjs/issues/1013): %w", importPath, genErr) + return nil, fmt.Errorf("package %s requires generics support (https://github.com/gopherjs/gopherjs/issues/1013): %w", srcs.ImportPath, genErr) } importContext.Packages[srcs.ImportPath] = typesPkg // Extract all go:linkname compiler directives from the package source. - goLinknames, err := srcs.ParseGoLinknames() + goLinknames, err := parseAllGoLinknames(srcs) if err != nil { return nil, err } diff --git a/compiler/sources.go b/compiler/sources.go deleted file mode 100644 index e6c3710f4..000000000 --- a/compiler/sources.go +++ /dev/null @@ -1,123 +0,0 @@ -package compiler - -import ( - "go/ast" - "go/token" - "go/types" - "sort" - - "github.com/neelance/astrewrite" -) - -// sources is a slice of parsed Go sources. -// -// Note that the sources would normally belong to a single logical Go package, -// but they don't have to be a real Go package (i.e. found on the file system) -// or represent a complete package (i.e. it could be only a few source files -// compiled by `gopherjs build foo.go bar.go`). -type sources struct { - // ImportPath representing the sources, if exists. May be empty for "virtual" - // packages like testmain or playground-generated package. - ImportPath string - Files []*ast.File - FileSet *token.FileSet -} - -// Sort the Files slice by the original source name to ensure consistent order -// of processing. This is required for reproducible JavaScript output. -// -// Note this function mutates the original slice. -func (s sources) Sort() sources { - sort.Slice(s.Files, func(i, j int) bool { - return s.FileSet.File(s.Files[i].Pos()).Name() > s.FileSet.File(s.Files[j].Pos()).Name() - }) - return s -} - -// Simplified returns a new sources instance with each Files entry processed by -// astrewrite.Simplify. -func (s sources) Simplified(typesInfo *types.Info) sources { - simplified := sources{ - ImportPath: s.ImportPath, - FileSet: s.FileSet, - } - for _, file := range s.Files { - simplified.Files = append(simplified.Files, astrewrite.Simplify(file, typesInfo, false)) - } - return simplified -} - -// TypeCheck the sources. Returns information about declared package types and -// type information for the supplied AST. -func (s sources) TypeCheck(importContext *ImportContext, tContext *types.Context) (*types.Info, *types.Package, error) { - const errLimit = 10 // Max number of type checking errors to return. - - typesInfo := &types.Info{ - Types: make(map[ast.Expr]types.TypeAndValue), - Defs: make(map[*ast.Ident]types.Object), - Uses: make(map[*ast.Ident]types.Object), - Implicits: make(map[ast.Node]types.Object), - Selections: make(map[*ast.SelectorExpr]*types.Selection), - Scopes: make(map[ast.Node]*types.Scope), - Instances: make(map[*ast.Ident]types.Instance), - } - - var typeErrs ErrorList - - importer := packageImporter{ImportContext: importContext} - - config := &types.Config{ - Context: tContext, - Importer: &importer, - Sizes: sizes32, - Error: func(err error) { typeErrs = typeErrs.AppendDistinct(err) }, - } - typesPkg, err := config.Check(s.ImportPath, s.FileSet, s.Files, typesInfo) - // If we encountered any import errors, it is likely that the other type errors - // are not meaningful and would be resolved by fixing imports. Return them - // separately, if any. https://github.com/gopherjs/gopherjs/issues/119. - if importer.Errors.ErrOrNil() != nil { - return nil, nil, importer.Errors.Trim(errLimit).ErrOrNil() - } - // Return any other type errors. - if typeErrs.ErrOrNil() != nil { - return nil, nil, typeErrs.Trim(errLimit).ErrOrNil() - } - // Any general errors that may have occurred during type checking. - if err != nil { - return nil, nil, err - } - return typesInfo, typesPkg, nil -} - -// ParseGoLinknames extracts all //go:linkname compiler directive from the sources. -func (s sources) ParseGoLinknames() ([]GoLinkname, error) { - goLinknames := []GoLinkname{} - var errs ErrorList - for _, file := range s.Files { - found, err := parseGoLinknames(s.FileSet, s.ImportPath, file) - errs = errs.Append(err) - goLinknames = append(goLinknames, found...) - } - return goLinknames, errs.ErrOrNil() -} - -// packageImporter implements go/types.Importer interface. -type packageImporter struct { - ImportContext *ImportContext - Errors ErrorList -} - -func (pi *packageImporter) Import(path string) (*types.Package, error) { - if path == "unsafe" { - return types.Unsafe, nil - } - - a, err := pi.ImportContext.Import(path) - if err != nil { - pi.Errors = pi.Errors.AppendDistinct(err) - return nil, err - } - - return pi.ImportContext.Packages[a.ImportPath], nil -} diff --git a/compiler/sources/errorCollectingImporter.go b/compiler/sources/errorCollectingImporter.go new file mode 100644 index 000000000..bb9325230 --- /dev/null +++ b/compiler/sources/errorCollectingImporter.go @@ -0,0 +1,23 @@ +package sources + +import ( + "go/types" + + "github.com/gopherjs/gopherjs/compiler/errorList" +) + +// errorCollectingImporter implements go/types.Importer interface and +// wraps it to collect import errors. +type errorCollectingImporter struct { + Importer types.Importer + Errors errorList.ErrorList +} + +func (ei *errorCollectingImporter) Import(path string) (*types.Package, error) { + pkg, err := ei.Importer.Import(path) + if err != nil { + ei.Errors = ei.Errors.AppendDistinct(err) + return nil, err + } + return pkg, nil +} diff --git a/compiler/sources/sources.go b/compiler/sources/sources.go new file mode 100644 index 000000000..92b38d966 --- /dev/null +++ b/compiler/sources/sources.go @@ -0,0 +1,141 @@ +package sources + +import ( + "go/ast" + "go/token" + "go/types" + "sort" + "strings" + + "github.com/gopherjs/gopherjs/compiler/errorList" + "github.com/gopherjs/gopherjs/compiler/jsFile" + "github.com/neelance/astrewrite" +) + +// Sources is a slice of parsed Go sources and additional data for a package. +// +// Note that the sources would normally belong to a single logical Go package, +// but they don't have to be a real Go package (i.e. found on the file system) +// or represent a complete package (i.e. it could be only a few source files +// compiled by `gopherjs build foo.go bar.go`). +type Sources struct { + // ImportPath representing the sources, if exists. + // + // May be empty for "virtual" + // packages like testmain or playground-generated package. + // Otherwise this must be the absolute import path for a package. + ImportPath string + + // Dir is the directory containing package sources + Dir string + + // Files is the parsed and augmented Go AST files for the package. + Files []*ast.File + + // FileSet is the file set for the parsed files. + FileSet *token.FileSet + + // JSFiles is the JavaScript files that are part of the package. + JSFiles []jsFile.JSFile +} + +// Sort the Go files slice by the original source name to ensure consistent order +// of processing. This is required for reproducible JavaScript output. +// +// Note this function mutates the original slice. +func (s Sources) Sort() Sources { + sort.Slice(s.Files, func(i, j int) bool { + return s.nameOfFileAtIndex(i) > s.nameOfFileAtIndex(j) + }) + return s +} + +// nameOfFileAtIndex gets the name of the Go source file at the given index. +func (s Sources) nameOfFileAtIndex(i int) string { + return s.FileSet.File(s.Files[i].Pos()).Name() +} + +// Simplified returns a new sources instance with each Files entry processed by +// astrewrite.Simplify. The JSFiles are copied unchanged. +func (s Sources) Simplified(typesInfo *types.Info) Sources { + simplified := Sources{ + ImportPath: s.ImportPath, + Dir: s.Dir, + Files: make([]*ast.File, len(s.Files)), + FileSet: s.FileSet, + JSFiles: s.JSFiles, + } + for i, file := range s.Files { + simplified.Files[i] = astrewrite.Simplify(file, typesInfo, false) + } + return simplified +} + +// TypeCheck the sources. Returns information about declared package types and +// type information for the supplied AST. +func (s Sources) TypeCheck(importer types.Importer, sizes types.Sizes, tContext *types.Context) (*types.Info, *types.Package, error) { + const errLimit = 10 // Max number of type checking errors to return. + + typesInfo := &types.Info{ + Types: make(map[ast.Expr]types.TypeAndValue), + Defs: make(map[*ast.Ident]types.Object), + Uses: make(map[*ast.Ident]types.Object), + Implicits: make(map[ast.Node]types.Object), + Selections: make(map[*ast.SelectorExpr]*types.Selection), + Scopes: make(map[ast.Node]*types.Scope), + Instances: make(map[*ast.Ident]types.Instance), + } + + var typeErrs errorList.ErrorList + + ecImporter := &errorCollectingImporter{Importer: importer} + + config := &types.Config{ + Context: tContext, + Importer: ecImporter, + Sizes: sizes, + Error: func(err error) { typeErrs = typeErrs.AppendDistinct(err) }, + } + typesPkg, err := config.Check(s.ImportPath, s.FileSet, s.Files, typesInfo) + // If we encountered any import errors, it is likely that the other type errors + // are not meaningful and would be resolved by fixing imports. Return them + // separately, if any. https://github.com/gopherjs/gopherjs/issues/119. + if ecImporter.Errors.ErrOrNil() != nil { + return nil, nil, ecImporter.Errors.Trim(errLimit).ErrOrNil() + } + // Return any other type errors. + if typeErrs.ErrOrNil() != nil { + return nil, nil, typeErrs.Trim(errLimit).ErrOrNil() + } + // Any general errors that may have occurred during type checking. + if err != nil { + return nil, nil, err + } + return typesInfo, typesPkg, nil +} + +// Imports calculates the import paths of the package's dependencies +// based on all the imports in the augmented Go AST files. +// +// The given skip paths will not be returned in the results. +// This will not return any `*_test` packages in the results. +func (s Sources) Imports(skip ...string) []string { + seen := make(map[string]struct{}) + for _, sk := range skip { + seen[sk] = struct{}{} + } + imports := []string{} + for _, file := range s.Files { + for _, imp := range file.Imports { + path := strings.Trim(imp.Path.Value, `"`) + if _, ok := seen[path]; !ok { + if !strings.HasSuffix(path, "_test") { + imports = append(imports, path) + } + seen[path] = struct{}{} + } + } + } + sort.Strings(imports) + return imports +} diff --git a/tool.go b/tool.go index 9f791af1b..c1b63ffd7 100644 --- a/tool.go +++ b/tool.go @@ -26,6 +26,7 @@ import ( gbuild "github.com/gopherjs/gopherjs/build" "github.com/gopherjs/gopherjs/build/cache" "github.com/gopherjs/gopherjs/compiler" + "github.com/gopherjs/gopherjs/compiler/errorList" "github.com/gopherjs/gopherjs/internal/sysutil" "github.com/neelance/sourcemap" log "github.com/sirupsen/logrus" @@ -766,7 +767,7 @@ func handleError(err error, options *gbuild.Options, browserErrors *bytes.Buffer switch err := err.(type) { case nil: return 0 - case compiler.ErrorList: + case errorList.ErrorList: for _, entry := range err { printError(entry, options, browserErrors) } From 5740abfa806052fe76f92e661fe7f8340304b4ab Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Wed, 26 Feb 2025 10:38:36 -0700 Subject: [PATCH 2/3] made requested changes from prior ticket and did some cleanup --- build/build.go | 10 +++++++--- compiler/linkname.go | 2 +- compiler/package.go | 2 +- compiler/sources/errorCollectingImporter.go | 2 +- compiler/sources/sources.go | 13 +++++++++---- {compiler => internal}/errorList/errorList.go | 0 tool.go | 2 +- 7 files changed, 20 insertions(+), 11 deletions(-) rename {compiler => internal}/errorList/errorList.go (100%) diff --git a/build/build.go b/build/build.go index c0be20fe6..4517979c0 100644 --- a/build/build.go +++ b/build/build.go @@ -27,9 +27,9 @@ import ( "github.com/fsnotify/fsnotify" "github.com/gopherjs/gopherjs/compiler" "github.com/gopherjs/gopherjs/compiler/astutil" - "github.com/gopherjs/gopherjs/compiler/errorList" "github.com/gopherjs/gopherjs/compiler/jsFile" "github.com/gopherjs/gopherjs/compiler/sources" + "github.com/gopherjs/gopherjs/internal/errorList" "github.com/gopherjs/gopherjs/internal/testmain" log "github.com/sirupsen/logrus" @@ -967,7 +967,7 @@ func (s *Session) loadTestPackage(pkg *PackageData) (*sources.Sources, error) { } // Import dependencies for the testmain package. - for _, importedPkgPath := range srcs.Imports() { + for _, importedPkgPath := range srcs.UnresolvedImports() { _, _, err := s.loadImportPathWithSrcDir(importedPkgPath, pkg.Dir) if err != nil { return nil, err @@ -1037,6 +1037,10 @@ var getExeModTime = func() func() time.Time { } }() +// loadPackages will recursively load and parse the given package and +// its dependencies. This will return the sources for the given package. +// The returned source and sources for the dependencies will be added +// to the session's sources map. func (s *Session) loadPackages(pkg *PackageData) (*sources.Sources, error) { if srcs, ok := s.sources[pkg.ImportPath]; ok { return srcs, nil @@ -1089,7 +1093,7 @@ func (s *Session) loadPackages(pkg *PackageData) (*sources.Sources, error) { // Import dependencies from the augmented files, // whilst skipping any that have been already imported. - for _, importedPkgPath := range srcs.Imports(pkg.Imports...) { + for _, importedPkgPath := range srcs.UnresolvedImports(pkg.Imports...) { _, _, err := s.loadImportPathWithSrcDir(importedPkgPath, pkg.Dir) if err != nil { return nil, err diff --git a/compiler/linkname.go b/compiler/linkname.go index 3a9b47934..af3ef6cc0 100644 --- a/compiler/linkname.go +++ b/compiler/linkname.go @@ -7,8 +7,8 @@ import ( "strings" "github.com/gopherjs/gopherjs/compiler/astutil" - "github.com/gopherjs/gopherjs/compiler/errorList" "github.com/gopherjs/gopherjs/compiler/internal/symbol" + "github.com/gopherjs/gopherjs/internal/errorList" ) // GoLinkname describes a go:linkname compiler directive found in the source code. diff --git a/compiler/package.go b/compiler/package.go index 2039758f0..239c9bf75 100644 --- a/compiler/package.go +++ b/compiler/package.go @@ -9,12 +9,12 @@ import ( "golang.org/x/tools/go/types/typeutil" - "github.com/gopherjs/gopherjs/compiler/errorList" "github.com/gopherjs/gopherjs/compiler/internal/analysis" "github.com/gopherjs/gopherjs/compiler/internal/dce" "github.com/gopherjs/gopherjs/compiler/internal/typeparams" "github.com/gopherjs/gopherjs/compiler/sources" "github.com/gopherjs/gopherjs/compiler/typesutil" + "github.com/gopherjs/gopherjs/internal/errorList" "github.com/gopherjs/gopherjs/internal/experiments" ) diff --git a/compiler/sources/errorCollectingImporter.go b/compiler/sources/errorCollectingImporter.go index bb9325230..73bd3c65d 100644 --- a/compiler/sources/errorCollectingImporter.go +++ b/compiler/sources/errorCollectingImporter.go @@ -3,7 +3,7 @@ package sources import ( "go/types" - "github.com/gopherjs/gopherjs/compiler/errorList" + "github.com/gopherjs/gopherjs/internal/errorList" ) // errorCollectingImporter implements go/types.Importer interface and diff --git a/compiler/sources/sources.go b/compiler/sources/sources.go index 92b38d966..fbcc98b95 100644 --- a/compiler/sources/sources.go +++ b/compiler/sources/sources.go @@ -7,8 +7,8 @@ import ( "sort" "strings" - "github.com/gopherjs/gopherjs/compiler/errorList" "github.com/gopherjs/gopherjs/compiler/jsFile" + "github.com/gopherjs/gopherjs/internal/errorList" "github.com/neelance/astrewrite" ) @@ -114,12 +114,17 @@ func (s Sources) TypeCheck(importer types.Importer, sizes types.Sizes, tContext return typesInfo, typesPkg, nil } -// Imports calculates the import paths of the package's dependencies +// UnresolvedImports calculates the import paths of the package's dependencies // based on all the imports in the augmented Go AST files. // -// The given skip paths will not be returned in the results. +// This is used to determine the unresolved imports that weren't in the +// PackageData.Imports slice since they were added during augmentation or +// during template generation. +// +// The given skip paths (typically those imports from PackageData.Imports) +// will not be returned in the results. // This will not return any `*_test` packages in the results. -func (s Sources) Imports(skip ...string) []string { +func (s Sources) UnresolvedImports(skip ...string) []string { seen := make(map[string]struct{}) for _, sk := range skip { seen[sk] = struct{}{} diff --git a/compiler/errorList/errorList.go b/internal/errorList/errorList.go similarity index 100% rename from compiler/errorList/errorList.go rename to internal/errorList/errorList.go diff --git a/tool.go b/tool.go index c1b63ffd7..46d6a6edc 100644 --- a/tool.go +++ b/tool.go @@ -26,7 +26,7 @@ import ( gbuild "github.com/gopherjs/gopherjs/build" "github.com/gopherjs/gopherjs/build/cache" "github.com/gopherjs/gopherjs/compiler" - "github.com/gopherjs/gopherjs/compiler/errorList" + "github.com/gopherjs/gopherjs/internal/errorList" "github.com/gopherjs/gopherjs/internal/sysutil" "github.com/neelance/sourcemap" log "github.com/sirupsen/logrus" From c8b8905d151fc4aa5bdfac288bb1b8c713f9d71b Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Thu, 27 Feb 2025 15:34:43 -0700 Subject: [PATCH 3/3] moved linkname to its own package --- compiler/compiler.go | 9 +++-- compiler/compiler_test.go | 3 +- compiler/{ => linkname}/linkname.go | 23 ++++++----- compiler/{ => linkname}/linkname_test.go | 4 +- compiler/package.go | 14 +------ compiler/sources/errorCollectingImporter.go | 23 ----------- compiler/sources/sources.go | 43 +++++++++++++++++---- compiler/utils.go | 5 --- 8 files changed, 60 insertions(+), 64 deletions(-) rename compiler/{ => linkname}/linkname.go (88%) rename compiler/{ => linkname}/linkname_test.go (98%) delete mode 100644 compiler/sources/errorCollectingImporter.go diff --git a/compiler/compiler.go b/compiler/compiler.go index 96ec390d8..e8264c946 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -18,6 +18,7 @@ import ( "time" "github.com/gopherjs/gopherjs/compiler/internal/dce" + "github.com/gopherjs/gopherjs/compiler/linkname" "github.com/gopherjs/gopherjs/compiler/prelude" "golang.org/x/tools/go/gcexportdata" ) @@ -59,7 +60,7 @@ type Archive struct { // Whether or not the package was compiled with minification enabled. Minified bool // A list of go:linkname directives encountered in the package. - GoLinknames []GoLinkname + GoLinknames []linkname.GoLinkname } func (a Archive) String() string { @@ -112,7 +113,7 @@ func WriteProgramCode(pkgs []*Archive, w *SourceMapFilter, goVersion string) err minify := mainPkg.Minified // Aggregate all go:linkname directives in the program together. - gls := goLinknameSet{} + gls := linkname.GoLinknameSet{} for _, pkg := range pkgs { gls.Add(pkg.GoLinknames) } @@ -164,7 +165,7 @@ func WriteProgramCode(pkgs []*Archive, w *SourceMapFilter, goVersion string) err return nil } -func WritePkgCode(pkg *Archive, dceSelection map[*Decl]struct{}, gls goLinknameSet, minify bool, w *SourceMapFilter) error { +func WritePkgCode(pkg *Archive, dceSelection map[*Decl]struct{}, gls linkname.GoLinknameSet, minify bool, w *SourceMapFilter) error { if w.MappingCallback != nil && pkg.FileSet != nil { w.fileSet = pkg.FileSet } @@ -264,7 +265,7 @@ type serializableArchive struct { IncJSCode []byte FileSet []byte Minified bool - GoLinknames []GoLinkname + GoLinknames []linkname.GoLinkname BuildTime time.Time } diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index 4c1dd4dba..0742cbaaa 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -12,6 +12,7 @@ import ( "golang.org/x/tools/go/packages" "github.com/gopherjs/gopherjs/compiler/internal/dce" + "github.com/gopherjs/gopherjs/compiler/linkname" "github.com/gopherjs/gopherjs/compiler/sources" "github.com/gopherjs/gopherjs/internal/srctesting" ) @@ -782,7 +783,7 @@ func renderPackage(t *testing.T, archive *Archive, minify bool) string { buf := &bytes.Buffer{} - if err := WritePkgCode(archive, selection, goLinknameSet{}, minify, &SourceMapFilter{Writer: buf}); err != nil { + if err := WritePkgCode(archive, selection, linkname.GoLinknameSet{}, minify, &SourceMapFilter{Writer: buf}); err != nil { t.Fatal(err) } diff --git a/compiler/linkname.go b/compiler/linkname/linkname.go similarity index 88% rename from compiler/linkname.go rename to compiler/linkname/linkname.go index af3ef6cc0..6c3a9623c 100644 --- a/compiler/linkname.go +++ b/compiler/linkname/linkname.go @@ -1,4 +1,4 @@ -package compiler +package linkname import ( "fmt" @@ -22,7 +22,7 @@ type GoLinkname struct { Reference symbol.Name } -// parseGoLinknames processed comments in a source file and extracts //go:linkname +// ParseGoLinknames processed comments in a source file and extracts //go:linkname // compiler directive from the comments. // // The following directive format is supported: @@ -38,7 +38,7 @@ type GoLinkname struct { // - The local function referenced by the directive must have no body (in other // words, it can only "import" an external function implementation into the // local scope). -func parseGoLinknames(fset *token.FileSet, pkgPath string, file *ast.File) ([]GoLinkname, error) { +func ParseGoLinknames(fset *token.FileSet, pkgPath string, file *ast.File) ([]GoLinkname, error) { var errs errorList.ErrorList = nil var directives []GoLinkname @@ -108,7 +108,7 @@ func parseGoLinknames(fset *token.FileSet, pkgPath string, file *ast.File) ([]Go for _, cg := range file.Comments { for _, c := range cg.List { if err := processComment(c); err != nil { - errs = append(errs, ErrorAt(err, fset, c.Pos())) + errs = append(errs, errorAt(err, fset, c.Pos())) } } } @@ -116,15 +116,20 @@ func parseGoLinknames(fset *token.FileSet, pkgPath string, file *ast.File) ([]Go return directives, errs.ErrOrNil() } -// goLinknameSet is a utility that enables quick lookup of whether a decl is +// errorAt annotates an error with a position in the source code. +func errorAt(err error, fset *token.FileSet, pos token.Pos) error { + return fmt.Errorf("%s: %w", fset.Position(pos), err) +} + +// GoLinknameSet is a utility that enables quick lookup of whether a decl is // affected by any go:linkname directive in the program. -type goLinknameSet struct { +type GoLinknameSet struct { byImplementation map[symbol.Name][]GoLinkname byReference map[symbol.Name]GoLinkname } // Add more GoLinkname directives into the set. -func (gls *goLinknameSet) Add(entries []GoLinkname) error { +func (gls *GoLinknameSet) Add(entries []GoLinkname) error { if gls.byImplementation == nil { gls.byImplementation = map[symbol.Name][]GoLinkname{} } @@ -144,7 +149,7 @@ func (gls *goLinknameSet) Add(entries []GoLinkname) error { // IsImplementation returns true if there is a directive referencing this symbol // as an implementation. -func (gls *goLinknameSet) IsImplementation(sym symbol.Name) bool { +func (gls *GoLinknameSet) IsImplementation(sym symbol.Name) bool { _, found := gls.byImplementation[sym] return found } @@ -152,7 +157,7 @@ func (gls *goLinknameSet) IsImplementation(sym symbol.Name) bool { // FindImplementation returns a symbol name, which provides the implementation // for the given symbol. The second value indicates whether the implementation // was found. -func (gls *goLinknameSet) FindImplementation(sym symbol.Name) (symbol.Name, bool) { +func (gls *GoLinknameSet) FindImplementation(sym symbol.Name) (symbol.Name, bool) { directive, found := gls.byReference[sym] return directive.Implementation, found } diff --git a/compiler/linkname_test.go b/compiler/linkname/linkname_test.go similarity index 98% rename from compiler/linkname_test.go rename to compiler/linkname/linkname_test.go index 7f46c6cfb..e2abc2825 100644 --- a/compiler/linkname_test.go +++ b/compiler/linkname/linkname_test.go @@ -1,4 +1,4 @@ -package compiler +package linkname import ( "go/ast" @@ -151,7 +151,7 @@ func TestParseGoLinknames(t *testing.T) { for _, test := range tests { t.Run(test.desc, func(t *testing.T) { file, fset := parseSource(t, test.src) - directives, err := parseGoLinknames(fset, "testcase", file) + directives, err := ParseGoLinknames(fset, "testcase", file) if test.wantError != "" { if err == nil { diff --git a/compiler/package.go b/compiler/package.go index 239c9bf75..5345b5666 100644 --- a/compiler/package.go +++ b/compiler/package.go @@ -211,18 +211,6 @@ func (ic *ImportContext) Import(path string) (*types.Package, error) { return ic.Packages[a.ImportPath], nil } -// parseAllGoLinknames extracts all //go:linkname compiler directive from the sources. -func parseAllGoLinknames(s sources.Sources) ([]GoLinkname, error) { - goLinknames := []GoLinkname{} - var errs errorList.ErrorList - for _, file := range s.Files { - found, err := parseGoLinknames(s.FileSet, s.ImportPath, file) - errs = errs.Append(err) - goLinknames = append(goLinknames, found...) - } - return goLinknames, errs.ErrOrNil() -} - // Compile the provided Go sources as a single package. // // Import path must be the absolute import path for a package. Provided sources @@ -256,7 +244,7 @@ func Compile(srcs sources.Sources, importContext *ImportContext, minify bool) (_ importContext.Packages[srcs.ImportPath] = typesPkg // Extract all go:linkname compiler directives from the package source. - goLinknames, err := parseAllGoLinknames(srcs) + goLinknames, err := srcs.ParseGoLinknames() if err != nil { return nil, err } diff --git a/compiler/sources/errorCollectingImporter.go b/compiler/sources/errorCollectingImporter.go deleted file mode 100644 index 73bd3c65d..000000000 --- a/compiler/sources/errorCollectingImporter.go +++ /dev/null @@ -1,23 +0,0 @@ -package sources - -import ( - "go/types" - - "github.com/gopherjs/gopherjs/internal/errorList" -) - -// errorCollectingImporter implements go/types.Importer interface and -// wraps it to collect import errors. -type errorCollectingImporter struct { - Importer types.Importer - Errors errorList.ErrorList -} - -func (ei *errorCollectingImporter) Import(path string) (*types.Package, error) { - pkg, err := ei.Importer.Import(path) - if err != nil { - ei.Errors = ei.Errors.AppendDistinct(err) - return nil, err - } - return pkg, nil -} diff --git a/compiler/sources/sources.go b/compiler/sources/sources.go index fbcc98b95..e66fa2243 100644 --- a/compiler/sources/sources.go +++ b/compiler/sources/sources.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/gopherjs/gopherjs/compiler/jsFile" + "github.com/gopherjs/gopherjs/compiler/linkname" "github.com/gopherjs/gopherjs/internal/errorList" "github.com/neelance/astrewrite" ) @@ -45,16 +46,11 @@ type Sources struct { // Note this function mutates the original slice. func (s Sources) Sort() Sources { sort.Slice(s.Files, func(i, j int) bool { - return s.nameOfFileAtIndex(i) > s.nameOfFileAtIndex(j) + return s.FileSet.File(s.Files[i].Pos()).Name() > s.FileSet.File(s.Files[j].Pos()).Name() }) return s } -// nameOfFileAtIndex gets the name of the Go source file at the given index. -func (s Sources) nameOfFileAtIndex(i int) string { - return s.FileSet.File(s.Files[i].Pos()).Name() -} - // Simplified returns a new sources instance with each Files entry processed by // astrewrite.Simplify. The JSFiles are copied unchanged. func (s Sources) Simplified(typesInfo *types.Info) Sources { @@ -88,7 +84,7 @@ func (s Sources) TypeCheck(importer types.Importer, sizes types.Sizes, tContext var typeErrs errorList.ErrorList - ecImporter := &errorCollectingImporter{Importer: importer} + ecImporter := &packageImporter{Importer: importer} config := &types.Config{ Context: tContext, @@ -114,6 +110,18 @@ func (s Sources) TypeCheck(importer types.Importer, sizes types.Sizes, tContext return typesInfo, typesPkg, nil } +// ParseGoLinknames extracts all //go:linkname compiler directive from the sources. +func (s Sources) ParseGoLinknames() ([]linkname.GoLinkname, error) { + goLinknames := []linkname.GoLinkname{} + var errs errorList.ErrorList + for _, file := range s.Files { + found, err := linkname.ParseGoLinknames(s.FileSet, s.ImportPath, file) + errs = errs.Append(err) + goLinknames = append(goLinknames, found...) + } + return goLinknames, errs.ErrOrNil() +} + // UnresolvedImports calculates the import paths of the package's dependencies // based on all the imports in the augmented Go AST files. // @@ -144,3 +152,24 @@ func (s Sources) UnresolvedImports(skip ...string) []string { sort.Strings(imports) return imports } + +// packageImporter implements go/types.Importer interface and +// wraps it to collect import errors. +type packageImporter struct { + Importer types.Importer + Errors errorList.ErrorList +} + +func (ei *packageImporter) Import(path string) (*types.Package, error) { + if path == "unsafe" { + return types.Unsafe, nil + } + + pkg, err := ei.Importer.Import(path) + if err != nil { + ei.Errors = ei.Errors.AppendDistinct(err) + return nil, err + } + + return pkg, nil +} diff --git a/compiler/utils.go b/compiler/utils.go index 5d2cb4629..83b826ce2 100644 --- a/compiler/utils.go +++ b/compiler/utils.go @@ -933,11 +933,6 @@ func formatJSStructTagVal(jsTag string) string { return "." + jsTag } -// ErrorAt annotates an error with a position in the source code. -func ErrorAt(err error, fset *token.FileSet, pos token.Pos) error { - return fmt.Errorf("%s: %w", fset.Position(pos), err) -} - // FatalError is an error compiler panics with when it encountered a fatal error. // // FatalError implements io.Writer, which can be used to record any free-form