1
0
mirror of https://github.com/golang/go synced 2024-11-18 02:54:47 -07:00

go.tools/go/types: replace ObjSet with improved Scope

- First step towards unified use of scopes. Will enable
  further simplifications.

- Removed various ForEach iterators in favor of the existing
  accessor methods, for a thinner API.

- Renamed outer/Outer to parent/Parent for scopes.

- Removed check.lookup in favor of Scope.LookupParent.

R=adonovan
CC=golang-dev
https://golang.org/cl/9862044
This commit is contained in:
Robert Griesemer 2013-05-30 21:58:14 -07:00
parent 291b2c84b6
commit 3cad037e2f
19 changed files with 315 additions and 314 deletions

View File

@ -48,14 +48,6 @@ type checker struct {
pos []token.Pos // stack of expr positions; debugging support, used if trace is set
}
func (check *checker) openScope() {
check.topScope = &Scope{Outer: check.topScope}
}
func (check *checker) closeScope() {
check.topScope = check.topScope.Outer
}
func (check *checker) callIdent(id *ast.Ident, obj Object) {
if f := check.ctxt.Ident; f != nil {
f(id, obj)
@ -68,18 +60,6 @@ func (check *checker) callImplicitObj(node ast.Node, obj Object) {
}
}
// lookup returns the Object denoted by ident by looking up the
// identifier in the scope chain, starting with the top-most scope.
// If no object is found, lookup returns nil.
func (check *checker) lookup(ident *ast.Ident) Object {
for scope := check.topScope; scope != nil; scope = scope.Outer {
if obj := scope.Lookup(ident.Name); obj != nil {
return obj
}
}
return nil
}
type function struct {
file *Scope // only valid if resolve is set
obj *Func // for debugging/tracing only
@ -104,7 +84,7 @@ type bailout struct{}
func check(ctxt *Context, path string, fset *token.FileSet, files ...*ast.File) (pkg *Package, err error) {
pkg = &Package{
path: path,
scope: &Scope{Outer: Universe},
scope: NewScope(Universe),
imports: make(map[string]*Package),
}

View File

@ -117,10 +117,11 @@ func (check *checker) collectParams(scope *Scope, list *ast.FieldList, variadicO
return
}
func (check *checker) collectMethods(scope *Scope, list *ast.FieldList) (methods ObjSet) {
func (check *checker) collectMethods(scope *Scope, list *ast.FieldList) *Scope {
if list == nil {
return
return nil
}
methods := NewScope(nil)
for _, f := range list.List {
typ := check.typ(f.Type, len(f.Names) > 0) // cycles are not ok for embedded interfaces
// the parser ensures that f.Tag is nil and we don't
@ -164,7 +165,7 @@ func (check *checker) collectMethods(scope *Scope, list *ast.FieldList) (methods
}
}
}
return
return methods
}
func (check *checker) tag(t *ast.BasicLit) string {
@ -1045,15 +1046,12 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle
goto Error // error was reported before
case *ast.Ident:
obj := check.lookup(e)
obj := check.topScope.LookupParent(e.Name)
check.callIdent(e, obj)
if obj == nil {
if e.Name == "_" {
check.invalidOp(e.Pos(), "cannot use _ as value or type")
check.errorf(e.Pos(), "cannot use _ as value or type")
} else {
// TODO(gri) anonymous function result parameters are
// not declared - this causes trouble when
// type-checking return statements
check.errorf(e.Pos(), "undeclared name: %s", e.Name)
}
goto Error // error was reported before
@ -1279,9 +1277,9 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle
// can only appear in qualified identifiers which are mapped to
// selector expressions.
if ident, ok := e.X.(*ast.Ident); ok {
if pkg, ok := check.lookup(ident).(*Package); ok {
if pkg, ok := check.topScope.LookupParent(ident.Name).(*Package); ok {
check.callIdent(ident, pkg)
exp := pkg.scope.Lookup(sel)
exp := pkg.scope.Lookup(nil, sel)
if exp == nil {
check.errorf(e.Pos(), "%s not declared by package %s", sel, ident)
goto Error
@ -1693,20 +1691,20 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle
x.mode = typexpr
case *ast.StructType:
scope := &Scope{Outer: check.topScope}
scope := NewScope(check.topScope)
fields, tags := check.collectFields(scope, e.Fields, cycleOk)
x.mode = typexpr
x.typ = &Struct{scope: scope, fields: fields, tags: tags}
case *ast.FuncType:
scope := &Scope{Outer: check.topScope}
scope := NewScope(check.topScope)
params, isVariadic := check.collectParams(scope, e.Params, true)
results, _ := check.collectParams(scope, e.Results, false)
x.mode = typexpr
x.typ = &Signature{scope: scope, recv: nil, params: NewTuple(params...), results: NewTuple(results...), isVariadic: isVariadic}
case *ast.InterfaceType:
scope := &Scope{Outer: check.topScope}
scope := NewScope(check.topScope)
x.mode = typexpr
x.typ = &Interface{methods: check.collectMethods(scope, e.Methods)}

View File

@ -202,7 +202,7 @@ func declConst(pkg *Package, name string) *Const {
// the constant may have been imported before - if it exists
// already in the respective scope, return that constant
scope := pkg.scope
if obj := scope.Lookup(name); obj != nil {
if obj := scope.Lookup(nil, name); obj != nil {
return obj.(*Const)
}
// otherwise create a new constant and insert it into the scope
@ -213,7 +213,7 @@ func declConst(pkg *Package, name string) *Const {
func declTypeName(pkg *Package, name string) *TypeName {
scope := pkg.scope
if obj := scope.Lookup(name); obj != nil {
if obj := scope.Lookup(nil, name); obj != nil {
return obj.(*TypeName)
}
obj := &TypeName{pkg: pkg, name: name}
@ -226,7 +226,7 @@ func declTypeName(pkg *Package, name string) *TypeName {
func declVar(pkg *Package, name string) *Var {
scope := pkg.scope
if obj := scope.Lookup(name); obj != nil {
if obj := scope.Lookup(nil, name); obj != nil {
return obj.(*Var)
}
obj := &Var{pkg: pkg, name: name}
@ -236,7 +236,7 @@ func declVar(pkg *Package, name string) *Var {
func declFunc(pkg *Package, name string) *Func {
scope := pkg.scope
if obj := scope.Lookup(name); obj != nil {
if obj := scope.Lookup(nil, name); obj != nil {
return obj.(*Func)
}
obj := &Func{pkg: pkg, name: name}
@ -360,7 +360,7 @@ func (p *gcParser) getPkg(id, name string) *Package {
}
pkg := p.imports[id]
if pkg == nil && name != "" {
pkg = &Package{name: name, path: id, scope: new(Scope)}
pkg = &Package{name: name, path: id, scope: NewScope(nil)}
p.imports[id] = pkg
}
return pkg
@ -385,7 +385,7 @@ func (p *gcParser) parseExportedName() (pkg *Package, name string) {
//
func (p *gcParser) parseBasicType() Type {
id := p.expect(scanner.Ident)
obj := Universe.Lookup(id)
obj := Universe.Lookup(nil, id)
if obj, ok := obj.(*TypeName); ok {
return obj.typ
}
@ -580,7 +580,7 @@ func (p *gcParser) parseSignature() *Signature {
// visible in the export data.
//
func (p *gcParser) parseInterfaceType() Type {
var methods ObjSet
var methods *Scope // lazily allocated
p.expectKeyword("interface")
p.expect('{')
@ -590,6 +590,9 @@ func (p *gcParser) parseInterfaceType() Type {
}
pkg, name := p.parseName(true)
sig := p.parseSignature()
if methods == nil {
methods = NewScope(nil)
}
if alt := methods.Insert(&Func{token.NoPos, pkg, nil, name, sig, nil}); alt != nil {
p.errorf("multiple methods named %s.%s", alt.Pkg().name, alt.Name())
}
@ -879,6 +882,9 @@ func (p *gcParser) parseMethodDecl() {
// add method to type unless type was imported before
// and method exists already
if base.methods == nil {
base.methods = NewScope(nil)
}
base.methods.Insert(&Func{token.NoPos, pkg, nil, name, sig, nil})
}

View File

@ -146,7 +146,7 @@ func TestGcImportedTypes(t *testing.T) {
continue
}
obj := pkg.scope.Lookup(objName)
obj := pkg.scope.Lookup(nil, objName)
// TODO(gri) should define an accessor on Object
var kind ast.ObjKind

View File

@ -19,14 +19,14 @@ import (
// All objects implement the Object interface.
//
type Object interface {
Pkg() *Package // nil for objects in the Universe scope and labels
Outer() *Scope // the scope in which this object is declared
Pkg() *Package // nil for objects in the Universe scope and labels
Parent() *Scope // the scope in which this object is declared
Name() string
Type() Type
Pos() token.Pos // position of object identifier in declaration
// TODO(gri) provide String method!
setOuter(*Scope)
setParent(*Scope)
}
// A Package represents the contents (objects) of a Go package.
@ -34,7 +34,7 @@ type Package struct {
pos token.Pos // position of package import path or local package identifier, if present
name string
path string // import path, "" for current (non-imported) package
outer *Scope
parent *Scope
scope *Scope // package-level scope
imports map[string]*Package // map of import paths to imported packages
complete bool // if set, this package was imported completely
@ -45,7 +45,7 @@ func NewPackage(path, name string) *Package {
}
func (obj *Package) Pkg() *Package { return obj }
func (obj *Package) Outer() *Scope { return obj.outer }
func (obj *Package) Parent() *Scope { return obj.parent }
func (obj *Package) Scope() *Scope { return obj.scope }
func (obj *Package) Name() string { return obj.name }
func (obj *Package) Type() Type { return Typ[Invalid] }
@ -53,56 +53,56 @@ func (obj *Package) Pos() token.Pos { return obj.pos }
func (obj *Package) Path() string { return obj.path }
func (obj *Package) Imports() map[string]*Package { return obj.imports }
func (obj *Package) Complete() bool { return obj.complete }
func (obj *Package) setOuter(*Scope) { /* don't do anything - this is the package's scope */
func (obj *Package) setParent(*Scope) { /* don't do anything - this is the package's scope */
}
// A Const represents a declared constant.
type Const struct {
pos token.Pos // position of identifier in constant declaration
pkg *Package
outer *Scope
name string
typ Type
val exact.Value
pos token.Pos // position of identifier in constant declaration
pkg *Package
parent *Scope
name string
typ Type
val exact.Value
visited bool // for initialization cycle detection
}
func (obj *Const) Pkg() *Package { return obj.pkg }
func (obj *Const) Outer() *Scope { return obj.outer }
func (obj *Const) Name() string { return obj.name }
func (obj *Const) Type() Type { return obj.typ }
func (obj *Const) Pos() token.Pos { return obj.pos }
func (obj *Const) Val() exact.Value { return obj.val }
func (obj *Const) setOuter(s *Scope) { obj.outer = s }
func (obj *Const) Pkg() *Package { return obj.pkg }
func (obj *Const) Parent() *Scope { return obj.parent }
func (obj *Const) Name() string { return obj.name }
func (obj *Const) Type() Type { return obj.typ }
func (obj *Const) Pos() token.Pos { return obj.pos }
func (obj *Const) Val() exact.Value { return obj.val }
func (obj *Const) setParent(s *Scope) { obj.parent = s }
// A TypeName represents a declared type.
type TypeName struct {
pos token.Pos // position of identifier in type declaration
pkg *Package
outer *Scope
name string
typ Type // *Named or *Basic
pos token.Pos // position of identifier in type declaration
pkg *Package
parent *Scope
name string
typ Type // *Named or *Basic
}
func NewTypeName(pkg *Package, name string, typ Type) *TypeName {
return &TypeName{token.NoPos, pkg, nil, name, typ}
}
func (obj *TypeName) Pkg() *Package { return obj.pkg }
func (obj *TypeName) Outer() *Scope { return obj.outer }
func (obj *TypeName) Name() string { return obj.name }
func (obj *TypeName) Type() Type { return obj.typ }
func (obj *TypeName) Pos() token.Pos { return obj.pos }
func (obj *TypeName) setOuter(s *Scope) { obj.outer = s }
func (obj *TypeName) Pkg() *Package { return obj.pkg }
func (obj *TypeName) Parent() *Scope { return obj.parent }
func (obj *TypeName) Name() string { return obj.name }
func (obj *TypeName) Type() Type { return obj.typ }
func (obj *TypeName) Pos() token.Pos { return obj.pos }
func (obj *TypeName) setParent(s *Scope) { obj.parent = s }
// A Variable represents a declared variable (including function parameters and results).
type Var struct {
pos token.Pos // position of identifier in variable declaration
pkg *Package // nil for parameters
outer *Scope
name string
typ Type
pos token.Pos // position of identifier in variable declaration
pkg *Package // nil for parameters
parent *Scope
name string
typ Type
visited bool // for initialization cycle detection
}
@ -111,41 +111,41 @@ func NewVar(pkg *Package, name string, typ Type) *Var {
return &Var{token.NoPos, pkg, nil, name, typ, false}
}
func (obj *Var) Pkg() *Package { return obj.pkg }
func (obj *Var) Outer() *Scope { return obj.outer }
func (obj *Var) Name() string { return obj.name }
func (obj *Var) Type() Type { return obj.typ }
func (obj *Var) Pos() token.Pos { return obj.pos }
func (obj *Var) setOuter(s *Scope) { obj.outer = s }
func (obj *Var) Pkg() *Package { return obj.pkg }
func (obj *Var) Parent() *Scope { return obj.parent }
func (obj *Var) Name() string { return obj.name }
func (obj *Var) Type() Type { return obj.typ }
func (obj *Var) Pos() token.Pos { return obj.pos }
func (obj *Var) setParent(s *Scope) { obj.parent = s }
// A Func represents a declared function.
type Func struct {
pos token.Pos
pkg *Package
outer *Scope
name string
typ Type // *Signature or *Builtin
pos token.Pos
pkg *Package
parent *Scope
name string
typ Type // *Signature or *Builtin
decl *ast.FuncDecl // TODO(gri) can we get rid of this field?
}
func (obj *Func) Pkg() *Package { return obj.pkg }
func (obj *Func) Outer() *Scope { return obj.outer }
func (obj *Func) Name() string { return obj.name }
func (obj *Func) Type() Type { return obj.typ }
func (obj *Func) Pos() token.Pos { return obj.pos }
func (obj *Func) setOuter(s *Scope) { obj.outer = s }
func (obj *Func) Pkg() *Package { return obj.pkg }
func (obj *Func) Parent() *Scope { return obj.parent }
func (obj *Func) Name() string { return obj.name }
func (obj *Func) Type() Type { return obj.typ }
func (obj *Func) Pos() token.Pos { return obj.pos }
func (obj *Func) setParent(s *Scope) { obj.parent = s }
// A Label represents a declared label.
type Label struct {
pos token.Pos
outer *Scope
name string
pos token.Pos
parent *Scope
name string
}
func (obj *Label) Pkg() *Package { return nil }
func (obj *Label) Outer() *Scope { return obj.outer }
func (obj *Label) Name() string { return obj.name }
func (obj *Label) Type() Type { return nil }
func (obj *Label) Pos() token.Pos { return obj.pos }
func (obj *Label) setOuter(s *Scope) { obj.outer = s }
func (obj *Label) Pkg() *Package { return nil }
func (obj *Label) Parent() *Scope { return obj.parent }
func (obj *Label) Name() string { return obj.name }
func (obj *Label) Type() Type { return nil }
func (obj *Label) Pos() token.Pos { return obj.pos }
func (obj *Label) setParent(s *Scope) { obj.parent = s }

View File

@ -1,64 +0,0 @@
// 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 (
"bytes"
"fmt"
"go/ast"
)
// An ObjSet maintains a set of objects identified by
// their name and package that declares them.
//
type ObjSet struct {
entries []Object // set entries in insertion order
}
// Lookup returns the object with the given package and name
// if it is found in ObjSet s, otherwise it returns nil.
//
func (s *ObjSet) Lookup(pkg *Package, name string) Object {
for _, obj := range s.entries {
// spec:
// "Two identifiers are different if they are spelled differently,
// or if they appear in different packages and are not exported.
// Otherwise, they are the same."
if obj.Name() == name && (ast.IsExported(name) || obj.Pkg().path == pkg.path) {
return obj
}
}
return nil
}
// Insert attempts to insert an object obj into ObjSet s.
// If s already contains an object from the same package
// with the same name, Insert leaves s unchanged and returns
// that object. Otherwise it inserts obj and returns nil.
//
func (s *ObjSet) Insert(obj Object) Object {
pkg := obj.Pkg()
name := obj.Name()
assert(obj.Type() != nil)
if alt := s.Lookup(pkg, name); alt != nil {
return alt
}
s.entries = append(s.entries, obj)
return nil
}
// Debugging support
func (s *ObjSet) String() string {
var buf bytes.Buffer
fmt.Fprintf(&buf, "ObjSet %p {", s)
if s != nil && len(s.entries) > 0 {
fmt.Fprintln(&buf)
for _, obj := range s.entries {
fmt.Fprintf(&buf, "\t%s.%s\t%T\n", obj.Pkg().path, obj.Name(), obj)
}
}
fmt.Fprintf(&buf, "}\n")
return buf.String()
}

View File

@ -208,11 +208,16 @@ func identicalTypes(a, b *Tuple) bool {
// identicalMethods returns true if both object sets a and b have the
// same length and corresponding methods have identical types.
// TODO(gri) make this more efficient
func identicalMethods(a, b ObjSet) bool {
if len(a.entries) != len(b.entries) {
// TODO(gri) make this more efficient (e.g., sort them on completion)
func identicalMethods(a, b *Scope) bool {
if a.NumEntries() != b.NumEntries() {
return false
}
if a.IsEmpty() {
return true
}
m := make(map[string]*Func)
for _, obj := range a.entries {
x := obj.(*Func)
@ -227,6 +232,7 @@ func identicalMethods(a, b ObjSet) bool {
return false
}
}
return true
}
@ -269,6 +275,11 @@ func missingMethod(typ Type, T *Interface) (method *Func, wrongType bool) {
// 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 T.IsEmpty() {
return
}
// T.methods.NumEntries() > 0
if ityp, _ := typ.Underlying().(*Interface); ityp != nil {
for _, obj := range T.methods.entries {
m := obj.(*Func)

View File

@ -15,7 +15,7 @@ import (
func (check *checker) declare(scope *Scope, id *ast.Ident, obj Object) {
if obj.Name() == "_" {
// blank identifiers are not declared
obj.setOuter(scope)
obj.setParent(scope)
} else if alt := scope.Insert(obj); alt != nil {
check.errorf(obj.Pos(), "%s redeclared in this block", obj.Name())
if pos := alt.Pos(); pos.IsValid() {
@ -32,7 +32,7 @@ func (check *checker) declareShort(scope *Scope, list []Object) {
n := 0 // number of new objects
for _, obj := range list {
if obj.Name() == "_" {
obj.setOuter(scope)
obj.setParent(scope)
continue // blank identifiers are not visible
}
if scope.Insert(obj) == nil {
@ -86,7 +86,7 @@ func (check *checker) resolveFiles(files []*ast.File, importer Importer) {
// the package identifier denotes the current package, but it is in no scope
check.callIdent(file.Name, pkg)
fileScope = &Scope{Outer: pkg.scope}
fileScope = NewScope(pkg.scope)
scopes = append(scopes, fileScope)
for _, decl := range file.Decls {
@ -125,7 +125,7 @@ func (check *checker) resolveFiles(files []*ast.File, importer Importer) {
// add import to file scope
if name == "." {
// merge imported scope with file scope
for _, obj := range imp.scope.Entries {
for _, obj := range imp.scope.entries {
// gcimported package scopes contain non-exported
// objects such as types used in partially exported
// objects - do not accept them
@ -232,7 +232,7 @@ func (check *checker) resolveFiles(files []*ast.File, importer Importer) {
obj := &Func{pos: d.Name.Pos(), pkg: pkg, name: d.Name.Name, decl: d}
if obj.name == "init" {
// init functions are not visible - don't declare them in package scope
obj.outer = pkg.scope
obj.parent = pkg.scope
check.callIdent(d.Name, obj)
} else {
check.declare(pkg.scope, d.Name, obj)
@ -247,8 +247,8 @@ func (check *checker) resolveFiles(files []*ast.File, importer Importer) {
// Phase 2: Objects in file scopes and package scopes must have different names.
for _, scope := range scopes {
for _, obj := range scope.Entries {
if alt := pkg.scope.Lookup(obj.Name()); alt != nil {
for _, obj := range scope.entries {
if alt := pkg.scope.Lookup(nil, obj.Name()); alt != nil {
// TODO(gri) better error message
check.errorf(alt.Pos(), "%s redeclared in this block by import of package %s", obj.Name(), obj.Pkg().Name())
}
@ -258,7 +258,6 @@ func (check *checker) resolveFiles(files []*ast.File, importer Importer) {
// Phase 3: Associate methods with types.
// We do this after all top-level type names have been collected.
check.topScope = pkg.scope
for _, meth := range methods {
m := meth.meth
// The receiver type must be one of the following:
@ -270,6 +269,8 @@ func (check *checker) resolveFiles(files []*ast.File, importer Importer) {
typ = ptr.X
}
// determine receiver base type name
// Note: We cannot simply call check.typ because this will require
// check.objMap to be usable, which it isn't quite yet.
ident, ok := typ.(*ast.Ident)
if !ok {
// Disabled for now since the parser reports this error.
@ -278,7 +279,7 @@ func (check *checker) resolveFiles(files []*ast.File, importer Importer) {
}
// determine receiver base type object
var tname *TypeName
if obj := check.lookup(ident); obj != nil {
if obj := pkg.scope.LookupParent(ident.Name); obj != nil {
obj, ok := obj.(*TypeName)
if !ok {
check.errorf(ident.Pos(), "%s is not a type", ident.Name)
@ -291,24 +292,29 @@ func (check *checker) resolveFiles(files []*ast.File, importer Importer) {
tname = obj
} else {
// identifier not declared/resolved
check.errorf(ident.Pos(), "undeclared name: %s", ident.Name)
if ident.Name == "_" {
check.errorf(ident.Pos(), "cannot use _ as value or type")
} else {
check.errorf(ident.Pos(), "undeclared name: %s", ident.Name)
}
continue // ignore this method
}
// declare method in receiver base type scope
scope := check.methods[tname]
scope := check.methods[tname] // lazily allocated
if scope == nil {
scope = new(Scope)
check.methods[tname] = scope
}
fun := &Func{pos: m.Name.Pos(), pkg: check.pkg, name: m.Name.Name, decl: m}
check.declare(scope, m.Name, fun)
// HACK(gri) change method outer scope to file scope containing the declaration
fun.outer = meth.file // remember the file scope
// HACK(gri) change method parent scope to file scope containing the declaration
fun.parent = meth.file // remember the file scope
}
// Phase 4) Typecheck all objects in objList but not function bodies.
check.objMap = objMap // indicate we are doing global declarations (objects may not have a type yet)
check.topScope = pkg.scope
for _, obj := range objList {
if obj.Type() == nil {
check.declareObject(obj, false)
@ -469,39 +475,42 @@ func (check *checker) declareType(obj *TypeName, typ ast.Expr, cycleOk bool) {
case *Struct:
// struct fields must not conflict with methods
for _, f := range t.fields {
if m := scope.Lookup(f.Name); m != nil {
if m := scope.Lookup(nil, f.Name); m != nil {
check.errorf(m.Pos(), "type %s has both field and method named %s", obj.name, f.Name)
// ok to continue
}
}
case *Interface:
// methods cannot be associated with an interface type
for _, m := range scope.Entries {
for _, m := range scope.entries {
recv := m.(*Func).decl.Recv.List[0].Type
check.errorf(recv.Pos(), "invalid receiver type %s (%s is an interface type)", obj.name, obj.name)
// ok to continue
}
}
// typecheck method signatures
var methods ObjSet
for _, obj := range scope.Entries {
m := obj.(*Func)
var methods *Scope // lazily allocated
if !scope.IsEmpty() {
methods = NewScope(nil)
for _, obj := range scope.entries {
m := obj.(*Func)
// set the correct file scope for checking this method type
fileScope := m.outer
assert(fileScope != nil)
oldScope := check.topScope
check.topScope = fileScope
// set the correct file scope for checking this method type
fileScope := m.parent
assert(fileScope != nil)
oldScope := check.topScope
check.topScope = fileScope
sig := check.typ(m.decl.Type, cycleOk).(*Signature)
params, _ := check.collectParams(sig.scope, m.decl.Recv, false)
sig := check.typ(m.decl.Type, cycleOk).(*Signature)
params, _ := check.collectParams(sig.scope, m.decl.Recv, false)
check.topScope = oldScope // reset topScope
check.topScope = oldScope // reset topScope
sig.recv = params[0] // the parser/assocMethod ensure there is exactly one parameter
m.typ = sig
assert(methods.Insert(obj) == nil)
check.later(m, sig, m.decl.Body)
sig.recv = params[0] // the parser/assocMethod ensure there is exactly one parameter
m.typ = sig
assert(methods.Insert(obj) == nil)
check.later(m, sig, m.decl.Body)
}
}
named.methods = methods
delete(check.methods, obj) // we don't need this scope anymore

View File

@ -31,7 +31,7 @@ func (check *checker) isTerminating(s ast.Stmt, label string) bool {
// the predeclared panic() function is terminating
if call, _ := s.X.(*ast.CallExpr); call != nil {
if id, _ := call.Fun.(*ast.Ident); id != nil {
if obj := check.lookup(id); obj != nil {
if obj := check.topScope.LookupParent(id.Name); obj != nil {
// TODO(gri) Predeclared functions should be modelled as objects
// rather then ordinary functions that have a predeclared
// function type. This would simplify code here and else-

View File

@ -7,71 +7,140 @@ package types
import (
"bytes"
"fmt"
"go/ast"
)
// A Scope maintains the set of named language entities declared
// in the scope and a link to the immediately surrounding (outer)
// scope.
//
// TODO(gri) Provide scopes with a name or other mechanism so that
// objects can use that information for better printing.
// A Scope maintains a set of objects and a link to its containing (parent)
// scope. Objects may be inserted and looked up by name, or by package path
// and name. A nil *Scope acts like an empty scope for operations that do not
// modify the scope or access a scope's parent scope.
type Scope struct {
Outer *Scope
Entries []Object // scope entries in insertion order
large map[string]Object // for fast lookup - only used for larger scopes
parent *Scope
entries []Object
}
// Lookup returns the object with the given name if it is
// found in scope s, otherwise it returns nil. Outer scopes
// NewScope returns a new, empty scope.
func NewScope(parent *Scope) *Scope {
return &Scope{parent, nil}
}
// Parent returns the scope's containing (parent) scope.
func (s *Scope) Parent() *Scope {
return s.parent
}
// NumEntries() returns the number of scope entries.
// If s == nil, the result is 0.
func (s *Scope) NumEntries() int {
if s == nil {
return 0 // empty scope
}
return len(s.entries)
}
// IsEmpty reports whether the scope is empty.
// If s == nil, the result is true.
func (s *Scope) IsEmpty() bool {
return s == nil || len(s.entries) == 0
}
// At returns the i'th scope entry for 0 <= i < NumEntries().
func (s *Scope) At(i int) Object {
return s.entries[i]
}
// Index returns the index of the scope entry with the given package
// (path) and name if such an entry exists in s; otherwise the result
// is negative. A nil scope acts like an empty scope, and parent scopes
// are ignored.
//
func (s *Scope) Lookup(name string) Object {
if s.large != nil {
return s.large[name]
// If pkg != nil, both pkg.Path() and name are used to identify an
// entry, per the Go rules for identifier equality. If pkg == nil,
// only the name is used and the package path is ignored.
func (s *Scope) Index(pkg *Package, name string) int {
if s == nil {
return -1 // empty scope
}
for _, obj := range s.Entries {
if obj.Name() == name {
return obj
// fast path: only the name must match
if pkg == nil {
for i, obj := range s.entries {
if obj.Name() == name {
return i
}
}
return -1
}
// slow path: both pkg path and name must match
// TODO(gri) if packages were canonicalized, we could just compare the packages
for i, obj := range s.entries {
// spec:
// "Two identifiers are different if they are spelled differently,
// or if they appear in different packages and are not exported.
// Otherwise, they are the same."
if obj.Name() == name && (ast.IsExported(name) || obj.Pkg().path == pkg.path) {
return i
}
}
// not found
return -1
// TODO(gri) Optimize Lookup by also maintaining a map representation
// for larger scopes.
}
// Lookup returns the scope entry At(i) for i = Index(pkg, name), if i >= 0.
// Otherwise it returns nil.
func (s *Scope) Lookup(pkg *Package, name string) Object {
if i := s.Index(pkg, name); i >= 0 {
return s.At(i)
}
return nil
}
// LookupParent follows the parent chain of scopes starting with s until it finds
// a scope where Lookup(nil, name) returns a non-nil entry, and then returns that
// entry. If no such scope exists, the result is nil.
func (s *Scope) LookupParent(name string) Object {
for s != nil {
if i := s.Index(nil, name); i >= 0 {
return s.At(i)
}
s = s.parent
}
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, sets the object's scope to
// s, and returns nil.
// If s already contains an object with the same package path
// and name, Insert leaves s unchanged and returns that object.
// Otherwise it inserts obj, sets the object's scope to s, and
// returns nil.
//
func (s *Scope) Insert(obj Object) Object {
name := obj.Name()
if alt := s.Lookup(name); alt != nil {
if alt := s.Lookup(obj.Pkg(), obj.Name()); alt != nil {
return alt
}
s.Entries = append(s.Entries, obj)
obj.setOuter(s)
// If the scope size reaches a threshold, use a map for faster lookups.
const threshold = 20
if len(s.Entries) > threshold {
if s.large == nil {
m := make(map[string]Object, len(s.Entries))
for _, obj := range s.Entries {
m[obj.Name()] = obj
}
s.large = m
}
s.large[name] = obj
}
s.entries = append(s.entries, obj)
obj.setParent(s)
return nil
}
// Debugging support
// String returns a string representation of the scope, for debugging.
func (s *Scope) String() string {
if s == nil {
return "scope {}"
}
var buf bytes.Buffer
fmt.Fprintf(&buf, "scope %p {", s)
if s != nil && len(s.Entries) > 0 {
if s != nil && len(s.entries) > 0 {
fmt.Fprintln(&buf)
for _, obj := range s.Entries {
for _, obj := range s.entries {
fmt.Fprintf(&buf, "\t%s\t%T\n", obj.Name(), obj)
}
}

View File

@ -299,6 +299,14 @@ func (check *checker) multipleDefaults(list []ast.Stmt) {
}
}
func (check *checker) openScope() {
check.topScope = NewScope(check.topScope)
}
func (check *checker) closeScope() {
check.topScope = check.topScope.Parent()
}
// stmt typechecks statement s.
func (check *checker) stmt(s ast.Stmt) {
switch s := s.(type) {
@ -328,7 +336,7 @@ func (check *checker) stmt(s ast.Stmt) {
// but some builtins are excluded
// (Caution: This evaluates e.Fun twice, once here and once
// below as part of s.X. This has consequences for
// check.register. Perhaps this can be avoided.)
// check.callIdent. Perhaps this can be avoided.)
check.expr(&x, e.Fun, nil, -1)
if x.mode != invalid {
if b, ok := x.typ.(*Builtin); ok && !b.isStatement {
@ -397,7 +405,7 @@ func (check *checker) stmt(s ast.Stmt) {
if ident, ok := x.(*ast.Ident); ok {
// use the correct obj if the ident is redeclared
obj = &Var{pos: ident.Pos(), pkg: check.pkg, name: ident.Name}
if alt := check.topScope.Lookup(ident.Name); alt != nil {
if alt := check.topScope.Lookup(nil, ident.Name); alt != nil {
obj = alt
}
check.callIdent(ident, obj)
@ -532,7 +540,7 @@ func (check *checker) stmt(s ast.Stmt) {
if tag == nil {
// use fake true tag value and position it at the opening { of the switch
ident := &ast.Ident{NamePos: s.Body.Lbrace, Name: "true"}
check.callIdent(ident, Universe.Lookup("true"))
check.callIdent(ident, Universe.Lookup(nil, "true"))
tag = ident
}
check.expr(&x, tag, nil, -1)

View File

@ -36,9 +36,8 @@ func (x *pi /* ERROR "not a type" */ ) m3() {}
type _ struct { m int }
type _ struct { m int }
// TODO(gri) blank idents not fully checked - disabled for now
// func (_ /* ERROR "cannot use _" */) m() {}
// func (_ /* ERROR "cannot use _" */) m() {}
func (_ /* ERROR "cannot use _" */) m() {}
func m(_ /* ERROR "cannot use _" */) {}
// Methods with receiver base type declared in another file.
func (T3) m1() {}

View File

@ -162,11 +162,6 @@ func (s *Struct) Tag(i int) string {
}
return ""
}
func (s *Struct) ForEachField(f func(*Field)) {
for _, fld := range s.fields {
f(fld)
}
}
func (f *Field) isMatch(pkg *Package, name string) bool {
// spec:
@ -226,16 +221,6 @@ func (t *Tuple) Len() int {
// At returns the i'th variable of tuple t.
func (t *Tuple) At(i int) *Var { return t.vars[i] }
// ForEach calls f with each variable of tuple t in index order.
// TODO(gri): Do we keep ForEach or should we abandon it in favor or Len and At?
func (t *Tuple) ForEach(f func(*Var)) {
if t != nil {
for _, x := range t.vars {
f(x)
}
}
}
// A Signature represents a (non-builtin) function type.
type Signature struct {
scope *Scope // function scope
@ -313,27 +298,19 @@ func (b *Builtin) Name() string {
// An Interface represents an interface type.
type Interface struct {
methods ObjSet
methods *Scope // may be nil
}
// NumMethods returns the number of methods of interface t.
func (t *Interface) NumMethods() int { return len(t.methods.entries) }
func (t *Interface) NumMethods() int { return t.methods.NumEntries() }
// Method returns the i'th method of interface t for 0 <= i < t.NumMethods().
func (t *Interface) Method(i int) *Func {
return t.methods.entries[i].(*Func)
return t.methods.At(i).(*Func)
}
// IsEmpty() reports whether t is an empty interface.
func (t *Interface) IsEmpty() bool { return len(t.methods.entries) == 0 }
// ForEachMethod calls f with each method of interface t in index order.
// TODO(gri) Should we abandon this in favor of NumMethods and Method?
func (t *Interface) ForEachMethod(f func(*Func)) {
for _, obj := range t.methods.entries {
f(obj.(*Func))
}
}
func (t *Interface) IsEmpty() bool { return t.methods.IsEmpty() }
// A Map represents a map type.
type Map struct {
@ -372,11 +349,25 @@ func (c *Chan) Elem() Type { return c.elt }
type Named struct {
obj *TypeName // corresponding declared object
underlying Type // nil if not fully declared yet; never a *Named
methods ObjSet // directly associated methods (not the method set of this type)
methods *Scope // directly associated methods (not the method set of this type); may be nil
}
// NewNamed returns a new named type for the given type name, underlying type, and associated methods.
func NewNamed(obj *TypeName, underlying Type, methods ObjSet) *Named {
// The underlying type must exist and not be a *Named, and the methods scope entries must be *Func
// objects if the scope is not empty.
func NewNamed(obj *TypeName, underlying Type, methods *Scope) *Named {
if _, ok := underlying.(*Named); ok {
panic("types.NewNamed: underlying type must not be *Named")
}
if methods != nil {
for _, obj := range methods.entries {
if _, ok := obj.(*Func); !ok {
panic("types.NewNamed: methods must be *Func objects")
}
}
}
typ := &Named{obj, underlying, methods}
if obj.typ == nil {
obj.typ = typ
@ -388,19 +379,11 @@ func NewNamed(obj *TypeName, underlying Type, methods ObjSet) *Named {
func (t *Named) Obj() *TypeName { return t.obj }
// NumMethods returns the number of methods directly associated with named type t.
func (t *Named) NumMethods() int { return len(t.methods.entries) }
func (t *Named) NumMethods() int { return t.methods.NumEntries() }
// Method returns the i'th method of named type t for 0 <= i < t.NumMethods().
func (t *Named) Method(i int) *Func {
return t.methods.entries[i].(*Func)
}
// ForEachMethod calls f with each method associated with t in index order.
// TODO(gri) Should we abandon this in favor of NumMethods and Method?
func (t *Named) ForEachMethod(fn func(*Func)) {
for _, obj := range t.methods.entries {
fn(obj.(*Func))
}
return t.methods.At(i).(*Func)
}
// Implementations for Type methods.

View File

@ -110,7 +110,7 @@ func TestTypes(t *testing.T) {
t.Errorf("%s: %s", src, err)
continue
}
typ := pkg.scope.Lookup("T").Type().Underlying()
typ := pkg.scope.Lookup(nil, "T").Type().Underlying()
str := typeString(typ)
if str != test.str {
t.Errorf("%s: got %s, want %s", test.src, str, test.str)

View File

@ -101,7 +101,7 @@ func init() {
// error type
{
// Error has a nil package in its qualified name since it is in no package
var methods ObjSet
methods := NewScope(nil)
sig := &Signature{results: NewTuple(&Var{name: "", typ: Typ[String]})}
methods.Insert(&Func{token.NoPos, nil, nil, "Error", sig, nil})
def(&TypeName{name: "error", typ: &Named{underlying: &Interface{methods: methods}}})
@ -115,7 +115,7 @@ func init() {
def(&Func{name: f.name, typ: f})
}
universeIota = Universe.Lookup("iota").(*Const)
universeIota = Universe.Lookup(nil, "iota").(*Const)
}
// Objects with names containing blanks are internal and not entered into

View File

@ -169,8 +169,8 @@ func NewBuilder(context *Context) *Builder {
}
// Create Values for built-in functions.
for _, obj := range types.Universe.Entries {
switch obj := obj.(type) {
for i, n := 0, types.Universe.NumEntries(); i < n; i++ {
switch obj := types.Universe.At(i).(type) {
case *types.Func:
v := &Builtin{obj}
b.globals[obj] = v
@ -1856,7 +1856,7 @@ func (b *Builder) rangeIndexed(fn *Function, x Value, tv types.Type) (k, v Value
} else {
// length = len(x).
var c Call
c.Call.Func = b.globals[types.Universe.Lookup("len")]
c.Call.Func = b.globals[types.Universe.Lookup(nil, "len")]
c.Call.Args = []Value{x}
c.setType(tInt)
length = fn.emit(&c)
@ -2324,9 +2324,10 @@ func (b *Builder) buildFunction(fn *Function) {
if recv := fn.Signature.Recv(); recv != nil {
fn.addParamObj(recv)
}
fn.Signature.Params().ForEach(func(p *types.Var) {
fn.addParamObj(p)
})
params := fn.Signature.Params()
for i, n := 0, params.Len(); i < n; i++ {
fn.addParamObj(params.At(i))
}
}
return
}
@ -2571,8 +2572,9 @@ func (b *Builder) createPackageImpl(typkg *types.Package, importPath string, fil
// No code.
// No position information.
for _, obj := range p.Types.Scope().Entries {
b.memberFromObject(p, obj, nil)
scope := p.Types.Scope()
for i, n := 0, scope.NumEntries(); i < n; i++ {
b.memberFromObject(p, scope.At(i), nil)
}
}
@ -2619,9 +2621,9 @@ func (b *Builder) buildDecl(pkg *Package, decl ast.Decl) {
continue
}
nt := pkg.ObjectOf(id).Type().(*types.Named)
nt.ForEachMethod(func(m *types.Func) {
b.buildFunction(b.Prog.concreteMethods[m])
})
for i, n := 0, nt.NumMethods(); i < n; i++ {
b.buildFunction(b.Prog.concreteMethods[nt.Method(i)])
}
}
}

View File

@ -42,7 +42,7 @@ var errorType = makeNamedType("error", &opaqueType{nil, "error"})
func makeNamedType(name string, underlying types.Type) *types.Named {
obj := types.NewTypeName(reflectTypesPackage, name, nil)
return types.NewNamed(obj, underlying, types.ObjSet{})
return types.NewNamed(obj, underlying, nil)
}
func makeReflectValue(t types.Type, v value) value {

View File

@ -147,17 +147,19 @@ func buildMethodSet(prog *Program, typ types.Type) MethodSet {
t = t.Deref()
if nt, ok := t.(*types.Named); ok {
nt.ForEachMethod(func(m *types.Func) {
for i, n := 0, nt.NumMethods(); i < n; i++ {
m := nt.Method(i)
addCandidate(nextcands, MakeId(m.Name(), m.Pkg()), m, prog.concreteMethods[m], node)
})
}
t = nt.Underlying()
}
switch t := t.(type) {
case *types.Interface:
t.ForEachMethod(func(m *types.Func) {
for i, n := 0, t.NumMethods(); i < n; i++ {
m := t.Method(i)
addCandidate(nextcands, MakeId(m.Name(), m.Pkg()), m, nil, node)
})
}
case *types.Struct:
for i, n := 0, t.NumFields(); i < n; i++ {
@ -443,13 +445,12 @@ func findPromotedField(st *types.Struct, id Id) (*anonFieldPath, int) {
visited := make(map[types.Type]bool)
var list, next []*anonFieldPath
i := 0
st.ForEachField(func(f *types.Field) {
for i, n := 0, st.NumFields(); i < n; i++ {
f := st.Field(i)
if f.IsAnonymous {
list = append(list, &anonFieldPath{nil, i, f})
}
i++
})
}
// Search the current level if there is any work to do and collect
// embedded types of the next lower level in the next list.
@ -470,13 +471,12 @@ func findPromotedField(st *types.Struct, id Id) (*anonFieldPath, int) {
return node, i
}
}
i := 0
typ.ForEachField(func(f *types.Field) {
for i, n := 0, typ.NumFields(); i < n; i++ {
f := typ.Field(i)
if f.IsAnonymous {
next = append(next, &anonFieldPath{node, i, f})
}
i++
})
}
}
}

View File

@ -90,7 +90,7 @@ func (info *TypeInfo) IsType(e ast.Expr) bool {
func (info *TypeInfo) isPackageRef(sel *ast.SelectorExpr) types.Object {
if id, ok := sel.X.(*ast.Ident); ok {
if pkg, ok := info.ObjectOf(id).(*types.Package); ok {
return pkg.Scope().Lookup(sel.Sel.Name)
return pkg.Scope().Lookup(nil, sel.Sel.Name)
}
}
return nil