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 }