mirror of
https://github.com/golang/go
synced 2024-11-19 00:24:41 -07:00
185 lines
5.5 KiB
Go
185 lines
5.5 KiB
Go
|
// Package importer defines the Importer, which loads, parses and
|
||
|
// type-checks packages of Go code plus their transitive closure, and
|
||
|
// retains both the ASTs and the derived facts.
|
||
|
//
|
||
|
// TODO(adonovan): document and test this package better, with examples.
|
||
|
// Currently it's covered by the ssa/ tests.
|
||
|
//
|
||
|
package importer
|
||
|
|
||
|
import (
|
||
|
"go/ast"
|
||
|
"go/token"
|
||
|
|
||
|
"code.google.com/p/go.tools/go/exact"
|
||
|
"code.google.com/p/go.tools/go/types"
|
||
|
)
|
||
|
|
||
|
// An Importer's methods are not thread-safe.
|
||
|
type Importer struct {
|
||
|
context *Context // the client context
|
||
|
Fset *token.FileSet // position info for all files seen
|
||
|
Packages map[string]*PackageInfo // keys are import paths
|
||
|
errors map[string]error // cache of errors by import path
|
||
|
}
|
||
|
|
||
|
// Context specifies the supporting context for the importer.
|
||
|
//
|
||
|
type Context struct {
|
||
|
// TypeChecker contains options relating to the type checker.
|
||
|
// The Importer will override any user-supplied values for its
|
||
|
// Expr, Ident, ImplicitObj and Import fields; other fields
|
||
|
// will be passed through to the type checker.
|
||
|
TypeChecker types.Context
|
||
|
|
||
|
// If Loader is non-nil, it is used to satisfy imports.
|
||
|
//
|
||
|
// If it is nil, binary object files produced by the gc
|
||
|
// compiler will be loaded instead of source code for all
|
||
|
// imported packages. Such files supply only the types of
|
||
|
// package-level declarations and values of constants, but no
|
||
|
// code, so this mode will not yield a whole program. It is
|
||
|
// intended for analyses that perform intraprocedural analysis
|
||
|
// of a single package.
|
||
|
Loader SourceLoader
|
||
|
}
|
||
|
|
||
|
// SourceLoader is the signature of a function that locates, reads and
|
||
|
// parses a set of source files given an import path.
|
||
|
//
|
||
|
// fset is the fileset to which the ASTs should be added.
|
||
|
// path is the imported path, e.g. "sync/atomic".
|
||
|
//
|
||
|
// On success, the function returns files, the set of ASTs produced,
|
||
|
// or the first error encountered.
|
||
|
//
|
||
|
// The MakeGoBuildLoader utility can be used to construct a
|
||
|
// SourceLoader based on go/build.
|
||
|
//
|
||
|
type SourceLoader func(fset *token.FileSet, path string) (files []*ast.File, err error)
|
||
|
|
||
|
// New returns a new, empty Importer using configuration options
|
||
|
// specified by context.
|
||
|
//
|
||
|
func New(context *Context) *Importer {
|
||
|
imp := &Importer{
|
||
|
context: context,
|
||
|
Fset: token.NewFileSet(),
|
||
|
Packages: make(map[string]*PackageInfo),
|
||
|
errors: make(map[string]error),
|
||
|
}
|
||
|
imp.context.TypeChecker.Import = imp.doImport
|
||
|
return imp
|
||
|
}
|
||
|
|
||
|
// doImport loads the typechecker package identified by path
|
||
|
// Implements the types.Importer prototype.
|
||
|
//
|
||
|
func (imp *Importer) doImport(imports map[string]*types.Package, path string) (pkg *types.Package, err error) {
|
||
|
// Package unsafe is handled specially, and has no PackageInfo.
|
||
|
if path == "unsafe" {
|
||
|
return types.Unsafe, nil
|
||
|
}
|
||
|
|
||
|
if info, ok := imp.Packages[path]; ok {
|
||
|
imports[path] = info.Pkg
|
||
|
pkg = info.Pkg
|
||
|
return // positive cache hit
|
||
|
}
|
||
|
|
||
|
if err = imp.errors[path]; err != nil {
|
||
|
return // negative cache hit
|
||
|
}
|
||
|
|
||
|
// Load the source/binary for 'path', type-check it, construct
|
||
|
// a PackageInfo and update our map (imp.Packages) and the
|
||
|
// type-checker's map (imports).
|
||
|
var info *PackageInfo
|
||
|
if imp.context.Loader != nil {
|
||
|
info, err = imp.LoadPackage(path)
|
||
|
} else if pkg, err = types.GcImport(imports, path); err == nil {
|
||
|
info = &PackageInfo{Pkg: pkg}
|
||
|
imp.Packages[path] = info
|
||
|
}
|
||
|
|
||
|
if err == nil {
|
||
|
// Cache success.
|
||
|
pkg = info.Pkg
|
||
|
imports[path] = pkg
|
||
|
return pkg, nil
|
||
|
}
|
||
|
|
||
|
// Cache failure
|
||
|
imp.errors[path] = err
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
// LoadPackage loads the package of the specified import-path,
|
||
|
// performs type-checking, and returns the corresponding
|
||
|
// PackageInfo.
|
||
|
//
|
||
|
// Not idempotent!
|
||
|
// Precondition: Importer.context.Loader != nil.
|
||
|
// Not thread-safe!
|
||
|
// TODO(adonovan): rethink this API.
|
||
|
//
|
||
|
func (imp *Importer) LoadPackage(importPath string) (*PackageInfo, error) {
|
||
|
if imp.context.Loader == nil {
|
||
|
panic("Importer.LoadPackage without a SourceLoader")
|
||
|
}
|
||
|
files, err := imp.context.Loader(imp.Fset, importPath)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return imp.CreateSourcePackage(importPath, files)
|
||
|
}
|
||
|
|
||
|
// CreateSourcePackage invokes the type-checker on files and returns a
|
||
|
// PackageInfo containing the resulting type-checker package, the
|
||
|
// ASTs, and other type information.
|
||
|
//
|
||
|
// The order of files determines the package initialization order.
|
||
|
//
|
||
|
// importPath is the full name under which this package is known, such
|
||
|
// as appears in an import declaration. e.g. "sync/atomic".
|
||
|
//
|
||
|
// The ParseFiles utility may be helpful for parsing a set of Go
|
||
|
// source files.
|
||
|
//
|
||
|
func (imp *Importer) CreateSourcePackage(importPath string, files []*ast.File) (*PackageInfo, error) {
|
||
|
info := &PackageInfo{
|
||
|
Files: files,
|
||
|
types: make(map[ast.Expr]types.Type),
|
||
|
idents: make(map[*ast.Ident]types.Object),
|
||
|
constants: make(map[ast.Expr]exact.Value),
|
||
|
typecases: make(map[*ast.CaseClause]*types.Var),
|
||
|
}
|
||
|
tc := imp.context.TypeChecker
|
||
|
tc.Expr = func(x ast.Expr, typ types.Type, val exact.Value) {
|
||
|
info.types[x] = typ
|
||
|
if val != nil {
|
||
|
info.constants[x] = val
|
||
|
}
|
||
|
}
|
||
|
tc.Ident = func(ident *ast.Ident, obj types.Object) {
|
||
|
// Invariants:
|
||
|
// - obj is non-nil.
|
||
|
// - isBlankIdent(ident) <=> obj.GetType()==nil
|
||
|
info.idents[ident] = obj
|
||
|
}
|
||
|
tc.ImplicitObj = func(node ast.Node, obj types.Object) {
|
||
|
if cc, ok := node.(*ast.CaseClause); ok {
|
||
|
info.typecases[cc] = obj.(*types.Var)
|
||
|
}
|
||
|
}
|
||
|
var firstErr error
|
||
|
info.Pkg, firstErr = tc.Check(importPath, imp.Fset, files...)
|
||
|
tc.Expr = nil
|
||
|
tc.Ident = nil
|
||
|
if firstErr != nil {
|
||
|
return nil, firstErr
|
||
|
}
|
||
|
imp.Packages[importPath] = info
|
||
|
return info, nil
|
||
|
}
|