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:
parent
578f24d532
commit
5a9463bda7
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
120
src/pkg/go/types/objects.go
Normal 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
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
146
src/pkg/go/types/resolve.go
Normal 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}
|
||||
}
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user