1
0
mirror of https://github.com/golang/go synced 2024-10-03 18:21:21 -06:00

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
This commit is contained in:
Robert Griesemer 2013-01-11 13:53:38 -08:00
parent 578f24d532
commit 5a9463bda7
18 changed files with 599 additions and 240 deletions

View File

@ -5,7 +5,6 @@
package main
import (
"errors"
"flag"
"fmt"
"go/ast"
@ -92,8 +91,7 @@ func parse(fset *token.FileSet, filename string, src []byte) *ast.File {
return file
}
func parseStdin(fset *token.FileSet) (files map[string]*ast.File) {
files = make(map[string]*ast.File)
func parseStdin(fset *token.FileSet) (files []*ast.File) {
src, err := ioutil.ReadAll(os.Stdin)
if err != nil {
report(err)
@ -101,13 +99,12 @@ func parseStdin(fset *token.FileSet) (files map[string]*ast.File) {
}
const filename = "<standard input>"
if file := parse(fset, filename, src); file != nil {
files[filename] = file
files = []*ast.File{file}
}
return
}
func parseFiles(fset *token.FileSet, filenames []string) (files map[string]*ast.File) {
files = make(map[string]*ast.File)
func parseFiles(fset *token.FileSet, filenames []string) (files []*ast.File) {
for _, filename := range filenames {
src, err := ioutil.ReadFile(filename)
if err != nil {
@ -115,11 +112,7 @@ func parseFiles(fset *token.FileSet, filenames []string) (files map[string]*ast.
continue
}
if file := parse(fset, filename, src); file != nil {
if files[filename] != nil {
report(errors.New(fmt.Sprintf("%q: duplicate file", filename)))
continue
}
files[filename] = file
files = append(files, file)
}
}
return
@ -169,8 +162,8 @@ func processFiles(filenames []string, allFiles bool) {
processPackage(fset, parseFiles(fset, filenames[0:i]))
}
func processPackage(fset *token.FileSet, files map[string]*ast.File) {
_, err := types.Check(fset, files)
func processPackage(fset *token.FileSet, files []*ast.File) {
_, _, err := types.Check(fset, files)
if err != nil {
report(err)
}

View File

@ -64,19 +64,16 @@ func (s *Scope) String() string {
// ----------------------------------------------------------------------------
// 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,
// constant, type, variable, function (incl. methods), or label.
//
// The Data fields contains object-specific data:
//
// Kind Data type Data value
// Pkg *Scope package scope
// Con int iota for the respective declaration
// Con != nil constant value
// Typ *Scope method scope; nil if no methods
// Kind Data type Data value
// Pkg *types.Package package scope
// Con int iota for the respective declaration
// Con != nil constant value
// Typ *Scope (used as method scope during type checking - transient)
//
type Object struct {
Kind ObjKind

View File

@ -41,9 +41,20 @@ type Context struct {
Expr func(x ast.Expr, typ Type, val interface{})
// 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.
var Default = Context{
// TODO(gri) Perhaps this should depend on GOARCH?
@ -57,11 +68,17 @@ var Default = Context{
// it returns the first error. If the context's Error handler is nil,
// 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)
}
// 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)
}

View File

@ -9,9 +9,7 @@ package types
import (
"fmt"
"go/ast"
"go/scanner"
"go/token"
"sort"
)
// enable for debugging
@ -23,6 +21,7 @@ type checker struct {
files []*ast.File
// lazily initialized
pkg *Package
pkgscope *ast.Scope
firsterr error
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) {
check.valueSpec(spec.Pos(), obj, spec.Names, init.Type, init.Values, iota)
case ast.Typ:
typ := &NamedType{Obj: obj}
typ := &NamedType{obj: obj}
obj.Type = typ // "mark" object so recursion terminates
typ.Underlying = underlying(check.typ(obj.Decl.(*ast.TypeSpec).Type, cycleOk))
// typecheck associated method signatures
@ -188,14 +187,18 @@ func (check *checker) object(obj *ast.Object, cycleOk bool) {
}
}
// typecheck method signatures
var methods []*Method
for _, obj := range scope.Objects {
mdecl := obj.Decl.(*ast.FuncDecl)
sig := check.typ(mdecl.Type, cycleOk).(*Signature)
params, _ := check.collectParams(mdecl.Recv, false)
sig.Recv = params[0] // the parser/assocMethod ensure there is exactly one parameter
obj.Type = sig
methods = append(methods, &Method{QualifiedName{check.pkg, obj.Name}, sig})
check.later(obj, sig, mdecl.Body)
}
typ.Methods = methods
obj.Data = nil // don't use obj.Data later, accidentally
}
case ast.Fun:
@ -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.
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
check := checker{
ctxt: ctxt,
fset: fset,
files: sortedFiles(files),
files: files,
initspec: make(map[*ast.ValueSpec]*ast.ValueSpec),
}
@ -394,8 +379,9 @@ func check(ctxt *Context, fset *token.FileSet, files map[string]*ast.File) (pkg
imp := ctxt.Import
if imp == nil {
// wrap GcImport to import packages only once by default.
// TODO(gri) move this into resolve
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 {
return imports[path], nil
}
@ -406,17 +392,13 @@ func check(ctxt *Context, fset *token.FileSet, files map[string]*ast.File) (pkg
return pkg, err
}
}
pkg, err = ast.NewPackage(fset, files, imp, Universe)
if err != nil {
if list, _ := err.(scanner.ErrorList); len(list) > 0 {
for _, err := range list {
check.err(err)
}
} else {
check.err(err)
}
}
check.pkgscope = pkg.Scope
astpkg, pkg = check.resolve(imp)
// Imported packages and all types refer to types.Objects,
// the current package files' AST uses ast.Objects.
// Use an ast.Scope for the current package scope.
check.pkg = pkg
check.pkgscope = astpkg.Scope
// determine missing constant initialization expressions
// and associate methods with types

View File

@ -88,18 +88,15 @@ func splitError(err error) (pos, msg string) {
return
}
func parseFiles(t *testing.T, testname string, filenames []string) (map[string]*ast.File, []error) {
files := make(map[string]*ast.File)
func parseFiles(t *testing.T, testname string, filenames []string) ([]*ast.File, []error) {
var files []*ast.File
var errlist []error
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)
if file == nil {
t.Fatalf("%s: could not parse file %s", testname, filename)
}
files[filename] = file
files = append(files, file)
if err != nil {
if list, _ := err.(scanner.ErrorList); len(list) > 0 {
for _, err := range list {
@ -121,10 +118,11 @@ var errRx = regexp.MustCompile(`^/\* *ERROR *"([^"]*)" *\*/$`)
// errMap collects the regular expressions of ERROR comments found
// 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)
for filename := range files {
for _, file := range files {
filename := fset.Position(file.Package).Filename
src, err := ioutil.ReadFile(filename)
if err != nil {
t.Fatalf("%s: could not read %s", testname, filename)
@ -236,8 +234,8 @@ func TestCheck(t *testing.T) {
// Declare builtins for testing.
// Not done in an init func to avoid an init race with
// the construction of the Universe var.
def(ast.Fun, "assert").Type = &builtin{aType, _Assert, "assert", 1, false, true}
def(ast.Fun, "trace").Type = &builtin{aType, _Trace, "trace", 0, true, true}
def(ast.Fun, "assert", &builtin{aType, _Assert, "assert", 1, false, true})
def(ast.Fun, "trace", &builtin{aType, _Trace, "trace", 0, true, true})
// For easy debugging w/o changing the testing code,
// if there is a local test file, only test that file.

View File

@ -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
if x.mode != constant {
if x.mode != constant || !isConstType(typ) {
x.mode = value
}
x.expr = conv

View File

@ -311,7 +311,16 @@ func writeType(buf *bytes.Buffer, typ Type) {
writeType(buf, t.Elt)
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:
fmt.Fprintf(buf, "<type %T>", t)

View File

@ -48,14 +48,14 @@ func (check *checker) collectParams(list *ast.FieldList, variadicOk bool) (param
obj := name.Obj
obj.Type = typ
last = obj
params = append(params, &Var{obj.Name, typ})
params = append(params, &Var{Name: obj.Name, Type: typ})
}
} else {
// anonymous parameter
obj := ast.NewObj(ast.Var, "")
obj.Type = typ
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
@ -84,7 +84,7 @@ func (check *checker) collectMethods(list *ast.FieldList) (methods []*Method) {
continue
}
for _, name := range f.Names {
methods = append(methods, &Method{name.Name, sig})
methods = append(methods, &Method{QualifiedName{check.pkg, name.Name}, sig})
}
} else {
// embedded interface
@ -137,15 +137,24 @@ func (check *checker) collectFields(list *ast.FieldList, cycleOk bool) (fields [
if len(f.Names) > 0 {
// named fields
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 {
// anonymous field
switch t := deref(typ).(type) {
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:
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:
if typ != Typ[Invalid] {
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) {
case token.AND:
// spec: "As an exception to the addressability
// 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 {
x.mode = variable
}
@ -872,29 +878,33 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle
// selector expressions.
if ident, ok := e.X.(*ast.Ident); ok {
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 {
check.errorf(e.Sel.Pos(), "cannot refer to unexported %s", sel)
goto Error
}
// simplified version of the code for *ast.Idents:
// imported objects are always fully initialized
switch exp.Kind {
case ast.Con:
assert(exp.Data != nil)
// Simplified version of the code for *ast.Idents:
// - imported packages use types.Scope and types.Objects
// - imported objects are always fully initialized
switch exp := exp.(type) {
case *Const:
assert(exp.Val != nil)
x.mode = constant
x.val = exp.Data
case ast.Typ:
x.typ = exp.Type
x.val = exp.Val
case *TypeName:
x.mode = typexpr
case ast.Var:
x.typ = exp.Type
case *Var:
x.mode = variable
case ast.Fun:
x.typ = exp.Type
case *Func:
x.mode = value
x.typ = exp.Type
default:
unreachable()
}
x.expr = e
x.typ = exp.Type.(Type)
return
}
}
@ -903,7 +913,7 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle
if x.mode == invalid {
goto Error
}
mode, typ := lookupField(x.typ, sel)
mode, typ := lookupField(x.typ, QualifiedName{check.pkg, sel})
if mode == invalid {
check.invalidOp(e.Pos(), "%s has no single field or method %s", x, sel)
goto Error
@ -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
x.mode = value
x.typ = &Signature{
Params: append([]*Var{{"", x.typ}}, sig.Params...),
Params: append([]*Var{{Type: x.typ}}, sig.Params...),
Results: sig.Results,
IsVariadic: sig.IsVariadic,
}

View File

@ -2,8 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file implements an ast.Importer for gc-generated object files.
// TODO(gri) Eventually move this into a separate package outside types.
// This file implements an Importer for gc-generated object files.
package types
@ -83,7 +82,7 @@ func FindPkg(path, srcDir string) (filename, id string) {
// be the beginning of the export data section. The filename is only used
// in error messages.
//
func GcImportData(imports map[string]*ast.Object, filename, id string, data *bufio.Reader) (pkg *ast.Object, err error) {
func GcImportData(imports map[string]*Package, filename, id string, data *bufio.Reader) (pkg *Package, err error) {
// support for gcParser error handling
defer func() {
if r := recover(); r != nil {
@ -104,7 +103,7 @@ func GcImportData(imports map[string]*ast.Object, filename, id string, data *buf
// The imports map must contains all packages already imported.
// GcImport satisfies the ast.Importer signature.
//
func GcImport(imports map[string]*ast.Object, path string) (pkg *ast.Object, err error) {
func GcImport(imports map[string]*Package, path string) (pkg *Package, err error) {
if path == "unsafe" {
return Unsafe, nil
}
@ -156,13 +155,13 @@ func GcImport(imports map[string]*ast.Object, path string) (pkg *ast.Object, err
// object/archive file and populates its scope with the results.
type gcParser struct {
scanner scanner.Scanner
tok rune // current token
lit string // literal string; only valid for Ident, Int, String tokens
id string // package id of imported package
imports map[string]*ast.Object // package id -> package object
tok rune // current token
lit string // literal string; only valid for Ident, Int, String tokens
id string // package id of imported package
imports map[string]*Package // package id -> package object
}
func (p *gcParser) init(filename, id string, src io.Reader, imports map[string]*ast.Object) {
func (p *gcParser) init(filename, id string, src io.Reader, imports map[string]*Package) {
p.scanner.Init(src)
p.scanner.Error = func(_ *scanner.Scanner, msg string) { p.error(msg) }
p.scanner.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanChars | scanner.ScanStrings | scanner.ScanComments | scanner.SkipComments
@ -187,27 +186,45 @@ func (p *gcParser) next() {
}
}
// Declare inserts a named object of the given kind in scope.
func (p *gcParser) declare(scope *ast.Scope, kind ast.ObjKind, name string) *ast.Object {
// the object may have been imported before - if it exists
// already in the respective package scope, return that object
func declConst(scope *Scope, name string) *Const {
// the constant may have been imported before - if it exists
// already in the respective scope, return that constant
if obj := scope.Lookup(name); obj != nil {
assert(obj.Kind == kind)
return obj
return obj.(*Const)
}
// otherwise create a new constant and insert it into the scope
obj := &Const{Name: name}
scope.Insert(obj)
return obj
}
// otherwise create a new object and insert it into the package scope
obj := ast.NewObj(kind, name)
if scope.Insert(obj) != nil {
unreachable() // Lookup should have found it
func declTypeName(scope *Scope, name string) *TypeName {
if obj := scope.Lookup(name); obj != nil {
return obj.(*TypeName)
}
obj := &TypeName{Name: name}
// a named type may be referred to before the underlying type
// is known - set it up
obj.Type = &NamedType{Obj: obj}
scope.Insert(obj)
return obj
}
// if the new type object is a named type it may be referred
// to before the underlying type is known - set it up
if kind == ast.Typ {
obj.Type = &NamedType{Obj: obj}
func declVar(scope *Scope, name string) *Var {
if obj := scope.Lookup(name); obj != nil {
return obj.(*Var)
}
obj := &Var{Name: name}
scope.Insert(obj)
return obj
}
func declFunc(scope *Scope, name string) *Func {
if obj := scope.Lookup(name); obj != nil {
return obj.(*Func)
}
obj := &Func{Name: name}
scope.Insert(obj)
return obj
}
@ -270,7 +287,7 @@ func (p *gcParser) expectKeyword(keyword string) {
// ImportPath = string_lit .
//
func (p *gcParser) parsePkgId() *ast.Object {
func (p *gcParser) parsePkgId() *Package {
id, err := strconv.Unquote(p.expect(scanner.String))
if err != nil {
p.error(err)
@ -288,8 +305,7 @@ func (p *gcParser) parsePkgId() *ast.Object {
pkg := p.imports[id]
if pkg == nil {
pkg = ast.NewObj(ast.Pkg, "")
pkg.Data = ast.NewScope(nil)
pkg = &Package{Scope: new(Scope)}
p.imports[id] = pkg
}
@ -315,7 +331,7 @@ func (p *gcParser) parseDotIdent() string {
// ExportedName = "@" ImportPath "." dotIdentifier .
//
func (p *gcParser) parseExportedName() (*ast.Object, string) {
func (p *gcParser) parseExportedName() (*Package, string) {
p.expect('@')
pkg := p.parsePkgId()
p.expect('.')
@ -364,7 +380,7 @@ func (p *gcParser) parseMapType() Type {
// Name = identifier | "?" | ExportedName .
//
func (p *gcParser) parseName() (name string) {
func (p *gcParser) parseName() (pkg *Package, name string) {
switch p.tok {
case scanner.Ident:
name = p.lit
@ -374,7 +390,7 @@ func (p *gcParser) parseName() (name string) {
p.next()
case '@':
// exported name prefixed with package path
_, name = p.parseExportedName()
pkg, name = p.parseExportedName()
default:
p.error("name expected")
}
@ -385,7 +401,7 @@ func (p *gcParser) parseName() (name string) {
//
func (p *gcParser) parseField() *Field {
var f Field
f.Name = p.parseName()
f.Pkg, f.Name = p.parseName()
f.Type = p.parseType()
if p.tok == scanner.String {
f.Tag = p.expect(scanner.String)
@ -393,7 +409,7 @@ func (p *gcParser) parseField() *Field {
if f.Name == "" {
// anonymous field - typ must be T or *T and T must be a type name
if typ, ok := deref(f.Type).(*NamedType); ok && typ.Obj != nil {
f.Name = typ.Obj.Name
f.Name = typ.Obj.GetName()
f.IsAnonymous = true
} else {
p.errorf("anonymous field expected")
@ -424,7 +440,7 @@ func (p *gcParser) parseStructType() Type {
// Parameter = ( identifier | "?" ) [ "..." ] Type [ string_lit ] .
//
func (p *gcParser) parseParameter() (par *Var, isVariadic bool) {
name := p.parseName()
_, name := p.parseName()
if name == "" {
name = "_" // cannot access unnamed identifiers
}
@ -437,7 +453,7 @@ func (p *gcParser) parseParameter() (par *Var, isVariadic bool) {
if p.tok == scanner.String {
p.next()
}
par = &Var{name, typ}
par = &Var{Name: name, Type: typ}
return
}
@ -475,7 +491,7 @@ func (p *gcParser) parseSignature() *Signature {
switch p.tok {
case scanner.Ident, '[', '*', '<', '@':
// single, unnamed result
results = []*Var{{"", p.parseType()}}
results = []*Var{{Type: p.parseType()}}
case '(':
// named or multiple result(s)
var variadic bool
@ -505,9 +521,9 @@ func (p *gcParser) parseInterfaceType() Type {
if len(methods) > 0 {
p.expect(';')
}
name := p.parseName()
pkg, name := p.parseName()
typ := p.parseSignature()
methods = append(methods, &Method{name, typ})
methods = append(methods, &Method{QualifiedName{pkg, name}, typ})
}
p.expect('}')
@ -566,7 +582,7 @@ func (p *gcParser) parseType() Type {
case '@':
// TypeName
pkg, name := p.parseExportedName()
return p.declare(pkg.Data.(*ast.Scope), ast.Typ, name).Type.(Type)
return declTypeName(pkg.Scope, name).Type
case '[':
p.next() // look ahead
if p.tok == ']' {
@ -674,7 +690,7 @@ func (p *gcParser) parseNumber() (x operand) {
func (p *gcParser) parseConstDecl() {
p.expectKeyword("const")
pkg, name := p.parseExportedName()
obj := p.declare(pkg.Data.(*ast.Scope), ast.Con, name)
obj := declConst(pkg.Scope, name)
var x operand
if p.tok != '=' {
obj.Type = p.parseType()
@ -732,7 +748,7 @@ func (p *gcParser) parseConstDecl() {
obj.Type = x.typ
}
assert(x.val != nil)
obj.Data = x.val
obj.Val = x.val
}
// TypeDecl = "type" ExportedName Type .
@ -740,7 +756,7 @@ func (p *gcParser) parseConstDecl() {
func (p *gcParser) parseTypeDecl() {
p.expectKeyword("type")
pkg, name := p.parseExportedName()
obj := p.declare(pkg.Data.(*ast.Scope), ast.Typ, name)
obj := declTypeName(pkg.Scope, name)
// The type object may have been imported before and thus already
// have a type associated with it. We still need to parse the type
@ -759,17 +775,15 @@ func (p *gcParser) parseTypeDecl() {
func (p *gcParser) parseVarDecl() {
p.expectKeyword("var")
pkg, name := p.parseExportedName()
obj := p.declare(pkg.Data.(*ast.Scope), ast.Var, name)
obj := declVar(pkg.Scope, name)
obj.Type = p.parseType()
}
// Func = Signature [ Body ] .
// Body = "{" ... "}" .
//
func (p *gcParser) parseFunc(scope *ast.Scope, name string) *Signature {
obj := p.declare(scope, ast.Fun, name)
func (p *gcParser) parseFunc() *Signature {
sig := p.parseSignature()
obj.Type = sig
if p.tok == '{' {
p.next()
for i := 1; i > 0; p.next() {
@ -794,25 +808,26 @@ func (p *gcParser) parseMethodDecl() {
p.expect(')')
// determine receiver base type object
typ := recv.Type.(Type)
typ := recv.Type
if ptr, ok := typ.(*Pointer); ok {
typ = ptr.Base
}
obj := typ.(*NamedType).Obj
base := typ.(*NamedType)
// determine base type scope
var scope *ast.Scope
if obj.Data != nil {
scope = obj.Data.(*ast.Scope)
} else {
scope = ast.NewScope(nil)
obj.Data = scope
}
// declare method in base type scope
name := p.parseName() // unexported method names in imports are qualified with their package.
sig := p.parseFunc(scope, name)
// parse method name, signature, and possibly inlined body
pkg, name := p.parseName() // unexported method names in imports are qualified with their package.
sig := p.parseFunc()
sig.Recv = recv
// add method to type unless type was imported before
// and method exists already
// TODO(gri) investigate if this can be avoided
for _, m := range base.Methods {
if m.Name == name {
return // method was added before
}
}
base.Methods = append(base.Methods, &Method{QualifiedName{pkg, name}, sig})
}
// FuncDecl = "func" ExportedName Func .
@ -820,7 +835,8 @@ func (p *gcParser) parseMethodDecl() {
func (p *gcParser) parseFuncDecl() {
// "func" already consumed
pkg, name := p.parseExportedName()
p.parseFunc(pkg.Data.(*ast.Scope), name)
typ := p.parseFunc()
declFunc(pkg.Scope, name).Type = typ
}
// Decl = [ ImportDecl | ConstDecl | TypeDecl | VarDecl | FuncDecl | MethodDecl ] "\n" .
@ -852,7 +868,7 @@ func (p *gcParser) parseDecl() {
// Export = "PackageClause { Decl } "$$" .
// PackageClause = "package" identifier [ "safe" ] "\n" .
//
func (p *gcParser) parseExport() *ast.Object {
func (p *gcParser) parseExport() *Package {
p.expectKeyword("package")
name := p.expect(scanner.Ident)
if p.tok != '\n' {
@ -865,8 +881,7 @@ func (p *gcParser) parseExport() *ast.Object {
pkg := p.imports[p.id]
if pkg == nil {
pkg = ast.NewObj(ast.Pkg, name)
pkg.Data = ast.NewScope(nil)
pkg = &Package{Name: name, Scope: new(Scope)}
p.imports[p.id] = pkg
}

View File

@ -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
// 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 {
t0 := time.Now()
@ -147,12 +147,34 @@ func TestGcImportedTypes(t *testing.T) {
continue
}
obj := pkg.Data.(*ast.Scope).Lookup(objName)
if obj.Kind != test.kind {
t.Errorf("%s: got kind = %q; want %q", test.name, obj.Kind, test.kind)
obj := pkg.Scope.Lookup(objName)
// 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)
}
}

120
src/pkg/go/types/objects.go Normal file
View File

@ -0,0 +1,120 @@
// 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
}

View File

@ -222,11 +222,11 @@ type embeddedType struct {
}
// 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
// the field mode and type; otherwise the result mode is invalid.
// or method) of the given name from the given package. If such a field is found,
// 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)
//
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 := make(map[*NamedType]bool)
@ -265,20 +265,23 @@ func lookupFieldBreadthFirst(list []embeddedType, name string) (res lookupResult
visited[typ] = true
// look for a matching attached method
if data := typ.Obj.Data; data != nil {
if obj := data.(*ast.Scope).Lookup(name); obj != nil {
assert(obj.Type != nil)
if !potentialMatch(e.multiples, value, obj.Type.(Type)) {
if typ.obj != nil {
assert(typ.obj.Data == nil) // methods must have been moved to typ.Methods
}
for _, m := range typ.Methods {
if identicalNames(name, m.QualifiedName) {
assert(m.Type != nil)
if !potentialMatch(e.multiples, value, m.Type) {
return // name collision
}
}
}
switch typ := underlying(typ).(type) {
switch t := typ.Underlying.(type) {
case *Struct:
// look for a matching field and collect embedded types
for _, f := range typ.Fields {
if f.Name == name {
for _, f := range t.Fields {
if identicalNames(name, f.QualifiedName) {
assert(f.Type != nil)
if !potentialMatch(e.multiples, variable, f.Type) {
return // name collision
@ -301,8 +304,8 @@ func lookupFieldBreadthFirst(list []embeddedType, name string) (res lookupResult
case *Interface:
// look for a matching method
for _, m := range typ.Methods {
if m.Name == name {
for _, m := range t.Methods {
if identicalNames(name, m.QualifiedName) {
assert(m.Type != nil)
if !potentialMatch(e.multiples, value, m.Type) {
return // name collision
@ -348,23 +351,27 @@ func findType(list []embeddedType, typ *NamedType) *embeddedType {
return nil
}
func lookupField(typ Type, name string) (operandMode, Type) {
func lookupField(typ Type, name QualifiedName) (operandMode, Type) {
typ = deref(typ)
if typ, ok := typ.(*NamedType); ok {
if data := typ.Obj.Data; data != nil {
if obj := data.(*ast.Scope).Lookup(name); obj != nil {
assert(obj.Type != nil)
return value, obj.Type.(Type)
if t, ok := typ.(*NamedType); ok {
if t.obj != nil {
assert(t.obj.Data == nil) // methods must have been moved to t.Methods
}
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:
var next []embeddedType
for _, f := range typ.Fields {
if f.Name == name {
for _, f := range t.Fields {
if identicalNames(name, f.QualifiedName) {
return variable, f.Type
}
if f.IsAnonymous {
@ -380,8 +387,8 @@ func lookupField(typ Type, name string) (operandMode, Type) {
}
case *Interface:
for _, m := range typ.Methods {
if m.Name == name {
for _, m := range t.Methods {
if identicalNames(name, m.QualifiedName) {
return value, m.Type
}
}

View File

@ -6,6 +6,8 @@
package types
import "go/ast"
func isNamed(typ Type) bool {
if _, ok := typ.(*Basic); ok {
return ok
@ -126,11 +128,10 @@ func isIdentical(x, y Type) bool {
// and identical tags. Two anonymous fields are considered to have the same
// name. Lower-case field names from different packages are always different.
if y, ok := y.(*Struct); ok {
// TODO(gri) handle structs from different packages
if len(x.Fields) == len(y.Fields) {
for i, f := range x.Fields {
g := y.Fields[i]
if f.Name != g.Name ||
if !identicalNames(f.QualifiedName, g.QualifiedName) ||
!isIdentical(f.Type, g.Type) ||
f.Tag != g.Tag ||
f.IsAnonymous != g.IsAnonymous {
@ -183,13 +184,31 @@ func isIdentical(x, y Type) bool {
// Two named types are identical if their type names originate
// in the same type declaration.
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
}
// 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
// same length and corresponding objects have identical types.
func identicalTypes(a, b []*Var) bool {
@ -212,12 +231,13 @@ func identicalMethods(a, b []*Method) bool {
if len(a) != len(b) {
return false
}
m := make(map[string]*Method)
m := make(map[QualifiedName]*Method)
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 {
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
}
}
@ -275,12 +295,13 @@ func defaultType(typ Type) Type {
// is missing or simply has the wrong type.
//
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
// 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?
if ityp, _ := underlying(typ).(*Interface); ityp != nil {
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) {
return m, true
}
@ -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.
for _, m := range T.Methods {
mode, sig := lookupField(typ, m.Name)
mode, sig := lookupField(typ, m.QualifiedName)
if mode == invalid {
return m, false
}

146
src/pkg/go/types/resolve.go Normal file
View File

@ -0,0 +1,146 @@
// 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}
}

View File

@ -28,15 +28,19 @@ var sources = []string{
func f() string {
return fmt.Sprintf("%d", g())
}
func g() (x int) { return }
`,
`package p
import . "go/parser"
func g() Mode { return ImportsOnly }`,
// TODO(gri) fix this
// cannot handle dot-import at the moment
/*
`package p
import . "go/parser"
func g() Mode { return ImportsOnly }`,
*/
}
var pkgnames = []string{
"fmt",
"go/parser",
"math",
}
@ -74,18 +78,17 @@ func ResolveQualifiedIdents(fset *token.FileSet, pkg *ast.Package) error {
func TestResolveQualifiedIdents(t *testing.T) {
// parse package files
fset := token.NewFileSet()
files := make(map[string]*ast.File)
files := make([]*ast.File, len(sources))
for i, src := range sources {
filename := fmt.Sprintf("file%d", i)
f, err := parser.ParseFile(fset, filename, src, parser.DeclarationErrors)
f, err := parser.ParseFile(fset, "", src, parser.DeclarationErrors)
if err != nil {
t.Fatal(err)
}
files[filename] = f
files[i] = f
}
// resolve package AST
pkg, err := ast.NewPackage(fset, files, GcImport, Universe)
astpkg, pkg, err := Check(fset, files)
if err != nil {
t.Fatal(err)
}
@ -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
for _, f := range pkg.Files {
for _, f := range astpkg.Files {
for _, x := range f.Unresolved {
t.Errorf("%s: unresolved global identifier %s", fset.Position(x.Pos()), x.Name)
}
}
// resolve qualified identifiers
if err := ResolveQualifiedIdents(fset, pkg); err != nil {
if err := ResolveQualifiedIdents(fset, astpkg); err != nil {
t.Error(err)
}
// 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 x, ok := s.X.(*ast.Ident); ok {
if x.Obj == nil {

View File

@ -91,9 +91,15 @@ type Slice struct {
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.
type Field struct {
Name string // unqualified type name for anonymous fields
QualifiedName
Type Type
Tag string
IsAnonymous bool
@ -120,12 +126,6 @@ type Pointer struct {
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.
type Result struct {
implementsType
@ -183,9 +183,9 @@ type builtin struct {
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 {
Name string
QualifiedName
Type *Signature
}
@ -211,8 +211,11 @@ type Chan struct {
// A NamedType represents a named type as declared in a type declaration.
type NamedType struct {
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
Methods []*Method // TODO(gri) consider keeping them in sorted order
}
// All concrete types embed implementsType which

View File

@ -20,7 +20,8 @@ func makePkg(t *testing.T, src string) (*ast.Package, error) {
if err != nil {
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 {
@ -153,14 +154,14 @@ var testExprs = []testEntry{
func TestExprs(t *testing.T) {
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() })"
pkg, err := makePkg(t, src)
file, err := parser.ParseFile(fset, filename, src, parser.DeclarationErrors)
if err != nil {
t.Errorf("%s: %s", src, err)
continue
}
// TODO(gri) writing the code below w/o the decl variable will
// 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]
str := exprString(expr)
if str != test.str {

View File

@ -12,9 +12,9 @@ import (
)
var (
aType implementsType
Universe, unsafe *ast.Scope
Unsafe *ast.Object // package unsafe
aType implementsType
Universe *ast.Scope
Unsafe *Package // package unsafe
)
// Predeclared types, indexed by BasicKind.
@ -102,35 +102,31 @@ func init() {
Universe = ast.NewScope(nil)
// unsafe package and its scope
unsafe = ast.NewScope(nil)
Unsafe = ast.NewObj(ast.Pkg, "unsafe")
Unsafe.Data = unsafe
Unsafe = &Package{Name: "unsafe", Scope: new(Scope)}
// predeclared types
for _, t := range Typ {
def(ast.Typ, t.Name).Type = t
def(ast.Typ, t.Name, t)
}
for _, t := range aliases {
def(ast.Typ, t.Name).Type = t
def(ast.Typ, t.Name, t)
}
// error type
{
err := &Method{"Error", &Signature{Results: []*Var{{"", Typ[String]}}}}
obj := def(ast.Typ, "error")
obj.Type = &NamedType{Underlying: &Interface{Methods: []*Method{err}}, Obj: obj}
err := &Method{QualifiedName{Name: "Error"}, &Signature{Results: []*Var{{Name: "", Type: Typ[String]}}}}
def(ast.Typ, "error", &NamedType{Underlying: &Interface{Methods: []*Method{err}}})
}
// predeclared constants
for _, t := range predeclaredConstants {
obj := def(ast.Con, t.name)
obj.Type = Typ[t.kind]
obj := def(ast.Con, t.name, Typ[t.kind])
obj.Data = t.val
}
// predeclared functions
for _, f := range predeclaredFunctions {
def(ast.Fun, f.name).Type = f
def(ast.Fun, f.name, f)
}
universeIota = Universe.Lookup("iota")
@ -140,19 +136,36 @@ func init() {
// a scope. Objects with exported names are inserted in the unsafe package
// scope; other objects are inserted in the universe scope.
//
func def(kind ast.ObjKind, name string) *ast.Object {
obj := ast.NewObj(kind, name)
func def(kind ast.ObjKind, name string, typ Type) *ast.Object {
// insert non-internal objects into respective scope
if strings.Index(name, " ") < 0 {
scope := Universe
// exported identifiers go into package unsafe
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
}