diff options
Diffstat (limited to 'src/pkg/go/ast/resolve.go')
-rw-r--r-- | src/pkg/go/ast/resolve.go | 188 |
1 files changed, 188 insertions, 0 deletions
diff --git a/src/pkg/go/ast/resolve.go b/src/pkg/go/ast/resolve.go new file mode 100644 index 000000000..fddc3baab --- /dev/null +++ b/src/pkg/go/ast/resolve.go @@ -0,0 +1,188 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements NewPackage. + +package ast + +import ( + "fmt" + "go/scanner" + "go/token" + "os" +) + + +type pkgBuilder struct { + scanner.ErrorVector + fset *token.FileSet +} + + +func (p *pkgBuilder) error(pos token.Pos, msg string) { + p.Error(p.fset.Position(pos), msg) +} + + +func (p *pkgBuilder) errorf(pos token.Pos, format string, args ...interface{}) { + p.error(pos, fmt.Sprintf(format, args...)) +} + + +func (p *pkgBuilder) declare(scope, altScope *Scope, obj *Object) { + alt := scope.Insert(obj) + if alt == nil && altScope != nil { + // see if there is a conflicting declaration in altScope + alt = altScope.Lookup(obj.Name) + } + if alt != nil { + prevDecl := "" + if pos := alt.Pos(); pos.IsValid() { + prevDecl = fmt.Sprintf("\n\tprevious declaration at %s", p.fset.Position(pos)) + } + p.error(obj.Pos(), fmt.Sprintf("%s redeclared in this block%s", obj.Name, prevDecl)) + } +} + + +func resolve(scope *Scope, ident *Ident) bool { + for ; scope != nil; scope = scope.Outer { + if obj := scope.Lookup(ident.Name); obj != nil { + ident.Obj = obj + return true + } + } + return false +} + + +// NewPackage uses an Importer to resolve imports. Given an importPath, +// an importer returns the imported package's name, its scope of exported +// objects, and an error, if any. +// +type Importer func(path string) (name string, scope *Scope, err os.Error) + + +// NewPackage creates a new Package node from a set of File nodes. It resolves +// unresolved identifiers across files and updates each file's Unresolved list +// accordingly. If a non-nil importer and universe scope are provided, they are +// used to resolve identifiers not declared in any of the package files. Any +// remaining unresolved identifiers are reported as undeclared. If the files +// belong to different packages, one package name is selected and files with +// different package name are reported and then ignored. +// The result is a package node and a scanner.ErrorList if there were errors. +// +func NewPackage(fset *token.FileSet, files map[string]*File, importer Importer, universe *Scope) (*Package, os.Error) { + var p pkgBuilder + p.fset = fset + + // complete package scope + pkgName := "" + pkgScope := NewScope(universe) + for _, file := range files { + // package names must match + switch name := file.Name.Name; { + case pkgName == "": + pkgName = name + case name != pkgName: + p.errorf(file.Package, "package %s; expected %s", name, pkgName) + continue // ignore this file + } + + // collect top-level file objects in package scope + for _, obj := range file.Scope.Objects { + p.declare(pkgScope, nil, obj) + } + } + + // imports maps import paths to package names and scopes + // TODO(gri): Eventually we like to get to the import scope from + // a package object. Then we can have a map path -> Obj. + type importedPkg struct { + name string + scope *Scope + } + imports := make(map[string]*importedPkg) + + // complete file scopes with imports and resolve identifiers + for _, file := range files { + // ignore file if it belongs to a different package + // (error has already been reported) + if file.Name.Name != pkgName { + continue + } + + // build file scope by processing all imports + importErrors := false + fileScope := NewScope(pkgScope) + for _, spec := range file.Imports { + // add import to global map of imports + path := string(spec.Path.Value) + path = path[1 : len(path)-1] // strip ""'s + pkg := imports[path] + if pkg == nil { + if importer == nil { + importErrors = true + continue + } + name, scope, err := importer(path) + if err != nil { + p.errorf(spec.Path.Pos(), "could not import %s (%s)", path, err) + importErrors = true + continue + } + pkg = &importedPkg{name, scope} + imports[path] = pkg + // TODO(gri) If a local package name != "." is provided, + // global identifier resolution could proceed even if the + // import failed. Consider adjusting the logic here a bit. + } + // local name overrides imported package name + name := pkg.name + if spec.Name != nil { + name = spec.Name.Name + } + // add import to file scope + if name == "." { + // merge imported scope with file scope + for _, obj := range pkg.scope.Objects { + p.declare(fileScope, pkgScope, obj) + } + } else { + // declare imported package object in file scope + obj := NewObj(Pkg, name) + obj.Decl = spec + p.declare(fileScope, pkgScope, obj) + } + } + + // resolve identifiers + if importErrors { + // don't use the universe scope without correct imports + // (objects in the universe may be shadowed by imports; + // with missing imports identifiers might get resolved + // wrongly) + pkgScope.Outer = nil + } + i := 0 + for _, ident := range file.Unresolved { + if !resolve(fileScope, ident) { + p.errorf(ident.Pos(), "undeclared name: %s", ident.Name) + file.Unresolved[i] = ident + i++ + } + + } + file.Unresolved = file.Unresolved[0:i] + pkgScope.Outer = universe // reset universe scope + } + + // collect all import paths and respective package scopes + importedScopes := make(map[string]*Scope) + for path, pkg := range imports { + importedScopes[path] = pkg.scope + } + + return &Package{pkgName, pkgScope, importedScopes, files}, p.GetError(scanner.Sorted) +} |