1
0
mirror of https://github.com/golang/go synced 2024-11-18 18:54:42 -07:00

go.tools/go/types: request type Info via maps instead of callbacks

Allmost all uses of go/types that wanted the type
information computed, installed callback functions
that stored the information in maps. Most of the
time this is the only thing that could be done because
there is no guarantee that types are completely set
up before the end of type-checking.

This CL removes the respective Context callbacks in favor
of corresponding maps that collect the desired information
on demand, grouped together in an optional Info struct.

R=adonovan
CC=golang-dev
https://golang.org/cl/11530044
This commit is contained in:
Robert Griesemer 2013-07-18 13:09:03 -07:00
parent 78c8226b42
commit 6d85cc17dd
16 changed files with 207 additions and 239 deletions

View File

@ -19,24 +19,21 @@ func (pkg *Package) check(fs *token.FileSet, astFiles []*ast.File) error {
pkg.spans = make(map[types.Object]Span) pkg.spans = make(map[types.Object]Span)
pkg.types = make(map[ast.Expr]types.Type) pkg.types = make(map[ast.Expr]types.Type)
pkg.values = make(map[ast.Expr]exact.Value) pkg.values = make(map[ast.Expr]exact.Value)
exprFn := func(x ast.Expr, typ types.Type, val exact.Value) {
pkg.types[x] = typ
if val != nil {
pkg.values[x] = val
}
}
identFn := func(id *ast.Ident, obj types.Object) {
pkg.idents[id] = obj
pkg.growSpan(id, obj)
}
// By providing the Context with our own error function, it will continue // By providing the Context with our own error function, it will continue
// past the first error. There is no need for that function to do anything. // past the first error. There is no need for that function to do anything.
context := types.Context{ context := types.Context{
Ident: identFn,
Expr: exprFn,
Error: func(error) {}, Error: func(error) {},
} }
_, err := context.Check(pkg.path, fs, astFiles...) info := &types.Info{
Types: pkg.types,
Values: pkg.values,
Objects: pkg.idents,
}
_, err := context.Check(pkg.path, fs, astFiles, info)
// update spans
for id, obj := range pkg.idents {
pkg.growSpan(id, obj)
}
return err return err
} }

View File

@ -2,50 +2,26 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// Package types declares the data types and implements the algorithms for // Package types declares the data types and implements
// name resolution, source-level constant folding, and type checking of Go // the algorithms for type-checking of Go packages.
// packages. // Use Check and Context.Check to invoke the type-checker.
//
// Type-checking consists of several interdependent phases:
// //
// Name resolution maps each identifier (ast.Ident) in the program to the // Name resolution maps each identifier (ast.Ident) in the program to the
// language object (Object) it denotes. // language object (Object) it denotes.
// Use Info.Objects, Info.Implicits for the results of name resolution.
// //
// Constant folding computes the exact constant value (exact.Value) for // Constant folding computes the exact constant value (exact.Value) for
// every expression (ast.Expr) that is a compile-time constant. // every expression (ast.Expr) that is a compile-time constant.
// Use Info.Values for the results of constant folding.
// //
// Type checking computes the type (Type) of every expression (ast.Expr) // Type inference computes the type (Type) of every expression (ast.Expr)
// and checks for compliance with the language specification. // and checks for compliance with the language specification.
// // Use Info.Types for the results of type evaluation.
// The results of the various computations are delivered to a client via
// Context callback functions:
//
// Context callback: Information delivered to client:
//
// Ident, ImplicitObj results of name resolution
// Expr results of constant folding and type checking
// Error errors
//
// WARNING: THE TYPES API IS SUBJECT TO CHANGE.
// //
package types package types
// Most correct programs should now be accepted by the types package,
// but there are several known bugs which permit incorrect programs to
// pass without errors. Please do not file issues against these for now
// since they are known already:
//
// BUG(gri): Conversions of constants only change the type, not the value (e.g., int(1.1) is wrong).
// BUG(gri): Some built-ins don't check parameters fully, yet (e.g. append).
// BUG(gri): Use of labels is only partially checked.
// BUG(gri): Unused variables and imports are not reported.
// BUG(gri): Interface vs non-interface comparisons are not correctly implemented.
// BUG(gri): Switch statements don't check correct use of 'fallthrough'.
// BUG(gri): Switch statements don't check duplicate cases for all types for which it is required.
// BUG(gri): Some built-ins may not be callable if in statement-context.
// The API is still slightly in flux and the following changes are considered:
//
// API(gri): The GcImporter should probably be in its own package - it is only one of possible importers.
import ( import (
"go/ast" "go/ast"
"go/token" "go/token"
@ -53,6 +29,21 @@ import (
"code.google.com/p/go.tools/go/exact" "code.google.com/p/go.tools/go/exact"
) )
// Check type-checks a package and returns the resulting package object,
// or a nil package and the first error. The package is specified by a
// list of *ast.Files and corresponding file set, and the import path
// the package is identified with. The path must not be empty or dot (".").
//
// For more control over type-checking and results, use Context.Check.
func Check(path string, fset *token.FileSet, files []*ast.File) (*Package, error) {
var ctxt Context
pkg, err := ctxt.check(path, fset, files, nil)
if err != nil {
return nil, err
}
return pkg, nil
}
// A Context specifies the supporting context for type checking. // A Context specifies the supporting context for type checking.
// The zero value for a Context is a ready-to-use default context. // The zero value for a Context is a ready-to-use default context.
type Context struct { type Context struct {
@ -62,46 +53,18 @@ type Context struct {
// filename:line:column: message // filename:line:column: message
Error func(err error) Error func(err error)
// If Ident != nil, it is called for each identifier id that is type-
// checked (including package names, dots "." of dot-imports, and blank
// "_" identifiers), and obj is the object denoted by ident. The object
// is nil if the identifier was not declared. Ident may be called
// multiple times for the same identifier (e.g., for typed variable
// declarations with multiple initialization statements); but Ident
// will report the same obj for a given id in this case. The object
// may not be fully set up at the time of the callback (e.g., its type
// may be nil and only filled in later).
Ident func(id *ast.Ident, obj Object)
// TODO(gri) Can we make this stronger, so that Ident is called
// always exactly once (w/o resorting to a map, internally)?
// TODO(gri) At the moment, Ident is not called for label identifiers
// in break, continue, or goto statements.
// If ImplicitObj != nil, it is called exactly once for each node
// that declares an object obj implicitly. The following nodes may
// appear:
//
// node obj
// *ast.ImportSpec *Package (imports w/o renames), or imported objects (dot-imports)
// *ast.CaseClause type-specific variable introduced for each single-type type switch clause
// *ast.Field anonymous struct field or parameter, embedded interface
//
ImplicitObj func(node ast.Node, obj Object)
// If Expr != nil, it is called exactly once for each expression x
// that is type-checked: typ is the expression type, and val is the
// value if x is constant, val is nil otherwise. Expr is not called
// for identifiers appearing on the lhs of declarations.
//
// If x is a literal value (constant, composite literal), typ is always
// the dynamic type of x (never an interface type). Otherwise, typ is x's
// static type (possibly an interface type).
Expr func(x ast.Expr, typ Type, val exact.Value)
// TODO(gri): Should this hold for all constant expressions?
// If Import != nil, it is called for each imported package. // If Import != nil, it is called for each imported package.
// Otherwise, GcImporter is called. // Otherwise, GcImporter is called.
Import Importer // An importer resolves import paths to Package objects.
// The imports map records the packages already imported,
// indexed by package id (canonical import path).
// An importer must determine the canonical import path and
// check the map to see if it is already present in the imports map.
// If so, the Importer can return the map entry. Otherwise, the
// importer should load the package data for the given path into
// a new *Package, record pkg in the imports map, and then
// return pkg.
Import func(imports map[string]*Package, path string) (pkg *Package, err error)
// If Alignof != nil, it is called to determine the alignment // If Alignof != nil, it is called to determine the alignment
// of the given type. Otherwise DefaultAlignmentof is called. // of the given type. Otherwise DefaultAlignmentof is called.
@ -121,34 +84,42 @@ type Context struct {
Sizeof func(Type) int64 Sizeof func(Type) int64
} }
// An Importer resolves import paths to Package objects. // Info holds result type information for a package.
// The imports map records the packages already imported, type Info struct {
// indexed by package id (canonical import path). // If Types != nil, it records the type for each expression that is
// An Importer must determine the canonical import path and // type-checked. Expressions corresponding to identifiers on the lhs
// check the map to see if it is already present in the imports map. // of a declaration are recorded in Objects, not Types.
// If so, the Importer can return the map entry. Otherwise, the Types map[ast.Expr]Type
// Importer should load the package data for the given path into
// a new *Package, record pkg in the imports map, and then
// return pkg.
type Importer func(imports map[string]*Package, path string) (pkg *Package, err error)
// Check resolves and typechecks a set of package files within the given // If Values != nil, it records the value of each constant expression
// context. It returns the package and the first error encountered, if // that is type-checked.
// any. If the context's Error handler is nil, Check terminates as soon Values map[ast.Expr]exact.Value
// as the first error is encountered; otherwise it continues until the
// entire package is checked. If there are errors, the package may be // If Objects != nil, it records the object denoted by each identifier
// only partially type-checked, and the resulting package may be incomplete // that is type-checked (including package names, dots "." of dot-imports,
// (missing objects, imports, etc.). // and blank "_" identifiers). For identifiers that were not declared,
// The provided package path must not resolve to ".", otherwise Check // the corresponding object is nil.
// returns immediately with a corresponding error message. // BUG(gri) Label identifiers in break, continue, or goto statements
func (ctxt *Context) Check(path string, fset *token.FileSet, files ...*ast.File) (*Package, error) { // are not recorded.
return check(ctxt, path, fset, files...) Objects map[*ast.Ident]Object
// If Implicits != nil, it records the object for each node the implicitly
// declares objects. The following node and object types may appear:
//
// node obj
// *ast.ImportSpec *Package (imports w/o renames), or imported objects (dot-imports)
// *ast.CaseClause type-specific variable introduced for each single-type type switch clause
// *ast.Field anonymous struct field or parameter, embedded interface
//
Implicits map[ast.Node]Object
} }
// Check is shorthand for ctxt.Check where ctxt is a default context. // Check type-checks a package and returns the resulting package object, the first
func Check(path string, fset *token.FileSet, files ...*ast.File) (*Package, error) { // error if any, and if info != nil, additional type information. The package is
var ctxt Context // specified by a list of *ast.Files and corresponding file set, and the import
return ctxt.Check(path, fset, files...) // path the package is identified with. The path must not be empty or dot (".").
func (ctxt *Context) Check(path string, fset *token.FileSet, files []*ast.File, info *Info) (*Package, error) {
return ctxt.check(path, fset, files, info)
} }
// IsAssignableTo reports whether a value of type V // IsAssignableTo reports whether a value of type V
@ -157,3 +128,12 @@ func IsAssignableTo(V, T Type) bool {
x := operand{mode: value, typ: V} x := operand{mode: value, typ: V}
return x.isAssignableTo(nil, T) // context not needed for non-constant x return x.isAssignableTo(nil, T) // context not needed for non-constant x
} }
// BUG(gri): Conversions of constants only change the type, not the value (e.g., int(1.1) is wrong).
// BUG(gri): Some built-ins don't check parameters fully, yet (e.g. append).
// BUG(gri): Use of labels is only partially checked.
// BUG(gri): Unused variables and imports are not reported.
// BUG(gri): Interface vs non-interface comparisons are not correctly implemented.
// BUG(gri): Switch statements don't check correct use of 'fallthrough'.
// BUG(gri): Switch statements don't check duplicate cases for all types for which it is required.
// BUG(gri): Some built-ins may not be callable if in statement-context.

View File

@ -109,7 +109,7 @@ func (check *checker) assignVar(lhs ast.Expr, x *operand) {
// Don't evaluate lhs if it is the blank identifier. // Don't evaluate lhs if it is the blank identifier.
if ident, _ := lhs.(*ast.Ident); ident != nil && ident.Name == "_" { if ident, _ := lhs.(*ast.Ident); ident != nil && ident.Name == "_" {
check.callIdent(ident, nil) check.recordObject(ident, nil)
check.updateExprType(x.expr, x.typ, true) // rhs has its final type check.updateExprType(x.expr, x.typ, true) // rhs has its final type
return return
} }
@ -275,7 +275,7 @@ func (check *checker) shortVarDecl(lhs, rhs []ast.Expr) {
// declare new variable // declare new variable
obj = NewVar(ident.Pos(), check.pkg, ident.Name, nil) obj = NewVar(ident.Pos(), check.pkg, ident.Name, nil)
} }
check.callIdent(ident, obj) // obj may be nil check.recordObject(ident, obj) // obj may be nil
} else { } else {
check.errorf(lhs.Pos(), "cannot declare %s", lhs) check.errorf(lhs.Pos(), "cannot declare %s", lhs)
} }

View File

@ -170,7 +170,7 @@ func (check *checker) selector(x *operand, e *ast.SelectorExpr) {
// selector expressions. // selector expressions.
if ident, ok := e.X.(*ast.Ident); ok { if ident, ok := e.X.(*ast.Ident); ok {
if pkg, ok := check.topScope.LookupParent(ident.Name).(*Package); ok { if pkg, ok := check.topScope.LookupParent(ident.Name).(*Package); ok {
check.callIdent(ident, pkg) check.recordObject(ident, pkg)
exp := pkg.scope.Lookup(nil, sel) exp := pkg.scope.Lookup(nil, sel)
if exp == nil { if exp == nil {
check.errorf(e.Pos(), "%s not declared by package %s", sel, ident) check.errorf(e.Pos(), "%s not declared by package %s", sel, ident)
@ -182,7 +182,7 @@ func (check *checker) selector(x *operand, e *ast.SelectorExpr) {
check.errorf(e.Pos(), "%s not exported by package %s", sel, ident) check.errorf(e.Pos(), "%s not exported by package %s", sel, ident)
goto Error goto Error
} }
check.callIdent(e.Sel, exp) check.recordObject(e.Sel, exp)
// Simplified version of the code for *ast.Idents: // Simplified version of the code for *ast.Idents:
// - imported packages use types.Scope and types.Objects // - imported packages use types.Scope and types.Objects
// - imported objects are always fully initialized // - imported objects are always fully initialized
@ -225,7 +225,7 @@ func (check *checker) selector(x *operand, e *ast.SelectorExpr) {
goto Error goto Error
} }
check.callIdent(e.Sel, obj) check.recordObject(e.Sel, obj)
if x.mode == typexpr { if x.mode == typexpr {
// method expression // method expression

View File

@ -38,6 +38,7 @@ type exprInfo struct {
type checker struct { type checker struct {
ctxt *Context ctxt *Context
fset *token.FileSet fset *token.FileSet
Info
// lazily initialized // lazily initialized
pkg *Package // current package pkg *Package // current package
@ -69,17 +70,29 @@ func newChecker(ctxt *Context, fset *token.FileSet, pkg *Package) *checker {
} }
} }
func (check *checker) callIdent(id *ast.Ident, obj Object) { func (check *checker) recordTypeAndValue(x ast.Expr, typ Type, val exact.Value) {
if f := check.ctxt.Ident; f != nil { assert(x != nil && typ != nil)
assert(id != nil) if m := check.Types; m != nil {
f(id, obj) m[x] = typ
}
if val != nil {
if m := check.Values; m != nil {
m[x] = val
}
} }
} }
func (check *checker) callImplicitObj(node ast.Node, obj Object) { func (check *checker) recordObject(id *ast.Ident, obj Object) {
if f := check.ctxt.ImplicitObj; f != nil { assert(id != nil)
assert(node != nil && obj != nil) if m := check.Objects; m != nil {
f(node, obj) m[id] = obj
}
}
func (check *checker) recordImplicit(node ast.Node, obj Object) {
assert(node != nil && obj != nil)
if m := check.Implicits; m != nil {
m[node] = obj
} }
} }
@ -111,16 +124,16 @@ func (check *checker) handleBailout(err *error) {
*err = check.firsterr *err = check.firsterr
default: default:
// unexpected panic: don't crash clients // unexpected panic: don't crash clients
// TODO(gri) add a test case for this scenario
*err = fmt.Errorf("types internal error: %v", p)
if debug { if debug {
check.dump("INTERNAL PANIC: %v", p) check.dump("INTERNAL PANIC: %v", p)
panic(p) panic(p)
} }
// TODO(gri) add a test case for this scenario
*err = fmt.Errorf("types internal error: %v", p)
} }
} }
func check(ctxt *Context, pkgPath string, fset *token.FileSet, files ...*ast.File) (pkg *Package, err error) { func (ctxt *Context) check(pkgPath string, fset *token.FileSet, files []*ast.File, info *Info) (pkg *Package, err error) {
pkg = &Package{ pkg = &Package{
path: pkgPath, path: pkgPath,
scope: NewScope(Universe), scope: NewScope(Universe),
@ -132,10 +145,15 @@ func check(ctxt *Context, pkgPath string, fset *token.FileSet, files ...*ast.Fil
// we need a reasonable path to continue // we need a reasonable path to continue
if path.Clean(pkgPath) == "." { if path.Clean(pkgPath) == "." {
check.errorf(token.NoPos, "Check invoked with invalid package path: %q", pkgPath) check.errorf(token.NoPos, "invalid package path provided: %q", pkgPath)
return return
} }
// install optional info
if info != nil {
check.Info = *info
}
// determine package name and files // determine package name and files
i := 0 i := 0
for _, file := range files { for _, file := range files {
@ -151,16 +169,9 @@ func check(ctxt *Context, pkgPath string, fset *token.FileSet, files ...*ast.Fil
// ignore this file // ignore this file
} }
} }
files = files[:i]
// resolve identifiers
imp := ctxt.Import
if imp == nil {
imp = GcImport
}
// TODO(gri) resolveFiles needs to be split up and renamed (cleanup) // TODO(gri) resolveFiles needs to be split up and renamed (cleanup)
check.resolveFiles(files, imp) check.resolveFiles(files[:i])
// typecheck all function/method bodies // typecheck all function/method bodies
// (funclist may grow when checking statements - do not use range clause!) // (funclist may grow when checking statements - do not use range clause!)
@ -195,9 +206,9 @@ func check(ctxt *Context, pkgPath string, fset *token.FileSet, files ...*ast.Fil
// TODO(gri) Consider doing this before and // TODO(gri) Consider doing this before and
// after function body checking for smaller // after function body checking for smaller
// map size and more immediate feedback. // map size and more immediate feedback.
if ctxt.Expr != nil { if check.Types != nil || check.Values != nil {
for x, info := range check.untyped { for x, info := range check.untyped {
ctxt.Expr(x, info.typ, info.val) check.recordTypeAndValue(x, info.typ, info.val)
} }
} }

View File

@ -216,7 +216,7 @@ func checkFiles(t *testing.T, testfiles []string) {
errlist = append(errlist, err) errlist = append(errlist, err)
} }
} }
ctxt.Check(pkgName, fset, files...) ctxt.Check(pkgName, fset, files, nil)
if *listErrors { if *listErrors {
return return

View File

@ -7,6 +7,7 @@
package types package types
import ( import (
"go/ast"
"go/parser" "go/parser"
"go/token" "go/token"
"strings" "strings"
@ -98,7 +99,7 @@ func f(a int, s string) float64 {
t.Fatal(err) t.Fatal(err)
} }
pkg, err := Check("p", fset, file) pkg, err := Check("p", fset, []*ast.File{file})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -387,10 +387,8 @@ func (check *checker) updateExprType(x ast.Expr, typ Type, final bool) {
return return
} }
// Everything's fine, notify client of final type for x. // Everything's fine, record final type and value for x.
if f := check.ctxt.Expr; f != nil { check.recordTypeAndValue(x, typ, old.val)
f(x, typ, old.val)
}
} }
// convertUntyped attempts to set the type of an untyped value to the target type. // convertUntyped attempts to set the type of an untyped value to the target type.
@ -769,13 +767,13 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type) {
check.expr0(x, e, hint) check.expr0(x, e, hint)
// convert x into a user-friendly set of values // convert x into a user-friendly set of values
notify := check.ctxt.Expr record := true
var typ Type var typ Type
var val exact.Value var val exact.Value
switch x.mode { switch x.mode {
case invalid: case invalid:
typ = Typ[Invalid] typ = Typ[Invalid]
notify = nil // nothing to do record = false // nothing to do
case novalue: case novalue:
typ = (*Tuple)(nil) typ = (*Tuple)(nil)
case constant: case constant:
@ -784,7 +782,7 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type) {
case typexprn: case typexprn:
x.mode = typexpr x.mode = typexpr
typ = x.typ typ = x.typ
notify = nil // clients were already notified record = false // type was already recorded
default: default:
typ = x.typ typ = x.typ
} }
@ -794,12 +792,11 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type) {
// delay notification until it becomes typed // delay notification until it becomes typed
// or until the end of type checking // or until the end of type checking
check.untyped[x.expr] = exprInfo{false, typ.(*Basic), val} check.untyped[x.expr] = exprInfo{false, typ.(*Basic), val}
} else if notify != nil { } else if record {
// notify clients
// TODO(gri) ensure that literals always report // TODO(gri) ensure that literals always report
// their dynamic (never interface) type. // their dynamic (never interface) type.
// This is not the case yet. // This is not the case yet.
notify(x.expr, typ, val) check.recordTypeAndValue(e, typ, val)
} }
if trace { if trace {
@ -898,7 +895,7 @@ func (check *checker) expr0(x *operand, e ast.Expr, hint Type) {
continue continue
} }
fld := fields[i] fld := fields[i]
check.callIdent(key, fld) check.recordObject(key, fld)
// 0 <= i < len(fields) // 0 <= i < len(fields)
if visited[i] { if visited[i] {
check.errorf(kv.Pos(), "duplicate field name %s in struct literal", key.Name) check.errorf(kv.Pos(), "duplicate field name %s in struct literal", key.Name)

View File

@ -11,8 +11,6 @@ import (
"go/parser" "go/parser"
"strings" "strings"
"testing" "testing"
"code.google.com/p/go.tools/go/exact"
) )
func TestIssue5770(t *testing.T) { func TestIssue5770(t *testing.T) {
@ -23,7 +21,7 @@ func TestIssue5770(t *testing.T) {
return return
} }
_, err = Check(f.Name.Name, fset, f) // do not crash _, err = Check(f.Name.Name, fset, []*ast.File{f}) // do not crash
want := "undeclared name: T" want := "undeclared name: T"
if err == nil || !strings.Contains(err.Error(), want) { if err == nil || !strings.Contains(err.Error(), want) {
t.Errorf("got: %v; want: %s", err, want) t.Errorf("got: %v; want: %s", err, want)
@ -48,36 +46,36 @@ var (
return return
} }
ctxt := Context{ var ctxt Context
Expr: func(x ast.Expr, typ Type, val exact.Value) { types := make(map[ast.Expr]Type)
var want Type _, err = ctxt.Check(f.Name.Name, fset, []*ast.File{f}, &Info{Types: types})
switch x := x.(type) {
case *ast.BasicLit:
switch x.Value {
case `8`:
want = Typ[Uint8]
case `16`:
want = Typ[Uint16]
case `32`:
want = Typ[Uint32]
case `64`:
want = Typ[Uint] // because of "+ s", s is of type uint
case `"foo"`:
want = Typ[String]
}
case *ast.Ident:
if x.Name == "nil" {
want = Typ[UntypedNil]
}
}
if want != nil && !IsIdentical(typ, want) {
t.Errorf("got %s; want %s", typ, want)
}
},
}
_, err = ctxt.Check(f.Name.Name, fset, f)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
for x, typ := range types {
var want Type
switch x := x.(type) {
case *ast.BasicLit:
switch x.Value {
case `8`:
want = Typ[Uint8]
case `16`:
want = Typ[Uint16]
case `32`:
want = Typ[Uint32]
case `64`:
want = Typ[Uint] // because of "+ s", s is of type uint
case `"foo"`:
want = Typ[String]
}
case *ast.Ident:
if x.Name == "nil" {
want = Typ[UntypedNil]
}
}
if want != nil && !IsIdentical(typ, want) {
t.Errorf("got %s; want %s", typ, want)
}
}
} }

View File

@ -25,7 +25,7 @@ func (check *checker) declare(scope *Scope, id *ast.Ident, obj Object) {
obj = nil // for callIdent below obj = nil // for callIdent below
} }
if id != nil { if id != nil {
check.callIdent(id, obj) check.recordObject(id, obj)
} }
} }
@ -83,7 +83,7 @@ func (check *checker) arityMatch(s, init *ast.ValueSpec) {
} }
} }
func (check *checker) resolveFiles(files []*ast.File, importer Importer) { func (check *checker) resolveFiles(files []*ast.File) {
pkg := check.pkg pkg := check.pkg
// Phase 1: Pre-declare all package scope objects so that they can be found // Phase 1: Pre-declare all package scope objects so that they can be found
@ -103,13 +103,13 @@ func (check *checker) resolveFiles(files []*ast.File, importer Importer) {
if ident.Name == "init" { if ident.Name == "init" {
f, _ := obj.(*Func) f, _ := obj.(*Func)
if f == nil { if f == nil {
check.callIdent(ident, nil) check.recordObject(ident, nil)
check.errorf(ident.Pos(), "cannot declare init - must be func") check.errorf(ident.Pos(), "cannot declare init - must be func")
return return
} }
// don't declare init functions in the package scope - they are invisible // don't declare init functions in the package scope - they are invisible
f.parent = pkg.scope f.parent = pkg.scope
check.callIdent(ident, obj) check.recordObject(ident, obj)
} else { } else {
check.declare(pkg.scope, ident, obj) check.declare(pkg.scope, ident, obj)
} }
@ -118,9 +118,14 @@ func (check *checker) resolveFiles(files []*ast.File, importer Importer) {
objMap[obj] = &decl{fileScope, typ, init} objMap[obj] = &decl{fileScope, typ, init}
} }
importer := check.ctxt.Import
if importer == nil {
importer = GcImport
}
for _, file := range files { for _, file := range files {
// the package identifier denotes the current package, but it is in no scope // the package identifier denotes the current package, but it is in no scope
check.callIdent(file.Name, pkg) check.recordObject(file.Name, pkg)
fileScope = NewScope(pkg.scope) fileScope = NewScope(pkg.scope)
if retainASTLinks { if retainASTLinks {
@ -138,9 +143,6 @@ func (check *checker) resolveFiles(files []*ast.File, importer Importer) {
for iota, spec := range d.Specs { for iota, spec := range d.Specs {
switch s := spec.(type) { switch s := spec.(type) {
case *ast.ImportSpec: case *ast.ImportSpec:
if importer == nil {
continue
}
path, _ := strconv.Unquote(s.Path.Value) path, _ := strconv.Unquote(s.Path.Value)
imp, err := importer(pkg.imports, path) imp, err := importer(pkg.imports, path)
if imp == nil && err == nil { if imp == nil && err == nil {
@ -163,9 +165,9 @@ func (check *checker) resolveFiles(files []*ast.File, importer Importer) {
imp2 := NewPackage(s.Pos(), path, name, imp.scope, nil, imp.complete) imp2 := NewPackage(s.Pos(), path, name, imp.scope, nil, imp.complete)
if s.Name != nil { if s.Name != nil {
check.callIdent(s.Name, imp2) check.recordObject(s.Name, imp2)
} else { } else {
check.callImplicitObj(s, imp2) check.recordImplicit(s, imp2)
} }
// add import to file scope // add import to file scope
@ -179,7 +181,7 @@ func (check *checker) resolveFiles(files []*ast.File, importer Importer) {
// Note: This will change each imported object's scope! // Note: This will change each imported object's scope!
// May be an issue for types aliases. // May be an issue for types aliases.
check.declare(fileScope, nil, obj) check.declare(fileScope, nil, obj)
check.callImplicitObj(s, obj) check.recordImplicit(s, obj)
} }
} }
} else { } else {

View File

@ -67,15 +67,9 @@ func TestResolveIdents(t *testing.T) {
} }
// resolve and type-check package AST // resolve and type-check package AST
idents := make(map[*ast.Ident]Object)
var ctxt Context var ctxt Context
ctxt.Ident = func(id *ast.Ident, obj Object) { idents := make(map[*ast.Ident]Object)
if old, found := idents[id]; found && old != obj { pkg, err := ctxt.Check("testResolveIdents", fset, files, &Info{Objects: idents})
t.Errorf("%s: identifier %s reported multiple times with different objects", fset.Position(id.Pos()), id.Name)
}
idents[id] = obj
}
pkg, err := ctxt.Check("testResolveIdents", fset, files...)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -72,10 +72,9 @@ func typecheck(t *testing.T, path string, filenames []string) {
} }
// typecheck package files // typecheck package files
ctxt := Context{ var ctxt Context
Error: func(err error) { t.Error(err) }, ctxt.Error = func(err error) { t.Error(err) }
} ctxt.Check(path, fset, files, nil)
ctxt.Check(path, fset, files...)
pkgCount++ pkgCount++
} }

View File

@ -97,8 +97,7 @@ func (check *checker) stmt(s ast.Stmt) {
used = true used = true
// but some builtins are excluded // but some builtins are excluded
// (Caution: This evaluates e.Fun twice, once here and once // (Caution: This evaluates e.Fun twice, once here and once
// below as part of s.X. This has consequences for // below as part of s.X. Perhaps this can be avoided.)
// check.callIdent. Perhaps this can be avoided.)
check.expr(&x, e.Fun) check.expr(&x, e.Fun)
if x.mode != invalid { if x.mode != invalid {
if b, ok := x.typ.(*Builtin); ok && !b.isStatement { if b, ok := x.typ.(*Builtin); ok && !b.isStatement {
@ -268,7 +267,7 @@ func (check *checker) stmt(s ast.Stmt) {
if tag == nil { if tag == nil {
// use fake true tag value and position it at the opening { of the switch // use fake true tag value and position it at the opening { of the switch
ident := &ast.Ident{NamePos: s.Body.Lbrace, Name: "true"} ident := &ast.Ident{NamePos: s.Body.Lbrace, Name: "true"}
check.callIdent(ident, Universe.Lookup(nil, "true")) check.recordObject(ident, Universe.Lookup(nil, "true"))
tag = ident tag = ident
} }
check.expr(&x, tag) check.expr(&x, tag)
@ -418,7 +417,7 @@ func (check *checker) stmt(s ast.Stmt) {
if len(clause.List) == 1 && typ != nil { if len(clause.List) == 1 && typ != nil {
obj := NewVar(lhs.Pos(), check.pkg, lhs.Name, typ) obj := NewVar(lhs.Pos(), check.pkg, lhs.Name, typ)
check.declare(check.topScope, nil, obj) check.declare(check.topScope, nil, obj)
check.callImplicitObj(clause, obj) check.recordImplicit(clause, obj)
} }
} }
check.stmtList(clause.Body) check.stmtList(clause.Body)

View File

@ -21,8 +21,7 @@ func makePkg(t *testing.T, src string) (*Package, error) {
return nil, err return nil, err
} }
// use the package name as package path // use the package name as package path
pkg, err := Check(file.Name.Name, fset, file) return Check(file.Name.Name, fset, []*ast.File{file})
return pkg, err
} }
type testEntry struct { type testEntry struct {

View File

@ -23,7 +23,7 @@ func (check *checker) ident(x *operand, e *ast.Ident, def *Named, cycleOk bool)
x.expr = e x.expr = e
obj := check.topScope.LookupParent(e.Name) obj := check.topScope.LookupParent(e.Name)
check.callIdent(e, obj) check.recordObject(e, obj)
if obj == nil { if obj == nil {
if e.Name == "_" { if e.Name == "_" {
check.errorf(e.Pos(), "cannot use _ as value or type") check.errorf(e.Pos(), "cannot use _ as value or type")
@ -107,10 +107,7 @@ func (check *checker) typ(e ast.Expr, def *Named, cycleOk bool) Type {
t := check.typ0(e, def, cycleOk) t := check.typ0(e, def, cycleOk)
assert(e != nil && t != nil && !isUntyped(t)) assert(e != nil && t != nil && !isUntyped(t))
// notify clients check.recordTypeAndValue(e, t, nil)
if notify := check.ctxt.Expr; notify != nil {
notify(e, t, nil)
}
if trace { if trace {
check.indent-- check.indent--
@ -323,7 +320,7 @@ func (check *checker) collectParams(scope *Scope, list *ast.FieldList, variadicO
} else { } else {
// anonymous parameter // anonymous parameter
par := NewVar(ftype.Pos(), check.pkg, "", typ) par := NewVar(ftype.Pos(), check.pkg, "", typ)
check.callImplicitObj(field, par) check.recordImplicit(field, par)
params = append(params, par) params = append(params, par)
} }
} }

View File

@ -147,38 +147,32 @@ func (imp *Importer) LoadPackage(importPath string) (*PackageInfo, error) {
// source files. // source files.
// //
func (imp *Importer) CreateSourcePackage(importPath string, files []*ast.File) (*PackageInfo, error) { func (imp *Importer) CreateSourcePackage(importPath string, files []*ast.File) (*PackageInfo, error) {
info := &PackageInfo{ pkgInfo := &PackageInfo{
Files: files, Files: files,
types: make(map[ast.Expr]types.Type), types: make(map[ast.Expr]types.Type),
idents: make(map[*ast.Ident]types.Object), idents: make(map[*ast.Ident]types.Object),
constants: make(map[ast.Expr]exact.Value), constants: make(map[ast.Expr]exact.Value),
typecases: make(map[*ast.CaseClause]*types.Var), 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.Info{
info.types[x] = typ Types: pkgInfo.types,
if val != nil { Values: pkgInfo.constants,
info.constants[x] = val Objects: pkgInfo.idents,
} Implicits: make(map[ast.Node]types.Object),
}
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 var firstErr error
info.Pkg, firstErr = tc.Check(importPath, imp.Fset, files...) pkgInfo.Pkg, firstErr = imp.context.TypeChecker.Check(importPath, imp.Fset, files, info)
tc.Expr = nil
tc.Ident = nil
if firstErr != nil { if firstErr != nil {
return nil, firstErr return nil, firstErr
} }
imp.Packages[importPath] = info
return info, nil for node, obj := range info.Implicits {
if cc, ok := node.(*ast.CaseClause); ok {
pkgInfo.typecases[cc] = obj.(*types.Var)
}
}
imp.Packages[importPath] = pkgInfo
return pkgInfo, nil
} }