github.com/aykevl/tinygo@v0.5.0/loader/loader.go (about)

     1  package loader
     2  
     3  import (
     4  	"errors"
     5  	"go/ast"
     6  	"go/build"
     7  	"go/parser"
     8  	"go/token"
     9  	"go/types"
    10  	"os"
    11  	"path/filepath"
    12  	"sort"
    13  )
    14  
    15  // Program holds all packages and some metadata about the program as a whole.
    16  type Program struct {
    17  	Build         *build.Context
    18  	OverlayBuild  *build.Context
    19  	ShouldOverlay func(path string) bool
    20  	Packages      map[string]*Package
    21  	sorted        []*Package
    22  	fset          *token.FileSet
    23  	TypeChecker   types.Config
    24  	Dir           string // current working directory (for error reporting)
    25  	CFlags        []string
    26  }
    27  
    28  // Package holds a loaded package, its imports, and its parsed files.
    29  type Package struct {
    30  	*Program
    31  	*build.Package
    32  	Imports   map[string]*Package
    33  	Importing bool
    34  	Files     []*ast.File
    35  	Pkg       *types.Package
    36  	types.Info
    37  }
    38  
    39  // Import loads the given package relative to srcDir (for the vendor directory).
    40  // It only loads the current package without recursion.
    41  func (p *Program) Import(path, srcDir string) (*Package, error) {
    42  	if p.Packages == nil {
    43  		p.Packages = make(map[string]*Package)
    44  	}
    45  
    46  	// Load this package.
    47  	ctx := p.Build
    48  	if p.ShouldOverlay(path) {
    49  		ctx = p.OverlayBuild
    50  	}
    51  	buildPkg, err := ctx.Import(path, srcDir, build.ImportComment)
    52  	if err != nil {
    53  		return nil, err
    54  	}
    55  	if existingPkg, ok := p.Packages[buildPkg.ImportPath]; ok {
    56  		// Already imported, or at least started the import.
    57  		return existingPkg, nil
    58  	}
    59  	p.sorted = nil // invalidate the sorted order of packages
    60  	pkg := p.newPackage(buildPkg)
    61  	p.Packages[buildPkg.ImportPath] = pkg
    62  	return pkg, nil
    63  }
    64  
    65  // ImportFile loads and parses the import statements in the given path and
    66  // creates a pseudo-package out of it.
    67  func (p *Program) ImportFile(path string) (*Package, error) {
    68  	if p.Packages == nil {
    69  		p.Packages = make(map[string]*Package)
    70  	}
    71  	if _, ok := p.Packages[path]; ok {
    72  		// unlikely
    73  		return nil, errors.New("loader: cannot import file that is already imported as package: " + path)
    74  	}
    75  
    76  	file, err := p.parseFile(path, parser.ImportsOnly)
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  	buildPkg := &build.Package{
    81  		Dir:        filepath.Dir(path),
    82  		ImportPath: path,
    83  		GoFiles:    []string{filepath.Base(path)},
    84  	}
    85  	for _, importSpec := range file.Imports {
    86  		buildPkg.Imports = append(buildPkg.Imports, importSpec.Path.Value[1:len(importSpec.Path.Value)-1])
    87  	}
    88  	p.sorted = nil // invalidate the sorted order of packages
    89  	pkg := p.newPackage(buildPkg)
    90  	p.Packages[buildPkg.ImportPath] = pkg
    91  	return pkg, nil
    92  }
    93  
    94  // newPackage instantiates a new *Package object with initialized members.
    95  func (p *Program) newPackage(pkg *build.Package) *Package {
    96  	return &Package{
    97  		Program: p,
    98  		Package: pkg,
    99  		Imports: make(map[string]*Package, len(pkg.Imports)),
   100  		Info: types.Info{
   101  			Types:      make(map[ast.Expr]types.TypeAndValue),
   102  			Defs:       make(map[*ast.Ident]types.Object),
   103  			Uses:       make(map[*ast.Ident]types.Object),
   104  			Implicits:  make(map[ast.Node]types.Object),
   105  			Scopes:     make(map[ast.Node]*types.Scope),
   106  			Selections: make(map[*ast.SelectorExpr]*types.Selection),
   107  		},
   108  	}
   109  }
   110  
   111  // Sorted returns a list of all packages, sorted in a way that no packages come
   112  // before the packages they depend upon.
   113  func (p *Program) Sorted() []*Package {
   114  	if p.sorted == nil {
   115  		p.sort()
   116  	}
   117  	return p.sorted
   118  }
   119  
   120  func (p *Program) sort() {
   121  	p.sorted = nil
   122  	packageList := make([]*Package, 0, len(p.Packages))
   123  	packageSet := make(map[string]struct{}, len(p.Packages))
   124  	worklist := make([]string, 0, len(p.Packages))
   125  	for path := range p.Packages {
   126  		worklist = append(worklist, path)
   127  	}
   128  	sort.Strings(worklist)
   129  	for len(worklist) != 0 {
   130  		pkgPath := worklist[0]
   131  		pkg := p.Packages[pkgPath]
   132  
   133  		if _, ok := packageSet[pkgPath]; ok {
   134  			// Package already in the final package list.
   135  			worklist = worklist[1:]
   136  			continue
   137  		}
   138  
   139  		unsatisfiedImports := make([]string, 0)
   140  		for _, pkg := range pkg.Imports {
   141  			if _, ok := packageSet[pkg.ImportPath]; ok {
   142  				continue
   143  			}
   144  			unsatisfiedImports = append(unsatisfiedImports, pkg.ImportPath)
   145  		}
   146  		sort.Strings(unsatisfiedImports)
   147  		if len(unsatisfiedImports) == 0 {
   148  			// All dependencies of this package are satisfied, so add this
   149  			// package to the list.
   150  			packageList = append(packageList, pkg)
   151  			packageSet[pkgPath] = struct{}{}
   152  			worklist = worklist[1:]
   153  		} else {
   154  			// Prepend all dependencies to the worklist and reconsider this
   155  			// package (by not removing it from the worklist). At that point, it
   156  			// must be possible to add it to packageList.
   157  			worklist = append(unsatisfiedImports, worklist...)
   158  		}
   159  	}
   160  
   161  	p.sorted = packageList
   162  }
   163  
   164  // Parse recursively imports all packages, parses them, and typechecks them.
   165  //
   166  // The returned error may be an Errors error, which contains a list of errors.
   167  //
   168  // Idempotent.
   169  func (p *Program) Parse() error {
   170  	// Load all imports
   171  	for _, pkg := range p.Sorted() {
   172  		err := pkg.importRecursively()
   173  		if err != nil {
   174  			if err, ok := err.(*ImportCycleError); ok {
   175  				if pkg.ImportPath != err.Packages[0] {
   176  					err.Packages = append([]string{pkg.ImportPath}, err.Packages...)
   177  				}
   178  			}
   179  			return err
   180  		}
   181  	}
   182  
   183  	// Parse all packages.
   184  	for _, pkg := range p.Sorted() {
   185  		err := pkg.Parse()
   186  		if err != nil {
   187  			return err
   188  		}
   189  	}
   190  
   191  	// Typecheck all packages.
   192  	for _, pkg := range p.Sorted() {
   193  		err := pkg.Check()
   194  		if err != nil {
   195  			return err
   196  		}
   197  	}
   198  
   199  	return nil
   200  }
   201  
   202  // parseFile is a wrapper around parser.ParseFile.
   203  func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) {
   204  	if p.fset == nil {
   205  		p.fset = token.NewFileSet()
   206  	}
   207  
   208  	rd, err := os.Open(path)
   209  	if err != nil {
   210  		return nil, err
   211  	}
   212  	defer rd.Close()
   213  	relpath := path
   214  	if filepath.IsAbs(path) {
   215  		relpath, err = filepath.Rel(p.Dir, path)
   216  		if err != nil {
   217  			return nil, err
   218  		}
   219  	}
   220  	return parser.ParseFile(p.fset, relpath, rd, mode)
   221  }
   222  
   223  // Parse parses and typechecks this package.
   224  //
   225  // Idempotent.
   226  func (p *Package) Parse() error {
   227  	if len(p.Files) != 0 {
   228  		return nil
   229  	}
   230  
   231  	// Load the AST.
   232  	// TODO: do this in parallel.
   233  	if p.ImportPath == "unsafe" {
   234  		// Special case for the unsafe package. Don't even bother loading
   235  		// the files.
   236  		p.Pkg = types.Unsafe
   237  		return nil
   238  	}
   239  
   240  	files, err := p.parseFiles()
   241  	if err != nil {
   242  		return err
   243  	}
   244  	p.Files = files
   245  
   246  	return nil
   247  }
   248  
   249  // Check runs the package through the typechecker. The package must already be
   250  // loaded and all dependencies must have been checked already.
   251  //
   252  // Idempotent.
   253  func (p *Package) Check() error {
   254  	if p.Pkg != nil {
   255  		return nil
   256  	}
   257  
   258  	var typeErrors []error
   259  	checker := p.TypeChecker
   260  	checker.Error = func(err error) {
   261  		typeErrors = append(typeErrors, err)
   262  	}
   263  
   264  	// Do typechecking of the package.
   265  	checker.Importer = p
   266  
   267  	typesPkg, err := checker.Check(p.ImportPath, p.fset, p.Files, &p.Info)
   268  	if err != nil {
   269  		if err, ok := err.(Errors); ok {
   270  			return err
   271  		}
   272  		return Errors{p, typeErrors}
   273  	}
   274  	p.Pkg = typesPkg
   275  	return nil
   276  }
   277  
   278  // parseFiles parses the loaded list of files and returns this list.
   279  func (p *Package) parseFiles() ([]*ast.File, error) {
   280  	// TODO: do this concurrently.
   281  	var files []*ast.File
   282  	var fileErrs []error
   283  	for _, file := range p.GoFiles {
   284  		f, err := p.parseFile(filepath.Join(p.Package.Dir, file), parser.ParseComments)
   285  		if err != nil {
   286  			fileErrs = append(fileErrs, err)
   287  			continue
   288  		}
   289  		if err != nil {
   290  			fileErrs = append(fileErrs, err)
   291  			continue
   292  		}
   293  		files = append(files, f)
   294  	}
   295  	for _, file := range p.CgoFiles {
   296  		path := filepath.Join(p.Package.Dir, file)
   297  		f, err := p.parseFile(path, parser.ParseComments)
   298  		if err != nil {
   299  			fileErrs = append(fileErrs, err)
   300  			continue
   301  		}
   302  		errs := p.processCgo(path, f, append(p.CFlags, "-I"+p.Package.Dir))
   303  		if errs != nil {
   304  			fileErrs = append(fileErrs, errs...)
   305  			continue
   306  		}
   307  		files = append(files, f)
   308  	}
   309  	if len(fileErrs) != 0 {
   310  		return nil, Errors{p, fileErrs}
   311  	}
   312  	return files, nil
   313  }
   314  
   315  // Import implements types.Importer. It loads and parses packages it encounters
   316  // along the way, if needed.
   317  func (p *Package) Import(to string) (*types.Package, error) {
   318  	if to == "unsafe" {
   319  		return types.Unsafe, nil
   320  	}
   321  	if _, ok := p.Imports[to]; ok {
   322  		return p.Imports[to].Pkg, nil
   323  	} else {
   324  		return nil, errors.New("package not imported: " + to)
   325  	}
   326  }
   327  
   328  // importRecursively calls Program.Import() on all imported packages, and calls
   329  // importRecursively() on the imported packages as well.
   330  //
   331  // Idempotent.
   332  func (p *Package) importRecursively() error {
   333  	p.Importing = true
   334  	for _, to := range p.Package.Imports {
   335  		if to == "C" {
   336  			// Do Cgo processing in a later stage.
   337  			continue
   338  		}
   339  		if _, ok := p.Imports[to]; ok {
   340  			continue
   341  		}
   342  		importedPkg, err := p.Program.Import(to, p.Package.Dir)
   343  		if err != nil {
   344  			if err, ok := err.(*ImportCycleError); ok {
   345  				err.Packages = append([]string{p.ImportPath}, err.Packages...)
   346  			}
   347  			return err
   348  		}
   349  		if importedPkg.Importing {
   350  			return &ImportCycleError{[]string{p.ImportPath, importedPkg.ImportPath}, p.ImportPos[to]}
   351  		}
   352  		err = importedPkg.importRecursively()
   353  		if err != nil {
   354  			if err, ok := err.(*ImportCycleError); ok {
   355  				err.Packages = append([]string{p.ImportPath}, err.Packages...)
   356  			}
   357  			return err
   358  		}
   359  		p.Imports[to] = importedPkg
   360  	}
   361  	p.Importing = false
   362  	return nil
   363  }