Commit 5a9463bd authored by Robert Griesemer's avatar Robert Griesemer

go/types: Moving from *ast.Objects to types.Objects (step 1).

The existing type checker was relying on augmenting ast.Object
fields (empty interfaces) for its purposes. While this worked
for some time now, it has become increasingly brittle. Also,
the need for package information for Fields and Methods would
have required a new field in each ast.Object. Rather than making
them bigger and the code even more subtle, in this CL we are moving
away from ast.Objects.

The types packge now defines its own objects for different
language entities (Const, Var, TypeName, Func), and they
implement the types.Object interface. Imported packages
create a Package object which holds the exported entities
in a types.Scope of types.Objects.

For type-checking, the current package is still using ast.Objects
to make this transition manageable. In a next step, the type-
checker will also use types.Objects instead, which opens the door
door to resolving ASTs entirely by the type checker. As a result,
the AST and type checker become less entangled, and ASTs can be
manipulated "by hand" or programmatically w/o having to worry
about scope and object invariants that are very hard to maintain.

(As a consequence, a future parser can do less work, and a
future AST will not need to define objects and scopes anymore.
Also, object resolution which is now split across the parser,
the ast, (ast.NewPackage), and even the type checker (for composite
literal keys) can be done in a single place which will be simpler
and more efficient.)

Change details:
- Check now takes a []*ast.File instead of a map[string]*ast.File.
It's easier to handle (I deleted code at all use sites) and does
not suffer from undefined order (which is a pain for testing).
- ast.Object.Data is now a *types.Package rather then an *ast.Scope
if the object is a package (obj.Kind == ast.Pkg). Eventually this
will go away altogether.
- Instead of an ast.Importer, Check now uses a types.Importer
(which returns a *types.Package).
- types.NamedType has two object fields (Obj Object and obj *ast.Object);
eventually there will be only Obj. The *ast.Object is needed during
this transition since a NamedType may refer to either an imported
(using types.Object) or locally defined (using *ast.Object) type.
- ast.NewPackage is not used anymore - there's a local copy for
package-level resolution of imports.
- struct fields now take the package origin into account.
- The GcImporter is now returning a *types.Package. It cannot be
used with ast.NewPackage anymore. If that functionality is still
used, a copy of the old GcImporter should be made locally (note
that GcImporter was part of exp/types and it's API was not frozen).
- dot-imports are not handled for the time being (this will come back).

R=adonovan
CC=golang-dev
https://golang.org/cl/7058060
parent 578f24d5
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
package main package main
import ( import (
"errors"
"flag" "flag"
"fmt" "fmt"
"go/ast" "go/ast"
...@@ -92,8 +91,7 @@ func parse(fset *token.FileSet, filename string, src []byte) *ast.File { ...@@ -92,8 +91,7 @@ func parse(fset *token.FileSet, filename string, src []byte) *ast.File {
return file return file
} }
func parseStdin(fset *token.FileSet) (files map[string]*ast.File) { func parseStdin(fset *token.FileSet) (files []*ast.File) {
files = make(map[string]*ast.File)
src, err := ioutil.ReadAll(os.Stdin) src, err := ioutil.ReadAll(os.Stdin)
if err != nil { if err != nil {
report(err) report(err)
...@@ -101,13 +99,12 @@ func parseStdin(fset *token.FileSet) (files map[string]*ast.File) { ...@@ -101,13 +99,12 @@ func parseStdin(fset *token.FileSet) (files map[string]*ast.File) {
} }
const filename = "<standard input>" const filename = "<standard input>"
if file := parse(fset, filename, src); file != nil { if file := parse(fset, filename, src); file != nil {
files[filename] = file files = []*ast.File{file}
} }
return return
} }
func parseFiles(fset *token.FileSet, filenames []string) (files map[string]*ast.File) { func parseFiles(fset *token.FileSet, filenames []string) (files []*ast.File) {
files = make(map[string]*ast.File)
for _, filename := range filenames { for _, filename := range filenames {
src, err := ioutil.ReadFile(filename) src, err := ioutil.ReadFile(filename)
if err != nil { if err != nil {
...@@ -115,11 +112,7 @@ func parseFiles(fset *token.FileSet, filenames []string) (files map[string]*ast. ...@@ -115,11 +112,7 @@ func parseFiles(fset *token.FileSet, filenames []string) (files map[string]*ast.
continue continue
} }
if file := parse(fset, filename, src); file != nil { if file := parse(fset, filename, src); file != nil {
if files[filename] != nil { files = append(files, file)
report(errors.New(fmt.Sprintf("%q: duplicate file", filename)))
continue
}
files[filename] = file
} }
} }
return return
...@@ -169,8 +162,8 @@ func processFiles(filenames []string, allFiles bool) { ...@@ -169,8 +162,8 @@ func processFiles(filenames []string, allFiles bool) {
processPackage(fset, parseFiles(fset, filenames[0:i])) processPackage(fset, parseFiles(fset, filenames[0:i]))
} }
func processPackage(fset *token.FileSet, files map[string]*ast.File) { func processPackage(fset *token.FileSet, files []*ast.File) {
_, err := types.Check(fset, files) _, _, err := types.Check(fset, files)
if err != nil { if err != nil {
report(err) report(err)
} }
......
...@@ -64,19 +64,16 @@ func (s *Scope) String() string { ...@@ -64,19 +64,16 @@ func (s *Scope) String() string {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Objects // Objects
// TODO(gri) Consider replacing the Object struct with an interface
// and a corresponding set of object implementations.
// An Object describes a named language entity such as a package, // An Object describes a named language entity such as a package,
// constant, type, variable, function (incl. methods), or label. // constant, type, variable, function (incl. methods), or label.
// //
// The Data fields contains object-specific data: // The Data fields contains object-specific data:
// //
// Kind Data type Data value // Kind Data type Data value
// Pkg *Scope package scope // Pkg *types.Package package scope
// Con int iota for the respective declaration // Con int iota for the respective declaration
// Con != nil constant value // Con != nil constant value
// Typ *Scope method scope; nil if no methods // Typ *Scope (used as method scope during type checking - transient)
// //
type Object struct { type Object struct {
Kind ObjKind Kind ObjKind
......
...@@ -41,9 +41,20 @@ type Context struct { ...@@ -41,9 +41,20 @@ type Context struct {
Expr func(x ast.Expr, typ Type, val interface{}) Expr func(x ast.Expr, typ Type, val interface{})
// If Import is not nil, it is used instead of GcImport. // If Import is not nil, it is used instead of GcImport.
Import ast.Importer 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.
type Importer func(imports map[string]*Package, path string) (pkg *Package, err error)
// Default is the default context for type checking. // Default is the default context for type checking.
var Default = Context{ var Default = Context{
// TODO(gri) Perhaps this should depend on GOARCH? // TODO(gri) Perhaps this should depend on GOARCH?
...@@ -57,11 +68,17 @@ var Default = Context{ ...@@ -57,11 +68,17 @@ var Default = Context{
// it returns the first error. If the context's Error handler is nil, // it returns the first error. If the context's Error handler is nil,
// Check terminates as soon as the first error is encountered. // Check terminates as soon as the first error is encountered.
// //
func (ctxt *Context) Check(fset *token.FileSet, files map[string]*ast.File) (*ast.Package, error) { // CAUTION: At the moment, the returned *ast.Package only contains the package
// name and scope - the other fields are not set up. The returned
// *Package contains the name and imports (but no scope yet). Once
// we have the scope moved from *ast.Scope to *Scope, only *Package
// will be returned.
//
func (ctxt *Context) Check(fset *token.FileSet, files []*ast.File) (*ast.Package, *Package, error) {
return check(ctxt, fset, files) return check(ctxt, fset, files)
} }
// Check is shorthand for Default.Check. // Check is shorthand for Default.Check.
func Check(fset *token.FileSet, files map[string]*ast.File) (*ast.Package, error) { func Check(fset *token.FileSet, files []*ast.File) (*ast.Package, *Package, error) {
return Default.Check(fset, files) return Default.Check(fset, files)
} }
...@@ -9,9 +9,7 @@ package types ...@@ -9,9 +9,7 @@ package types
import ( import (
"fmt" "fmt"
"go/ast" "go/ast"
"go/scanner"
"go/token" "go/token"
"sort"
) )
// enable for debugging // enable for debugging
...@@ -23,6 +21,7 @@ type checker struct { ...@@ -23,6 +21,7 @@ type checker struct {
files []*ast.File files []*ast.File
// lazily initialized // lazily initialized
pkg *Package
pkgscope *ast.Scope pkgscope *ast.Scope
firsterr error firsterr error
initspec map[*ast.ValueSpec]*ast.ValueSpec // "inherited" type and initialization expressions for constant declarations initspec map[*ast.ValueSpec]*ast.ValueSpec // "inherited" type and initialization expressions for constant declarations
...@@ -164,7 +163,7 @@ func (check *checker) object(obj *ast.Object, cycleOk bool) { ...@@ -164,7 +163,7 @@ func (check *checker) object(obj *ast.Object, cycleOk bool) {
check.valueSpec(spec.Pos(), obj, spec.Names, init.Type, init.Values, iota) check.valueSpec(spec.Pos(), obj, spec.Names, init.Type, init.Values, iota)
case ast.Typ: case ast.Typ:
typ := &NamedType{Obj: obj} typ := &NamedType{obj: obj}
obj.Type = typ // "mark" object so recursion terminates obj.Type = typ // "mark" object so recursion terminates
typ.Underlying = underlying(check.typ(obj.Decl.(*ast.TypeSpec).Type, cycleOk)) typ.Underlying = underlying(check.typ(obj.Decl.(*ast.TypeSpec).Type, cycleOk))
// typecheck associated method signatures // typecheck associated method signatures
...@@ -188,14 +187,18 @@ func (check *checker) object(obj *ast.Object, cycleOk bool) { ...@@ -188,14 +187,18 @@ func (check *checker) object(obj *ast.Object, cycleOk bool) {
} }
} }
// typecheck method signatures // typecheck method signatures
var methods []*Method
for _, obj := range scope.Objects { for _, obj := range scope.Objects {
mdecl := obj.Decl.(*ast.FuncDecl) mdecl := obj.Decl.(*ast.FuncDecl)
sig := check.typ(mdecl.Type, cycleOk).(*Signature) sig := check.typ(mdecl.Type, cycleOk).(*Signature)
params, _ := check.collectParams(mdecl.Recv, false) params, _ := check.collectParams(mdecl.Recv, false)
sig.Recv = params[0] // the parser/assocMethod ensure there is exactly one parameter sig.Recv = params[0] // the parser/assocMethod ensure there is exactly one parameter
obj.Type = sig obj.Type = sig
methods = append(methods, &Method{QualifiedName{check.pkg, obj.Name}, sig})
check.later(obj, sig, mdecl.Body) check.later(obj, sig, mdecl.Body)
} }
typ.Methods = methods
obj.Data = nil // don't use obj.Data later, accidentally
} }
case ast.Fun: case ast.Fun:
...@@ -346,33 +349,15 @@ func (check *checker) iterate(f func(*checker, ast.Decl)) { ...@@ -346,33 +349,15 @@ func (check *checker) iterate(f func(*checker, ast.Decl)) {
} }
} }
// sortedFiles returns the sorted list of package files given a package file map.
func sortedFiles(m map[string]*ast.File) []*ast.File {
keys := make([]string, len(m))
i := 0
for k, _ := range m {
keys[i] = k
i++
}
sort.Strings(keys)
files := make([]*ast.File, len(m))
for i, k := range keys {
files[i] = m[k]
}
return files
}
// A bailout panic is raised to indicate early termination. // A bailout panic is raised to indicate early termination.
type bailout struct{} type bailout struct{}
func check(ctxt *Context, fset *token.FileSet, files map[string]*ast.File) (pkg *ast.Package, err error) { func check(ctxt *Context, fset *token.FileSet, files []*ast.File) (astpkg *ast.Package, pkg *Package, err error) {
// initialize checker // initialize checker
check := checker{ check := checker{
ctxt: ctxt, ctxt: ctxt,
fset: fset, fset: fset,
files: sortedFiles(files), files: files,
initspec: make(map[*ast.ValueSpec]*ast.ValueSpec), initspec: make(map[*ast.ValueSpec]*ast.ValueSpec),
} }
...@@ -394,8 +379,9 @@ func check(ctxt *Context, fset *token.FileSet, files map[string]*ast.File) (pkg ...@@ -394,8 +379,9 @@ func check(ctxt *Context, fset *token.FileSet, files map[string]*ast.File) (pkg
imp := ctxt.Import imp := ctxt.Import
if imp == nil { if imp == nil {
// wrap GcImport to import packages only once by default. // wrap GcImport to import packages only once by default.
// TODO(gri) move this into resolve
imported := make(map[string]bool) imported := make(map[string]bool)
imp = func(imports map[string]*ast.Object, path string) (*ast.Object, error) { imp = func(imports map[string]*Package, path string) (*Package, error) {
if imported[path] && imports[path] != nil { if imported[path] && imports[path] != nil {
return imports[path], nil return imports[path], nil
} }
...@@ -406,17 +392,13 @@ func check(ctxt *Context, fset *token.FileSet, files map[string]*ast.File) (pkg ...@@ -406,17 +392,13 @@ func check(ctxt *Context, fset *token.FileSet, files map[string]*ast.File) (pkg
return pkg, err return pkg, err
} }
} }
pkg, err = ast.NewPackage(fset, files, imp, Universe) astpkg, pkg = check.resolve(imp)
if err != nil {
if list, _ := err.(scanner.ErrorList); len(list) > 0 { // Imported packages and all types refer to types.Objects,
for _, err := range list { // the current package files' AST uses ast.Objects.
check.err(err) // Use an ast.Scope for the current package scope.
} check.pkg = pkg
} else { check.pkgscope = astpkg.Scope
check.err(err)
}
}
check.pkgscope = pkg.Scope
// determine missing constant initialization expressions // determine missing constant initialization expressions
// and associate methods with types // and associate methods with types
......
...@@ -88,18 +88,15 @@ func splitError(err error) (pos, msg string) { ...@@ -88,18 +88,15 @@ func splitError(err error) (pos, msg string) {
return return
} }
func parseFiles(t *testing.T, testname string, filenames []string) (map[string]*ast.File, []error) { func parseFiles(t *testing.T, testname string, filenames []string) ([]*ast.File, []error) {
files := make(map[string]*ast.File) var files []*ast.File
var errlist []error var errlist []error
for _, filename := range filenames { for _, filename := range filenames {
if _, exists := files[filename]; exists {
t.Fatalf("%s: duplicate file %s", testname, filename)
}
file, err := parser.ParseFile(fset, filename, nil, parser.DeclarationErrors) file, err := parser.ParseFile(fset, filename, nil, parser.DeclarationErrors)
if file == nil { if file == nil {
t.Fatalf("%s: could not parse file %s", testname, filename) t.Fatalf("%s: could not parse file %s", testname, filename)
} }
files[filename] = file files = append(files, file)
if err != nil { if err != nil {
if list, _ := err.(scanner.ErrorList); len(list) > 0 { if list, _ := err.(scanner.ErrorList); len(list) > 0 {
for _, err := range list { for _, err := range list {
...@@ -121,10 +118,11 @@ var errRx = regexp.MustCompile(`^/\* *ERROR *"([^"]*)" *\*/$`) ...@@ -121,10 +118,11 @@ var errRx = regexp.MustCompile(`^/\* *ERROR *"([^"]*)" *\*/$`)
// errMap collects the regular expressions of ERROR comments found // errMap collects the regular expressions of ERROR comments found
// in files and returns them as a map of error positions to error messages. // in files and returns them as a map of error positions to error messages.
// //
func errMap(t *testing.T, testname string, files map[string]*ast.File) map[string][]string { func errMap(t *testing.T, testname string, files []*ast.File) map[string][]string {
errmap := make(map[string][]string) errmap := make(map[string][]string)
for filename := range files { for _, file := range files {
filename := fset.Position(file.Package).Filename
src, err := ioutil.ReadFile(filename) src, err := ioutil.ReadFile(filename)
if err != nil { if err != nil {
t.Fatalf("%s: could not read %s", testname, filename) t.Fatalf("%s: could not read %s", testname, filename)
...@@ -236,8 +234,8 @@ func TestCheck(t *testing.T) { ...@@ -236,8 +234,8 @@ func TestCheck(t *testing.T) {
// Declare builtins for testing. // Declare builtins for testing.
// Not done in an init func to avoid an init race with // Not done in an init func to avoid an init race with
// the construction of the Universe var. // the construction of the Universe var.
def(ast.Fun, "assert").Type = &builtin{aType, _Assert, "assert", 1, false, true} def(ast.Fun, "assert", &builtin{aType, _Assert, "assert", 1, false, true})
def(ast.Fun, "trace").Type = &builtin{aType, _Trace, "trace", 0, true, true} def(ast.Fun, "trace", &builtin{aType, _Trace, "trace", 0, true, true})
// For easy debugging w/o changing the testing code, // For easy debugging w/o changing the testing code,
// if there is a local test file, only test that file. // if there is a local test file, only test that file.
......
...@@ -29,7 +29,7 @@ func (check *checker) conversion(x *operand, conv *ast.CallExpr, typ Type, iota ...@@ -29,7 +29,7 @@ func (check *checker) conversion(x *operand, conv *ast.CallExpr, typ Type, iota
} }
// TODO(gri) fix this - implement all checks and constant evaluation // TODO(gri) fix this - implement all checks and constant evaluation
if x.mode != constant { if x.mode != constant || !isConstType(typ) {
x.mode = value x.mode = value
} }
x.expr = conv x.expr = conv
......
...@@ -311,7 +311,16 @@ func writeType(buf *bytes.Buffer, typ Type) { ...@@ -311,7 +311,16 @@ func writeType(buf *bytes.Buffer, typ Type) {
writeType(buf, t.Elt) writeType(buf, t.Elt)
case *NamedType: case *NamedType:
buf.WriteString(t.Obj.Name) var s string
switch {
case t.obj != nil:
s = t.obj.Name
case t.Obj != nil:
s = t.Obj.GetName()
default:
s = "<NamedType w/o object>"
}
buf.WriteString(s)
default: default:
fmt.Fprintf(buf, "<type %T>", t) fmt.Fprintf(buf, "<type %T>", t)
......
...@@ -48,14 +48,14 @@ func (check *checker) collectParams(list *ast.FieldList, variadicOk bool) (param ...@@ -48,14 +48,14 @@ func (check *checker) collectParams(list *ast.FieldList, variadicOk bool) (param
obj := name.Obj obj := name.Obj
obj.Type = typ obj.Type = typ
last = obj last = obj
params = append(params, &Var{obj.Name, typ}) params = append(params, &Var{Name: obj.Name, Type: typ})
} }
} else { } else {
// anonymous parameter // anonymous parameter
obj := ast.NewObj(ast.Var, "") obj := ast.NewObj(ast.Var, "")
obj.Type = typ obj.Type = typ
last = obj last = obj
params = append(params, &Var{obj.Name, typ}) params = append(params, &Var{Name: obj.Name, Type: typ})
} }
} }
// For a variadic function, change the last parameter's object type // For a variadic function, change the last parameter's object type
...@@ -84,7 +84,7 @@ func (check *checker) collectMethods(list *ast.FieldList) (methods []*Method) { ...@@ -84,7 +84,7 @@ func (check *checker) collectMethods(list *ast.FieldList) (methods []*Method) {
continue continue
} }
for _, name := range f.Names { for _, name := range f.Names {
methods = append(methods, &Method{name.Name, sig}) methods = append(methods, &Method{QualifiedName{check.pkg, name.Name}, sig})
} }
} else { } else {
// embedded interface // embedded interface
...@@ -137,15 +137,24 @@ func (check *checker) collectFields(list *ast.FieldList, cycleOk bool) (fields [ ...@@ -137,15 +137,24 @@ func (check *checker) collectFields(list *ast.FieldList, cycleOk bool) (fields [
if len(f.Names) > 0 { if len(f.Names) > 0 {
// named fields // named fields
for _, name := range f.Names { for _, name := range f.Names {
fields = append(fields, &Field{name.Name, typ, tag, false}) fields = append(fields, &Field{QualifiedName{check.pkg, name.Name}, typ, tag, false})
} }
} else { } else {
// anonymous field // anonymous field
switch t := deref(typ).(type) { switch t := deref(typ).(type) {
case *Basic: case *Basic:
fields = append(fields, &Field{t.Name, typ, tag, true}) fields = append(fields, &Field{QualifiedName{check.pkg, t.Name}, typ, tag, true})
case *NamedType: case *NamedType:
fields = append(fields, &Field{t.Obj.Name, typ, tag, true}) var name string
switch {
case t.obj != nil:
name = t.obj.Name
case t.Obj != nil:
name = t.Obj.GetName()
default:
unreachable()
}
fields = append(fields, &Field{QualifiedName{check.pkg, name}, typ, tag, true})
default: default:
if typ != Typ[Invalid] { if typ != Typ[Invalid] {
check.invalidAST(f.Type.Pos(), "anonymous field type %s must be named", typ) check.invalidAST(f.Type.Pos(), "anonymous field type %s must be named", typ)
...@@ -183,9 +192,6 @@ func (check *checker) unary(x *operand, op token.Token) { ...@@ -183,9 +192,6 @@ func (check *checker) unary(x *operand, op token.Token) {
case token.AND: case token.AND:
// spec: "As an exception to the addressability // spec: "As an exception to the addressability
// requirement x may also be a composite literal." // requirement x may also be a composite literal."
// (The spec doesn't specify whether the literal
// can be parenthesized or not, but all compilers
// accept parenthesized literals.)
if _, ok := unparen(x.expr).(*ast.CompositeLit); ok { if _, ok := unparen(x.expr).(*ast.CompositeLit); ok {
x.mode = variable x.mode = variable
} }
...@@ -872,29 +878,33 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle ...@@ -872,29 +878,33 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle
// selector expressions. // selector expressions.
if ident, ok := e.X.(*ast.Ident); ok { if ident, ok := e.X.(*ast.Ident); ok {
if obj := ident.Obj; obj != nil && obj.Kind == ast.Pkg { if obj := ident.Obj; obj != nil && obj.Kind == ast.Pkg {
exp := obj.Data.(*ast.Scope).Lookup(sel) exp := obj.Data.(*Package).Scope.Lookup(sel)
if exp == nil { if exp == nil {
check.errorf(e.Sel.Pos(), "cannot refer to unexported %s", sel) check.errorf(e.Sel.Pos(), "cannot refer to unexported %s", sel)
goto Error goto Error
} }
// simplified version of the code for *ast.Idents: // Simplified version of the code for *ast.Idents:
// imported objects are always fully initialized // - imported packages use types.Scope and types.Objects
switch exp.Kind { // - imported objects are always fully initialized
case ast.Con: switch exp := exp.(type) {
assert(exp.Data != nil) case *Const:
assert(exp.Val != nil)
x.mode = constant x.mode = constant
x.val = exp.Data x.typ = exp.Type
case ast.Typ: x.val = exp.Val
case *TypeName:
x.mode = typexpr x.mode = typexpr
case ast.Var: x.typ = exp.Type
case *Var:
x.mode = variable x.mode = variable
case ast.Fun: x.typ = exp.Type
case *Func:
x.mode = value x.mode = value
x.typ = exp.Type
default: default:
unreachable() unreachable()
} }
x.expr = e x.expr = e
x.typ = exp.Type.(Type)
return return
} }
} }
...@@ -903,7 +913,7 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle ...@@ -903,7 +913,7 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle
if x.mode == invalid { if x.mode == invalid {
goto Error goto Error
} }
mode, typ := lookupField(x.typ, sel) mode, typ := lookupField(x.typ, QualifiedName{check.pkg, sel})
if mode == invalid { if mode == invalid {
check.invalidOp(e.Pos(), "%s has no single field or method %s", x, sel) check.invalidOp(e.Pos(), "%s has no single field or method %s", x, sel)
goto Error goto Error
...@@ -921,7 +931,7 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle ...@@ -921,7 +931,7 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle
// pointer vs non-pointer receivers => typechecker is too lenient // pointer vs non-pointer receivers => typechecker is too lenient
x.mode = value x.mode = value
x.typ = &Signature{ x.typ = &Signature{
Params: append([]*Var{{"", x.typ}}, sig.Params...), Params: append([]*Var{{Type: x.typ}}, sig.Params...),
Results: sig.Results, Results: sig.Results,
IsVariadic: sig.IsVariadic, IsVariadic: sig.IsVariadic,
} }
......
This diff is collapsed.
...@@ -51,7 +51,7 @@ func compile(t *testing.T, dirname, filename string) string { ...@@ -51,7 +51,7 @@ func compile(t *testing.T, dirname, filename string) string {
// Use the same global imports map for all tests. The effect is // Use the same global imports map for all tests. The effect is
// as if all tested packages were imported into a single package. // as if all tested packages were imported into a single package.
var imports = make(map[string]*ast.Object) var imports = make(map[string]*Package)
func testPath(t *testing.T, path string) bool { func testPath(t *testing.T, path string) bool {
t0 := time.Now() t0 := time.Now()
...@@ -147,12 +147,34 @@ func TestGcImportedTypes(t *testing.T) { ...@@ -147,12 +147,34 @@ func TestGcImportedTypes(t *testing.T) {
continue continue
} }
obj := pkg.Data.(*ast.Scope).Lookup(objName) obj := pkg.Scope.Lookup(objName)
if obj.Kind != test.kind {
t.Errorf("%s: got kind = %q; want %q", test.name, obj.Kind, test.kind) // TODO(gri) should define an accessor on Object
var kind ast.ObjKind
var typ Type
switch obj := obj.(type) {
case *Const:
kind = ast.Con
typ = obj.Type
case *TypeName:
kind = ast.Typ
typ = obj.Type
case *Var:
kind = ast.Var
typ = obj.Type
case *Func:
kind = ast.Fun
typ = obj.Type
default:
unreachable()
} }
typ := typeString(underlying(obj.Type.(Type)))
if typ != test.typ { if kind != test.kind {
t.Errorf("%s: got kind = %q; want %q", test.name, kind, test.kind)
}
str := typeString(underlying(typ))
if str != test.typ {
t.Errorf("%s: got type = %q; want %q", test.name, typ, test.typ) t.Errorf("%s: got type = %q; want %q", test.name, typ, test.typ)
} }
} }
......
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package types
// An Object describes a named language entity such as a package,
// constant, type, variable, function (incl. methods), or label.
// All objects implement the Object interface.
//
type Object interface {
anObject()
GetName() string
}
// A Package represents the contents (objects) of a Go package.
type Package struct {
implementsObject
Name string
Path string // import path, "" for current (non-imported) package
Scope *Scope // nil for current (non-imported) package for now
Imports map[string]*Package // map of import paths to packages
}
// A Const represents a declared constant.
type Const struct {
implementsObject
Name string
Type Type
Val interface{}
}
// A TypeName represents a declared type.
type TypeName struct {
implementsObject
Name string
Type Type // *NamedType or *Basic
}
// A Variable represents a declared variable (including function parameters and results).
type Var struct {
implementsObject
Name string
Type Type
}
// A Func represents a declared function.
type Func struct {
implementsObject
Name string
Type Type // *Signature or *Builtin
}
func (obj *Package) GetName() string { return obj.Name }
func (obj *Const) GetName() string { return obj.Name }
func (obj *TypeName) GetName() string { return obj.Name }
func (obj *Var) GetName() string { return obj.Name }
func (obj *Func) GetName() string { return obj.Name }
func (obj *Package) GetType() Type { return nil }
func (obj *Const) GetType() Type { return obj.Type }
func (obj *TypeName) GetType() Type { return obj.Type }
func (obj *Var) GetType() Type { return obj.Type }
func (obj *Func) GetType() Type { return obj.Type }
// All concrete objects embed implementsObject which
// ensures that they all implement the Object interface.
type implementsObject struct{}
func (*implementsObject) anObject() {}
// A Scope maintains the set of named language entities declared
// in the scope and a link to the immediately surrounding (outer)
// scope.
//
type Scope struct {
Outer *Scope
Elems []Object // scope entries in insertion order
large map[string]Object // for fast lookup - only used for larger scopes
}
// Lookup returns the object with the given name if it is
// found in scope s, otherwise it returns nil. Outer scopes
// are ignored.
//
func (s *Scope) Lookup(name string) Object {
if s.large != nil {
return s.large[name]
}
for _, obj := range s.Elems {
if obj.GetName() == name {
return obj
}
}
return nil
}
// Insert attempts to insert an object obj into scope s.
// If s already contains an object with the same name,
// Insert leaves s unchanged and returns that object.
// Otherwise it inserts obj and returns nil.
//
func (s *Scope) Insert(obj Object) Object {
name := obj.GetName()
if alt := s.Lookup(name); alt != nil {
return alt
}
s.Elems = append(s.Elems, obj)
if len(s.Elems) > 20 {
if s.large == nil {
m := make(map[string]Object, len(s.Elems))
for _, obj := range s.Elems {
m[obj.GetName()] = obj
}
s.large = m
}
s.large[name] = obj
}
return nil
}
...@@ -222,11 +222,11 @@ type embeddedType struct { ...@@ -222,11 +222,11 @@ type embeddedType struct {
} }
// lookupFieldBreadthFirst searches all types in list for a single entry (field // lookupFieldBreadthFirst searches all types in list for a single entry (field
// or method) of the given name. If such a field is found, the result describes // or method) of the given name from the given package. If such a field is found,
// the field mode and type; otherwise the result mode is invalid. // the result describes the field mode and type; otherwise the result mode is invalid.
// (This function is similar in structure to FieldByNameFunc in reflect/type.go) // (This function is similar in structure to FieldByNameFunc in reflect/type.go)
// //
func lookupFieldBreadthFirst(list []embeddedType, name string) (res lookupResult) { func lookupFieldBreadthFirst(list []embeddedType, name QualifiedName) (res lookupResult) {
// visited records the types that have been searched already. // visited records the types that have been searched already.
visited := make(map[*NamedType]bool) visited := make(map[*NamedType]bool)
...@@ -265,20 +265,23 @@ func lookupFieldBreadthFirst(list []embeddedType, name string) (res lookupResult ...@@ -265,20 +265,23 @@ func lookupFieldBreadthFirst(list []embeddedType, name string) (res lookupResult
visited[typ] = true visited[typ] = true
// look for a matching attached method // look for a matching attached method
if data := typ.Obj.Data; data != nil { if typ.obj != nil {
if obj := data.(*ast.Scope).Lookup(name); obj != nil { assert(typ.obj.Data == nil) // methods must have been moved to typ.Methods
assert(obj.Type != nil) }
if !potentialMatch(e.multiples, value, obj.Type.(Type)) { for _, m := range typ.Methods {
if identicalNames(name, m.QualifiedName) {
assert(m.Type != nil)
if !potentialMatch(e.multiples, value, m.Type) {
return // name collision return // name collision
} }
} }
} }
switch typ := underlying(typ).(type) { switch t := typ.Underlying.(type) {
case *Struct: case *Struct:
// look for a matching field and collect embedded types // look for a matching field and collect embedded types
for _, f := range typ.Fields { for _, f := range t.Fields {
if f.Name == name { if identicalNames(name, f.QualifiedName) {
assert(f.Type != nil) assert(f.Type != nil)
if !potentialMatch(e.multiples, variable, f.Type) { if !potentialMatch(e.multiples, variable, f.Type) {
return // name collision return // name collision
...@@ -301,8 +304,8 @@ func lookupFieldBreadthFirst(list []embeddedType, name string) (res lookupResult ...@@ -301,8 +304,8 @@ func lookupFieldBreadthFirst(list []embeddedType, name string) (res lookupResult
case *Interface: case *Interface:
// look for a matching method // look for a matching method
for _, m := range typ.Methods { for _, m := range t.Methods {
if m.Name == name { if identicalNames(name, m.QualifiedName) {
assert(m.Type != nil) assert(m.Type != nil)
if !potentialMatch(e.multiples, value, m.Type) { if !potentialMatch(e.multiples, value, m.Type) {
return // name collision return // name collision
...@@ -348,23 +351,27 @@ func findType(list []embeddedType, typ *NamedType) *embeddedType { ...@@ -348,23 +351,27 @@ func findType(list []embeddedType, typ *NamedType) *embeddedType {
return nil return nil
} }
func lookupField(typ Type, name string) (operandMode, Type) { func lookupField(typ Type, name QualifiedName) (operandMode, Type) {
typ = deref(typ) typ = deref(typ)
if typ, ok := typ.(*NamedType); ok { if t, ok := typ.(*NamedType); ok {
if data := typ.Obj.Data; data != nil { if t.obj != nil {
if obj := data.(*ast.Scope).Lookup(name); obj != nil { assert(t.obj.Data == nil) // methods must have been moved to t.Methods
assert(obj.Type != nil) }
return value, obj.Type.(Type) for _, m := range t.Methods {
if identicalNames(name, m.QualifiedName) {
assert(m.Type != nil)
return value, m.Type
} }
} }
typ = t.Underlying
} }
switch typ := underlying(typ).(type) { switch t := typ.(type) {
case *Struct: case *Struct:
var next []embeddedType var next []embeddedType
for _, f := range typ.Fields { for _, f := range t.Fields {
if f.Name == name { if identicalNames(name, f.QualifiedName) {
return variable, f.Type return variable, f.Type
} }
if f.IsAnonymous { if f.IsAnonymous {
...@@ -380,8 +387,8 @@ func lookupField(typ Type, name string) (operandMode, Type) { ...@@ -380,8 +387,8 @@ func lookupField(typ Type, name string) (operandMode, Type) {
} }
case *Interface: case *Interface:
for _, m := range typ.Methods { for _, m := range t.Methods {
if m.Name == name { if identicalNames(name, m.QualifiedName) {
return value, m.Type return value, m.Type
} }
} }
......
...@@ -6,6 +6,8 @@ ...@@ -6,6 +6,8 @@
package types package types
import "go/ast"
func isNamed(typ Type) bool { func isNamed(typ Type) bool {
if _, ok := typ.(*Basic); ok { if _, ok := typ.(*Basic); ok {
return ok return ok
...@@ -126,11 +128,10 @@ func isIdentical(x, y Type) bool { ...@@ -126,11 +128,10 @@ func isIdentical(x, y Type) bool {
// and identical tags. Two anonymous fields are considered to have the same // and identical tags. Two anonymous fields are considered to have the same
// name. Lower-case field names from different packages are always different. // name. Lower-case field names from different packages are always different.
if y, ok := y.(*Struct); ok { if y, ok := y.(*Struct); ok {
// TODO(gri) handle structs from different packages
if len(x.Fields) == len(y.Fields) { if len(x.Fields) == len(y.Fields) {
for i, f := range x.Fields { for i, f := range x.Fields {
g := y.Fields[i] g := y.Fields[i]
if f.Name != g.Name || if !identicalNames(f.QualifiedName, g.QualifiedName) ||
!isIdentical(f.Type, g.Type) || !isIdentical(f.Type, g.Type) ||
f.Tag != g.Tag || f.Tag != g.Tag ||
f.IsAnonymous != g.IsAnonymous { f.IsAnonymous != g.IsAnonymous {
...@@ -183,13 +184,31 @@ func isIdentical(x, y Type) bool { ...@@ -183,13 +184,31 @@ func isIdentical(x, y Type) bool {
// Two named types are identical if their type names originate // Two named types are identical if their type names originate
// in the same type declaration. // in the same type declaration.
if y, ok := y.(*NamedType); ok { if y, ok := y.(*NamedType); ok {
return x.Obj == y.Obj switch {
case x.obj != nil:
return x.obj == y.obj
case x.Obj != nil:
return x.Obj == y.Obj
default:
unreachable()
}
} }
} }
return false return false
} }
// identicalNames returns true if the names a and b are equal.
func identicalNames(a, b QualifiedName) bool {
if a.Name != b.Name {
return false
}
// a.Name == b.Name
// TODO(gri) Guarantee that packages are canonicalized
// and then we can compare p == q directly.
return ast.IsExported(a.Name) || a.Pkg.Path == b.Pkg.Path
}
// identicalTypes returns true if both lists a and b have the // identicalTypes returns true if both lists a and b have the
// same length and corresponding objects have identical types. // same length and corresponding objects have identical types.
func identicalTypes(a, b []*Var) bool { func identicalTypes(a, b []*Var) bool {
...@@ -212,12 +231,13 @@ func identicalMethods(a, b []*Method) bool { ...@@ -212,12 +231,13 @@ func identicalMethods(a, b []*Method) bool {
if len(a) != len(b) { if len(a) != len(b) {
return false return false
} }
m := make(map[string]*Method) m := make(map[QualifiedName]*Method)
for _, x := range a { for _, x := range a {
m[x.Name] = x assert(m[x.QualifiedName] == nil) // method list must not have duplicate entries
m[x.QualifiedName] = x
} }
for _, y := range b { for _, y := range b {
if x := m[y.Name]; x == nil || !isIdentical(x.Type, y.Type) { if x := m[y.QualifiedName]; x == nil || !isIdentical(x.Type, y.Type) {
return false return false
} }
} }
...@@ -275,12 +295,13 @@ func defaultType(typ Type) Type { ...@@ -275,12 +295,13 @@ func defaultType(typ Type) Type {
// is missing or simply has the wrong type. // is missing or simply has the wrong type.
// //
func missingMethod(typ Type, T *Interface) (method *Method, wrongType bool) { func missingMethod(typ Type, T *Interface) (method *Method, wrongType bool) {
// TODO(gri): this needs to correctly compare method names (taking package into account)
// TODO(gri): distinguish pointer and non-pointer receivers // TODO(gri): distinguish pointer and non-pointer receivers
// an interface type implements T if it has no methods with conflicting signatures // an interface type implements T if it has no methods with conflicting signatures
// Note: This is stronger than the current spec. Should the spec require this? // Note: This is stronger than the current spec. Should the spec require this?
if ityp, _ := underlying(typ).(*Interface); ityp != nil { if ityp, _ := underlying(typ).(*Interface); ityp != nil {
for _, m := range T.Methods { for _, m := range T.Methods {
mode, sig := lookupField(ityp, m.Name) // TODO(gri) no need to go via lookupField mode, sig := lookupField(ityp, m.QualifiedName) // TODO(gri) no need to go via lookupField
if mode != invalid && !isIdentical(sig, m.Type) { if mode != invalid && !isIdentical(sig, m.Type) {
return m, true return m, true
} }
...@@ -290,7 +311,7 @@ func missingMethod(typ Type, T *Interface) (method *Method, wrongType bool) { ...@@ -290,7 +311,7 @@ func missingMethod(typ Type, T *Interface) (method *Method, wrongType bool) {
// a concrete type implements T if it implements all methods of T. // a concrete type implements T if it implements all methods of T.
for _, m := range T.Methods { for _, m := range T.Methods {
mode, sig := lookupField(typ, m.Name) mode, sig := lookupField(typ, m.QualifiedName)
if mode == invalid { if mode == invalid {
return m, false return m, false
} }
......
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package types
import (
"fmt"
"go/ast"
"strconv"
)
func (check *checker) declareObj(scope, altScope *ast.Scope, obj *ast.Object) {
alt := scope.Insert(obj)
if alt == nil && altScope != nil {
// see if there is a conflicting declaration in altScope
alt = altScope.Lookup(obj.Name)
}
if alt != nil {
prevDecl := ""
if pos := alt.Pos(); pos.IsValid() {
prevDecl = fmt.Sprintf("\n\tprevious declaration at %s", check.fset.Position(pos))
}
check.errorf(obj.Pos(), fmt.Sprintf("%s redeclared in this block%s", obj.Name, prevDecl))
}
}
func resolve(scope *ast.Scope, ident *ast.Ident) bool {
for ; scope != nil; scope = scope.Outer {
if obj := scope.Lookup(ident.Name); obj != nil {
ident.Obj = obj
return true
}
}
// handle universe scope lookups
return false
}
// TODO(gri) eventually resolve should only return *Package.
func (check *checker) resolve(importer Importer) (*ast.Package, *Package) {
// complete package scope
pkgName := ""
pkgScope := ast.NewScope(Universe)
i := 0
for _, file := range check.files {
// package names must match
switch name := file.Name.Name; {
case pkgName == "":
pkgName = name
case name != pkgName:
check.errorf(file.Package, "package %s; expected %s", name, pkgName)
continue // ignore this file
}
// keep this file
check.files[i] = file
i++
// collect top-level file objects in package scope
for _, obj := range file.Scope.Objects {
check.declareObj(pkgScope, nil, obj)
}
}
check.files = check.files[0:i]
// package global mapping of imported package ids to package objects
imports := make(map[string]*Package)
// complete file scopes with imports and resolve identifiers
for _, file := range check.files {
// build file scope by processing all imports
importErrors := false
fileScope := ast.NewScope(pkgScope)
for _, spec := range file.Imports {
if importer == nil {
importErrors = true
continue
}
path, _ := strconv.Unquote(spec.Path.Value)
pkg, err := importer(imports, path)
if err != nil {
check.errorf(spec.Path.Pos(), "could not import %s (%s)", path, err)
importErrors = true
continue
}
// TODO(gri) If a local package name != "." is provided,
// global identifier resolution could proceed even if the
// import failed. Consider adjusting the logic here a bit.
// local name overrides imported package name
name := pkg.Name
if spec.Name != nil {
name = spec.Name.Name
}
// add import to file scope
if name == "." {
// merge imported scope with file scope
// TODO(gri) Imported packages use Objects but the current
// package scope is based on ast.Scope and ast.Objects
// at the moment. Don't try to convert the imported
// objects for now. Once we get rid of ast.Object
// dependency, this loop can be enabled again.
panic("cannot handle dot-import")
/*
for _, obj := range pkg.Scope.Elems {
check.declareObj(fileScope, pkgScope, obj)
}
*/
} else if name != "_" {
// declare imported package object in file scope
// (do not re-use pkg in the file scope but create
// a new object instead; the Decl field is different
// for different files)
obj := ast.NewObj(ast.Pkg, name)
obj.Decl = spec
obj.Data = pkg
check.declareObj(fileScope, pkgScope, obj)
}
}
// resolve identifiers
if importErrors {
// don't use the universe scope without correct imports
// (objects in the universe may be shadowed by imports;
// with missing imports, identifiers might get resolved
// incorrectly to universe objects)
pkgScope.Outer = nil
}
i := 0
for _, ident := range file.Unresolved {
if !resolve(fileScope, ident) {
check.errorf(ident.Pos(), "undeclared name: %s", ident.Name)
file.Unresolved[i] = ident
i++
}
}
file.Unresolved = file.Unresolved[0:i]
pkgScope.Outer = Universe // reset outer scope
}
// TODO(gri) Once we have a pkgScope of type *Scope, only return *Package.
return &ast.Package{Name: pkgName, Scope: pkgScope}, &Package{Name: pkgName, Imports: imports}
}
...@@ -28,15 +28,19 @@ var sources = []string{ ...@@ -28,15 +28,19 @@ var sources = []string{
func f() string { func f() string {
return fmt.Sprintf("%d", g()) return fmt.Sprintf("%d", g())
} }
func g() (x int) { return }
`, `,
`package p // TODO(gri) fix this
import . "go/parser" // cannot handle dot-import at the moment
func g() Mode { return ImportsOnly }`, /*
`package p
import . "go/parser"
func g() Mode { return ImportsOnly }`,
*/
} }
var pkgnames = []string{ var pkgnames = []string{
"fmt", "fmt",
"go/parser",
"math", "math",
} }
...@@ -74,18 +78,17 @@ func ResolveQualifiedIdents(fset *token.FileSet, pkg *ast.Package) error { ...@@ -74,18 +78,17 @@ func ResolveQualifiedIdents(fset *token.FileSet, pkg *ast.Package) error {
func TestResolveQualifiedIdents(t *testing.T) { func TestResolveQualifiedIdents(t *testing.T) {
// parse package files // parse package files
fset := token.NewFileSet() fset := token.NewFileSet()
files := make(map[string]*ast.File) files := make([]*ast.File, len(sources))
for i, src := range sources { for i, src := range sources {
filename := fmt.Sprintf("file%d", i) f, err := parser.ParseFile(fset, "", src, parser.DeclarationErrors)
f, err := parser.ParseFile(fset, filename, src, parser.DeclarationErrors)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
files[filename] = f files[i] = f
} }
// resolve package AST // resolve package AST
pkg, err := ast.NewPackage(fset, files, GcImport, Universe) astpkg, pkg, err := Check(fset, files)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
...@@ -97,20 +100,22 @@ func TestResolveQualifiedIdents(t *testing.T) { ...@@ -97,20 +100,22 @@ func TestResolveQualifiedIdents(t *testing.T) {
} }
} }
// TODO(gri) fix this
// unresolved identifiers are not collected at the moment
// check that there are no top-level unresolved identifiers // check that there are no top-level unresolved identifiers
for _, f := range pkg.Files { for _, f := range astpkg.Files {
for _, x := range f.Unresolved { for _, x := range f.Unresolved {
t.Errorf("%s: unresolved global identifier %s", fset.Position(x.Pos()), x.Name) t.Errorf("%s: unresolved global identifier %s", fset.Position(x.Pos()), x.Name)
} }
} }
// resolve qualified identifiers // resolve qualified identifiers
if err := ResolveQualifiedIdents(fset, pkg); err != nil { if err := ResolveQualifiedIdents(fset, astpkg); err != nil {
t.Error(err) t.Error(err)
} }
// check that qualified identifiers are resolved // check that qualified identifiers are resolved
ast.Inspect(pkg, func(n ast.Node) bool { ast.Inspect(astpkg, func(n ast.Node) bool {
if s, ok := n.(*ast.SelectorExpr); ok { if s, ok := n.(*ast.SelectorExpr); ok {
if x, ok := s.X.(*ast.Ident); ok { if x, ok := s.X.(*ast.Ident); ok {
if x.Obj == nil { if x.Obj == nil {
......
...@@ -91,9 +91,15 @@ type Slice struct { ...@@ -91,9 +91,15 @@ type Slice struct {
Elt Type Elt Type
} }
// A QualifiedName is a name qualified with the package the declared the name.
type QualifiedName struct {
Pkg *Package // Pkg.Path == "" for current (non-imported) package
Name string // unqualified type name for anonymous fields
}
// A Field represents a field of a struct. // A Field represents a field of a struct.
type Field struct { type Field struct {
Name string // unqualified type name for anonymous fields QualifiedName
Type Type Type Type
Tag string Tag string
IsAnonymous bool IsAnonymous bool
...@@ -120,12 +126,6 @@ type Pointer struct { ...@@ -120,12 +126,6 @@ type Pointer struct {
Base Type Base Type
} }
// A Variable represents a variable (including function parameters and results).
type Var struct {
Name string
Type Type
}
// A Result represents a (multi-value) function call result. // A Result represents a (multi-value) function call result.
type Result struct { type Result struct {
implementsType implementsType
...@@ -183,9 +183,9 @@ type builtin struct { ...@@ -183,9 +183,9 @@ type builtin struct {
isStatement bool // true if the built-in is valid as an expression statement isStatement bool // true if the built-in is valid as an expression statement
} }
// A Method represents a method of an interface. // A Method represents a method.
type Method struct { type Method struct {
Name string QualifiedName
Type *Signature Type *Signature
} }
...@@ -211,8 +211,11 @@ type Chan struct { ...@@ -211,8 +211,11 @@ type Chan struct {
// A NamedType represents a named type as declared in a type declaration. // A NamedType represents a named type as declared in a type declaration.
type NamedType struct { type NamedType struct {
implementsType implementsType
Obj *ast.Object // corresponding declared object; Obj.Data.(*ast.Scope) contains methods, if any // TODO(gri) remove obj once we have moved away from ast.Objects
obj *ast.Object // corresponding declared object (current package)
Obj Object // corresponding declared object (imported package)
Underlying Type // nil if not fully declared yet; never a *NamedType Underlying Type // nil if not fully declared yet; never a *NamedType
Methods []*Method // TODO(gri) consider keeping them in sorted order
} }
// All concrete types embed implementsType which // All concrete types embed implementsType which
......
...@@ -20,7 +20,8 @@ func makePkg(t *testing.T, src string) (*ast.Package, error) { ...@@ -20,7 +20,8 @@ func makePkg(t *testing.T, src string) (*ast.Package, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return Check(fset, map[string]*ast.File{filename: file}) astpkg, _, err := Check(fset, []*ast.File{file})
return astpkg, err
} }
type testEntry struct { type testEntry struct {
...@@ -153,14 +154,14 @@ var testExprs = []testEntry{ ...@@ -153,14 +154,14 @@ var testExprs = []testEntry{
func TestExprs(t *testing.T) { func TestExprs(t *testing.T) {
for _, test := range testExprs { for _, test := range testExprs {
src := "package p; var _ = " + test.src + "; var (x, y int; s []string; f func(int, float32) int; i interface{}; t interface { foo() })" src := "package p; var _ = " + test.src + "; var (x, y int; s []string; f func(int, float32) int; i interface{}; t interface { foo() })"
pkg, err := makePkg(t, src) file, err := parser.ParseFile(fset, filename, src, parser.DeclarationErrors)
if err != nil { if err != nil {
t.Errorf("%s: %s", src, err) t.Errorf("%s: %s", src, err)
continue continue
} }
// TODO(gri) writing the code below w/o the decl variable will // TODO(gri) writing the code below w/o the decl variable will
// cause a 386 compiler error (out of fixed registers) // cause a 386 compiler error (out of fixed registers)
decl := pkg.Files[filename].Decls[0].(*ast.GenDecl) decl := file.Decls[0].(*ast.GenDecl)
expr := decl.Specs[0].(*ast.ValueSpec).Values[0] expr := decl.Specs[0].(*ast.ValueSpec).Values[0]
str := exprString(expr) str := exprString(expr)
if str != test.str { if str != test.str {
......
...@@ -12,9 +12,9 @@ import ( ...@@ -12,9 +12,9 @@ import (
) )
var ( var (
aType implementsType aType implementsType
Universe, unsafe *ast.Scope Universe *ast.Scope
Unsafe *ast.Object // package unsafe Unsafe *Package // package unsafe
) )
// Predeclared types, indexed by BasicKind. // Predeclared types, indexed by BasicKind.
...@@ -102,35 +102,31 @@ func init() { ...@@ -102,35 +102,31 @@ func init() {
Universe = ast.NewScope(nil) Universe = ast.NewScope(nil)
// unsafe package and its scope // unsafe package and its scope
unsafe = ast.NewScope(nil) Unsafe = &Package{Name: "unsafe", Scope: new(Scope)}
Unsafe = ast.NewObj(ast.Pkg, "unsafe")
Unsafe.Data = unsafe
// predeclared types // predeclared types
for _, t := range Typ { for _, t := range Typ {
def(ast.Typ, t.Name).Type = t def(ast.Typ, t.Name, t)
} }
for _, t := range aliases { for _, t := range aliases {
def(ast.Typ, t.Name).Type = t def(ast.Typ, t.Name, t)
} }
// error type // error type
{ {
err := &Method{"Error", &Signature{Results: []*Var{{"", Typ[String]}}}} err := &Method{QualifiedName{Name: "Error"}, &Signature{Results: []*Var{{Name: "", Type: Typ[String]}}}}
obj := def(ast.Typ, "error") def(ast.Typ, "error", &NamedType{Underlying: &Interface{Methods: []*Method{err}}})
obj.Type = &NamedType{Underlying: &Interface{Methods: []*Method{err}}, Obj: obj}
} }
// predeclared constants // predeclared constants
for _, t := range predeclaredConstants { for _, t := range predeclaredConstants {
obj := def(ast.Con, t.name) obj := def(ast.Con, t.name, Typ[t.kind])
obj.Type = Typ[t.kind]
obj.Data = t.val obj.Data = t.val
} }
// predeclared functions // predeclared functions
for _, f := range predeclaredFunctions { for _, f := range predeclaredFunctions {
def(ast.Fun, f.name).Type = f def(ast.Fun, f.name, f)
} }
universeIota = Universe.Lookup("iota") universeIota = Universe.Lookup("iota")
...@@ -140,19 +136,36 @@ func init() { ...@@ -140,19 +136,36 @@ func init() {
// a scope. Objects with exported names are inserted in the unsafe package // a scope. Objects with exported names are inserted in the unsafe package
// scope; other objects are inserted in the universe scope. // scope; other objects are inserted in the universe scope.
// //
func def(kind ast.ObjKind, name string) *ast.Object { func def(kind ast.ObjKind, name string, typ Type) *ast.Object {
obj := ast.NewObj(kind, name)
// insert non-internal objects into respective scope // insert non-internal objects into respective scope
if strings.Index(name, " ") < 0 { if strings.Index(name, " ") < 0 {
scope := Universe
// exported identifiers go into package unsafe // exported identifiers go into package unsafe
if ast.IsExported(name) { if ast.IsExported(name) {
scope = unsafe var obj Object
switch kind {
case ast.Typ:
obj = &TypeName{Name: name, Type: typ}
case ast.Fun:
obj = &Func{Name: name, Type: typ}
default:
unreachable()
}
if Unsafe.Scope.Insert(obj) != nil {
panic("internal error: double declaration")
}
} else {
obj := ast.NewObj(kind, name)
obj.Decl = Universe
obj.Type = typ
if typ, ok := typ.(*NamedType); ok {
typ.obj = obj
}
if Universe.Insert(obj) != nil {
panic("internal error: double declaration")
}
return obj
} }
if scope.Insert(obj) != nil {
panic("internal error: double declaration")
}
obj.Decl = scope
} }
return obj return nil
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment