From 6d85cc17ddd3f78347f3e8ac3b8910e5e7b60e0d Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Thu, 18 Jul 2013 13:09:03 -0700 Subject: [PATCH] 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 --- cmd/vet/types.go | 23 +++-- go/types/api.go | 172 +++++++++++++++++--------------------- go/types/assignments.go | 4 +- go/types/call.go | 6 +- go/types/check.go | 55 +++++++----- go/types/check_test.go | 2 +- go/types/eval_test.go | 3 +- go/types/expr.go | 19 ++--- go/types/issues_test.go | 62 +++++++------- go/types/resolver.go | 24 +++--- go/types/resolver_test.go | 10 +-- go/types/stdlib_test.go | 7 +- go/types/stmt.go | 7 +- go/types/types_test.go | 3 +- go/types/typexpr.go | 9 +- importer/importer.go | 40 ++++----- 16 files changed, 207 insertions(+), 239 deletions(-) diff --git a/cmd/vet/types.go b/cmd/vet/types.go index b58e6e19c5..c91e6d6e69 100644 --- a/cmd/vet/types.go +++ b/cmd/vet/types.go @@ -19,24 +19,21 @@ func (pkg *Package) check(fs *token.FileSet, astFiles []*ast.File) error { pkg.spans = make(map[types.Object]Span) pkg.types = make(map[ast.Expr]types.Type) 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 // past the first error. There is no need for that function to do anything. context := types.Context{ - Ident: identFn, - Expr: exprFn, 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 } diff --git a/go/types/api.go b/go/types/api.go index 881ca8961f..a81e3b00bc 100644 --- a/go/types/api.go +++ b/go/types/api.go @@ -2,50 +2,26 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package types declares the data types and implements the algorithms for -// name resolution, source-level constant folding, and type checking of Go -// packages. +// Package types declares the data types and implements +// the algorithms for type-checking of Go 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 // 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 // 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. -// -// 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. +// Use Info.Types for the results of type evaluation. // 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 ( "go/ast" "go/token" @@ -53,6 +29,21 @@ import ( "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. // The zero value for a Context is a ready-to-use default context. type Context struct { @@ -62,46 +53,18 @@ type Context struct { // filename:line:column: message 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. // 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 // of the given type. Otherwise DefaultAlignmentof is called. @@ -121,34 +84,42 @@ type Context struct { Sizeof func(Type) int64 } -// 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. -type Importer func(imports map[string]*Package, path string) (pkg *Package, err error) +// Info holds result type information for a package. +type Info struct { + // If Types != nil, it records the type for each expression that is + // type-checked. Expressions corresponding to identifiers on the lhs + // of a declaration are recorded in Objects, not Types. + Types map[ast.Expr]Type -// Check resolves and typechecks a set of package files within the given -// context. It returns the package and the first error encountered, if -// any. If the context's Error handler is nil, Check terminates as soon -// as the first error is encountered; otherwise it continues until the -// entire package is checked. If there are errors, the package may be -// only partially type-checked, and the resulting package may be incomplete -// (missing objects, imports, etc.). -// The provided package path must not resolve to ".", otherwise Check -// returns immediately with a corresponding error message. -func (ctxt *Context) Check(path string, fset *token.FileSet, files ...*ast.File) (*Package, error) { - return check(ctxt, path, fset, files...) + // If Values != nil, it records the value of each constant expression + // that is type-checked. + Values map[ast.Expr]exact.Value + + // If Objects != nil, it records the object denoted by each identifier + // that is type-checked (including package names, dots "." of dot-imports, + // and blank "_" identifiers). For identifiers that were not declared, + // the corresponding object is nil. + // BUG(gri) Label identifiers in break, continue, or goto statements + // are not recorded. + 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. -func Check(path string, fset *token.FileSet, files ...*ast.File) (*Package, error) { - var ctxt Context - return ctxt.Check(path, fset, files...) +// Check type-checks a package and returns the resulting package object, the first +// error if any, and if info != nil, additional type information. 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 ("."). +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 @@ -157,3 +128,12 @@ func IsAssignableTo(V, T Type) bool { x := operand{mode: value, typ: V} 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. diff --git a/go/types/assignments.go b/go/types/assignments.go index f7dd63c8fa..6de6b78384 100644 --- a/go/types/assignments.go +++ b/go/types/assignments.go @@ -109,7 +109,7 @@ func (check *checker) assignVar(lhs ast.Expr, x *operand) { // Don't evaluate lhs if it is the blank identifier. 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 return } @@ -275,7 +275,7 @@ func (check *checker) shortVarDecl(lhs, rhs []ast.Expr) { // declare new variable 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 { check.errorf(lhs.Pos(), "cannot declare %s", lhs) } diff --git a/go/types/call.go b/go/types/call.go index 60e3cca9dd..b92ca25ae3 100644 --- a/go/types/call.go +++ b/go/types/call.go @@ -170,7 +170,7 @@ func (check *checker) selector(x *operand, e *ast.SelectorExpr) { // selector expressions. if ident, ok := e.X.(*ast.Ident); 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) if exp == nil { 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) goto Error } - check.callIdent(e.Sel, exp) + check.recordObject(e.Sel, exp) // Simplified version of the code for *ast.Idents: // - imported packages use types.Scope and types.Objects // - imported objects are always fully initialized @@ -225,7 +225,7 @@ func (check *checker) selector(x *operand, e *ast.SelectorExpr) { goto Error } - check.callIdent(e.Sel, obj) + check.recordObject(e.Sel, obj) if x.mode == typexpr { // method expression diff --git a/go/types/check.go b/go/types/check.go index f4d2213759..8a2eff305c 100644 --- a/go/types/check.go +++ b/go/types/check.go @@ -38,6 +38,7 @@ type exprInfo struct { type checker struct { ctxt *Context fset *token.FileSet + Info // lazily initialized 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) { - if f := check.ctxt.Ident; f != nil { - assert(id != nil) - f(id, obj) +func (check *checker) recordTypeAndValue(x ast.Expr, typ Type, val exact.Value) { + assert(x != nil && typ != nil) + if m := check.Types; m != nil { + m[x] = typ + } + if val != nil { + if m := check.Values; m != nil { + m[x] = val + } } } -func (check *checker) callImplicitObj(node ast.Node, obj Object) { - if f := check.ctxt.ImplicitObj; f != nil { - assert(node != nil && obj != nil) - f(node, obj) +func (check *checker) recordObject(id *ast.Ident, obj Object) { + assert(id != nil) + if m := check.Objects; m != nil { + 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 default: // 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 { check.dump("INTERNAL PANIC: %v", 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{ path: pkgPath, 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 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 } + // install optional info + if info != nil { + check.Info = *info + } + // determine package name and files i := 0 for _, file := range files { @@ -151,16 +169,9 @@ func check(ctxt *Context, pkgPath string, fset *token.FileSet, files ...*ast.Fil // 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) - check.resolveFiles(files, imp) + check.resolveFiles(files[:i]) // typecheck all function/method bodies // (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 // after function body checking for smaller // map size and more immediate feedback. - if ctxt.Expr != nil { + if check.Types != nil || check.Values != nil { for x, info := range check.untyped { - ctxt.Expr(x, info.typ, info.val) + check.recordTypeAndValue(x, info.typ, info.val) } } diff --git a/go/types/check_test.go b/go/types/check_test.go index 4d53923452..10ddf38d6d 100644 --- a/go/types/check_test.go +++ b/go/types/check_test.go @@ -216,7 +216,7 @@ func checkFiles(t *testing.T, testfiles []string) { errlist = append(errlist, err) } } - ctxt.Check(pkgName, fset, files...) + ctxt.Check(pkgName, fset, files, nil) if *listErrors { return diff --git a/go/types/eval_test.go b/go/types/eval_test.go index 734f50deb7..c75c78c38d 100644 --- a/go/types/eval_test.go +++ b/go/types/eval_test.go @@ -7,6 +7,7 @@ package types import ( + "go/ast" "go/parser" "go/token" "strings" @@ -98,7 +99,7 @@ func f(a int, s string) float64 { t.Fatal(err) } - pkg, err := Check("p", fset, file) + pkg, err := Check("p", fset, []*ast.File{file}) if err != nil { t.Fatal(err) } diff --git a/go/types/expr.go b/go/types/expr.go index 51fd9dac5e..338c25c670 100644 --- a/go/types/expr.go +++ b/go/types/expr.go @@ -387,10 +387,8 @@ func (check *checker) updateExprType(x ast.Expr, typ Type, final bool) { return } - // Everything's fine, notify client of final type for x. - if f := check.ctxt.Expr; f != nil { - f(x, typ, old.val) - } + // Everything's fine, record final type and value for x. + check.recordTypeAndValue(x, typ, old.val) } // 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) // convert x into a user-friendly set of values - notify := check.ctxt.Expr + record := true var typ Type var val exact.Value switch x.mode { case invalid: typ = Typ[Invalid] - notify = nil // nothing to do + record = false // nothing to do case novalue: typ = (*Tuple)(nil) case constant: @@ -784,7 +782,7 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type) { case typexprn: x.mode = typexpr typ = x.typ - notify = nil // clients were already notified + record = false // type was already recorded default: typ = x.typ } @@ -794,12 +792,11 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type) { // delay notification until it becomes typed // or until the end of type checking check.untyped[x.expr] = exprInfo{false, typ.(*Basic), val} - } else if notify != nil { - // notify clients + } else if record { // TODO(gri) ensure that literals always report // their dynamic (never interface) type. // This is not the case yet. - notify(x.expr, typ, val) + check.recordTypeAndValue(e, typ, val) } if trace { @@ -898,7 +895,7 @@ func (check *checker) expr0(x *operand, e ast.Expr, hint Type) { continue } fld := fields[i] - check.callIdent(key, fld) + check.recordObject(key, fld) // 0 <= i < len(fields) if visited[i] { check.errorf(kv.Pos(), "duplicate field name %s in struct literal", key.Name) diff --git a/go/types/issues_test.go b/go/types/issues_test.go index 71a8326b3d..3b521e1a57 100644 --- a/go/types/issues_test.go +++ b/go/types/issues_test.go @@ -11,8 +11,6 @@ import ( "go/parser" "strings" "testing" - - "code.google.com/p/go.tools/go/exact" ) func TestIssue5770(t *testing.T) { @@ -23,7 +21,7 @@ func TestIssue5770(t *testing.T) { 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" if err == nil || !strings.Contains(err.Error(), want) { t.Errorf("got: %v; want: %s", err, want) @@ -48,36 +46,36 @@ var ( return } - ctxt := Context{ - Expr: func(x ast.Expr, typ Type, val exact.Value) { - 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) - } - }, - } - - _, err = ctxt.Check(f.Name.Name, fset, f) + var ctxt Context + types := make(map[ast.Expr]Type) + _, err = ctxt.Check(f.Name.Name, fset, []*ast.File{f}, &Info{Types: types}) if err != nil { 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) + } + } } diff --git a/go/types/resolver.go b/go/types/resolver.go index f8efe58360..11011830e9 100644 --- a/go/types/resolver.go +++ b/go/types/resolver.go @@ -25,7 +25,7 @@ func (check *checker) declare(scope *Scope, id *ast.Ident, obj Object) { obj = nil // for callIdent below } 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 // 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" { f, _ := obj.(*Func) if f == nil { - check.callIdent(ident, nil) + check.recordObject(ident, nil) check.errorf(ident.Pos(), "cannot declare init - must be func") return } // don't declare init functions in the package scope - they are invisible f.parent = pkg.scope - check.callIdent(ident, obj) + check.recordObject(ident, obj) } else { 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} } + importer := check.ctxt.Import + if importer == nil { + importer = GcImport + } + for _, file := range files { // 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) if retainASTLinks { @@ -138,9 +143,6 @@ func (check *checker) resolveFiles(files []*ast.File, importer Importer) { for iota, spec := range d.Specs { switch s := spec.(type) { case *ast.ImportSpec: - if importer == nil { - continue - } path, _ := strconv.Unquote(s.Path.Value) imp, err := importer(pkg.imports, path) 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) if s.Name != nil { - check.callIdent(s.Name, imp2) + check.recordObject(s.Name, imp2) } else { - check.callImplicitObj(s, imp2) + check.recordImplicit(s, imp2) } // 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! // May be an issue for types aliases. check.declare(fileScope, nil, obj) - check.callImplicitObj(s, obj) + check.recordImplicit(s, obj) } } } else { diff --git a/go/types/resolver_test.go b/go/types/resolver_test.go index 2960bd07ed..58b9a22efd 100644 --- a/go/types/resolver_test.go +++ b/go/types/resolver_test.go @@ -67,15 +67,9 @@ func TestResolveIdents(t *testing.T) { } // resolve and type-check package AST - idents := make(map[*ast.Ident]Object) var ctxt Context - ctxt.Ident = func(id *ast.Ident, obj Object) { - if old, found := idents[id]; found && old != obj { - 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...) + idents := make(map[*ast.Ident]Object) + pkg, err := ctxt.Check("testResolveIdents", fset, files, &Info{Objects: idents}) if err != nil { t.Fatal(err) } diff --git a/go/types/stdlib_test.go b/go/types/stdlib_test.go index 639d6bc399..552ea1153e 100644 --- a/go/types/stdlib_test.go +++ b/go/types/stdlib_test.go @@ -72,10 +72,9 @@ func typecheck(t *testing.T, path string, filenames []string) { } // typecheck package files - ctxt := Context{ - Error: func(err error) { t.Error(err) }, - } - ctxt.Check(path, fset, files...) + var ctxt Context + ctxt.Error = func(err error) { t.Error(err) } + ctxt.Check(path, fset, files, nil) pkgCount++ } diff --git a/go/types/stmt.go b/go/types/stmt.go index dcd54fe350..97b5af52e0 100644 --- a/go/types/stmt.go +++ b/go/types/stmt.go @@ -97,8 +97,7 @@ func (check *checker) stmt(s ast.Stmt) { used = true // but some builtins are excluded // (Caution: This evaluates e.Fun twice, once here and once - // below as part of s.X. This has consequences for - // check.callIdent. Perhaps this can be avoided.) + // below as part of s.X. Perhaps this can be avoided.) check.expr(&x, e.Fun) if x.mode != invalid { if b, ok := x.typ.(*Builtin); ok && !b.isStatement { @@ -268,7 +267,7 @@ func (check *checker) stmt(s ast.Stmt) { if tag == nil { // use fake true tag value and position it at the opening { of the switch 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 } check.expr(&x, tag) @@ -418,7 +417,7 @@ func (check *checker) stmt(s ast.Stmt) { if len(clause.List) == 1 && typ != nil { obj := NewVar(lhs.Pos(), check.pkg, lhs.Name, typ) check.declare(check.topScope, nil, obj) - check.callImplicitObj(clause, obj) + check.recordImplicit(clause, obj) } } check.stmtList(clause.Body) diff --git a/go/types/types_test.go b/go/types/types_test.go index 178939c9b5..869d10e3bf 100644 --- a/go/types/types_test.go +++ b/go/types/types_test.go @@ -21,8 +21,7 @@ func makePkg(t *testing.T, src string) (*Package, error) { return nil, err } // use the package name as package path - pkg, err := Check(file.Name.Name, fset, file) - return pkg, err + return Check(file.Name.Name, fset, []*ast.File{file}) } type testEntry struct { diff --git a/go/types/typexpr.go b/go/types/typexpr.go index 8777692b1e..1ed83680d5 100644 --- a/go/types/typexpr.go +++ b/go/types/typexpr.go @@ -23,7 +23,7 @@ func (check *checker) ident(x *operand, e *ast.Ident, def *Named, cycleOk bool) x.expr = e obj := check.topScope.LookupParent(e.Name) - check.callIdent(e, obj) + check.recordObject(e, obj) if obj == nil { if e.Name == "_" { 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) assert(e != nil && t != nil && !isUntyped(t)) - // notify clients - if notify := check.ctxt.Expr; notify != nil { - notify(e, t, nil) - } + check.recordTypeAndValue(e, t, nil) if trace { check.indent-- @@ -323,7 +320,7 @@ func (check *checker) collectParams(scope *Scope, list *ast.FieldList, variadicO } else { // anonymous parameter par := NewVar(ftype.Pos(), check.pkg, "", typ) - check.callImplicitObj(field, par) + check.recordImplicit(field, par) params = append(params, par) } } diff --git a/importer/importer.go b/importer/importer.go index 19355b61e3..b25d4100cb 100644 --- a/importer/importer.go +++ b/importer/importer.go @@ -147,38 +147,32 @@ func (imp *Importer) LoadPackage(importPath string) (*PackageInfo, error) { // source files. // func (imp *Importer) CreateSourcePackage(importPath string, files []*ast.File) (*PackageInfo, error) { - info := &PackageInfo{ + pkgInfo := &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) - } + + info := &types.Info{ + Types: pkgInfo.types, + Values: pkgInfo.constants, + Objects: pkgInfo.idents, + Implicits: make(map[ast.Node]types.Object), } var firstErr error - info.Pkg, firstErr = tc.Check(importPath, imp.Fset, files...) - tc.Expr = nil - tc.Ident = nil + pkgInfo.Pkg, firstErr = imp.context.TypeChecker.Check(importPath, imp.Fset, files, info) if firstErr != nil { 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 }