go/go/types/resolve.go
Robert Griesemer 3df6f127f0 go.tools/go/types: resolve objects in type checker
By setting resolve = true in check.go, the type checker
will do all identifier resolution during type checking
time and ignore (and not depend on) parser objects. This
permits the type checker to run easily on ASTs that are
not generated with invariants guaranteed by the parser.

There is a lot of new code; much of it slightly modified
copies of old code. There is also a lot of duplication.
After removing the dead code resulting from resolve = true
permanently (and removing the flag as well), it will be
easier to perform a thorough cleanup. As is, there are
too many intertwined code paths.

For now resolve = false. To be enabled in a successor CL.

R=adonovan
CC=golang-dev
https://golang.org/cl/9606045
2013-05-28 10:06:37 -07:00

183 lines
5.3 KiB
Go

// Copyright 2013 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.
package types
import (
"fmt"
"go/ast"
"go/token"
"strconv"
)
func (check *checker) declareObj(scope, altScope *Scope, obj Object, dotImport token.Pos) {
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 := ""
// for dot-imports, local declarations are declared first - swap messages
if dotImport.IsValid() {
if pos := alt.Pos(); pos.IsValid() {
check.errorf(pos, fmt.Sprintf("%s redeclared in this block by import at %s",
obj.Name(), check.fset.Position(dotImport)))
return
}
// get by w/o other position
check.errorf(dotImport, fmt.Sprintf("dot-import redeclares %s", obj.Name()))
return
}
if pos := alt.Pos(); pos.IsValid() {
prevDecl = fmt.Sprintf("\n\tother declaration at %s", check.fset.Position(pos))
}
check.errorf(obj.Pos(), fmt.Sprintf("%s redeclared in this block%s", obj.Name(), prevDecl))
}
}
func (check *checker) resolveIdent(scope *Scope, ident *ast.Ident) bool {
for ; scope != nil; scope = scope.Outer {
if obj := scope.Lookup(ident.Name); obj != nil {
check.register(ident, obj)
return true
}
}
return false
}
func (check *checker) resolve(importer Importer, files []*ast.File) (methods []*ast.FuncDecl) {
pkg := check.pkg
// complete package scope
for _, file := range files {
// the package identifier denotes the current package
check.register(file.Name, pkg)
// insert top-level file objects in package scope
// (the parser took care of declaration errors in a single file,
// but not across multiple files - hence we need to check again)
for _, decl := range file.Decls {
switch d := decl.(type) {
case *ast.BadDecl:
// ignore
case *ast.GenDecl:
if d.Tok == token.CONST {
check.assocInitvals(d)
}
for _, spec := range d.Specs {
switch s := spec.(type) {
case *ast.ImportSpec:
// handled separately below
case *ast.ValueSpec:
for _, name := range s.Names {
if name.Name == "_" {
continue
}
check.declareObj(pkg.scope, nil, check.lookup(name), token.NoPos)
}
case *ast.TypeSpec:
if s.Name.Name == "_" {
continue
}
check.declareObj(pkg.scope, nil, check.lookup(s.Name), token.NoPos)
default:
check.invalidAST(s.Pos(), "unknown ast.Spec node %T", s)
}
}
case *ast.FuncDecl:
if d.Recv != nil {
// collect method
methods = append(methods, d)
continue
}
if d.Name.Name == "_" || d.Name.Name == "init" {
continue // blank (_) and init functions are inaccessible
}
check.declareObj(pkg.scope, nil, check.lookup(d.Name), token.NoPos)
default:
check.invalidAST(d.Pos(), "unknown ast.Decl node %T", d)
}
}
}
// complete file scopes with imports and resolve identifiers
for _, file := range files {
// build file scope by processing all imports
importErrors := false
fileScope := &Scope{Outer: pkg.scope}
for _, spec := range file.Imports {
if importer == nil {
importErrors = true
continue
}
path, _ := strconv.Unquote(spec.Path.Value)
imp, err := importer(pkg.imports, path)
if err != nil {
check.errorf(spec.Path.Pos(), "could not import %s (%s)", path, err)
importErrors = true
continue
}
// 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 := imp.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 imp.scope.Entries {
// gcimported package scopes contain non-exported
// objects such as types used in partially exported
// objects - do not accept them
if ast.IsExported(obj.Name()) {
check.declareObj(fileScope, pkg.scope, obj, spec.Pos())
}
}
// TODO(gri) consider registering the "." identifier
// if we have Context.Ident callbacks for say blank
// (_) identifiers
// check.register(spec.Name, pkg)
} else if name != "_" {
// declare imported package object in file scope
// (do not re-use imp in the file scope but create
// a new object instead; the Decl field is different
// for different files)
obj := &Package{name: name, scope: imp.scope, spec: spec}
check.declareObj(fileScope, pkg.scope, obj, token.NoPos)
}
}
// 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
// incorrectly to universe objects)
pkg.scope.Outer = nil
}
i := 0
for _, ident := range file.Unresolved {
if !check.resolveIdent(fileScope, ident) {
check.errorf(ident.Pos(), "undeclared name: %s", ident.Name)
file.Unresolved[i] = ident
i++
}
}
file.Unresolved = file.Unresolved[0:i]
pkg.scope.Outer = Universe // reset outer scope (is nil if there were importErrors)
}
return
}