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 }