go.tools/go/types: simplified and faster Scope

- implemented objset for tracking duplicates of fields and methods
  which permitted a simpler and faster scope implementation in turn
- related cleanups and internal renames
- fixed a couple of identifier reporting bugs

Speed of type-checking itself increased by almost 10%
(from ~71Kloc/s to ~78Kloc/s on one machine, measured
via go test -run=Self).

R=adonovan
CC=golang-dev
https://golang.org/cl/11750043
This commit is contained in:
Robert Griesemer 2013-07-23 21:21:37 -07:00
Родитель 2f54663e0e
Коммит 66e7552830
16 изменённых файлов: 150 добавлений и 115 удалений

Просмотреть файл

@ -264,7 +264,7 @@ func (check *checker) shortVarDecl(lhs, rhs []ast.Expr) {
// Use the correct obj if the ident is redeclared. The
// variable's scope starts after the declaration; so we
// must use Scope.Lookup here and call Scope.Insert later.
if alt := scope.Lookup(nil, ident.Name); alt != nil {
if alt := scope.Lookup(ident.Name); alt != nil {
// redeclared object must be a variable
if alt, _ := alt.(*Var); alt != nil {
obj = alt

Просмотреть файл

@ -171,7 +171,7 @@ func (check *checker) selector(x *operand, e *ast.SelectorExpr) {
if ident, ok := e.X.(*ast.Ident); ok {
if pkg, ok := check.topScope.LookupParent(ident.Name).(*Package); ok {
check.recordObject(ident, pkg)
exp := pkg.scope.Lookup(nil, sel)
exp := pkg.scope.Lookup(sel)
if exp == nil {
check.errorf(e.Pos(), "%s not declared by package %s", sel, ident)
goto Error

Просмотреть файл

@ -207,7 +207,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(nil, name); obj != nil {
if obj := scope.Lookup(name); obj != nil {
return obj.(*Const)
}
// otherwise create a new constant and insert it into the scope
@ -218,7 +218,7 @@ func declConst(pkg *Package, name string) *Const {
func declTypeName(pkg *Package, name string) *TypeName {
scope := pkg.scope
if obj := scope.Lookup(nil, name); obj != nil {
if obj := scope.Lookup(name); obj != nil {
return obj.(*TypeName)
}
obj := NewTypeName(token.NoPos, pkg, name, nil)
@ -231,7 +231,7 @@ func declTypeName(pkg *Package, name string) *TypeName {
func declVar(pkg *Package, name string) *Var {
scope := pkg.scope
if obj := scope.Lookup(nil, name); obj != nil {
if obj := scope.Lookup(name); obj != nil {
return obj.(*Var)
}
obj := NewVar(token.NoPos, pkg, name, nil)
@ -241,7 +241,7 @@ func declVar(pkg *Package, name string) *Var {
func declFunc(pkg *Package, name string) *Func {
scope := pkg.scope
if obj := scope.Lookup(nil, name); obj != nil {
if obj := scope.Lookup(name); obj != nil {
return obj.(*Func)
}
obj := NewFunc(token.NoPos, pkg, name, nil)
@ -390,7 +390,7 @@ func (p *gcParser) parseExportedName() (pkg *Package, name string) {
//
func (p *gcParser) parseBasicType() Type {
id := p.expect(scanner.Ident)
obj := Universe.Lookup(nil, id)
obj := Universe.Lookup(id)
if obj, ok := obj.(*TypeName); ok {
return obj.typ
}

Просмотреть файл

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

36
go/types/objset.go Normal file
Просмотреть файл

@ -0,0 +1,36 @@
// 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.
// This file implements objsets.
//
// An objset is similar to a Scope but objset elements
// are identified by their unique id, instead of their
// object name.
package types
// An objset is a set of objects identified by their unique id.
// The zero value for objset is a ready-to-use empty objset.
type objset struct {
objmap map[string]Object // allocated lazily
}
// insert attempts to insert an object obj into objset s.
// If s already contains an alternative object alt with
// the same name, insert leaves s unchanged and returns alt.
// Otherwise it inserts obj and returns nil.
// The object name must not be blank _.
func (s *objset) insert(obj Object) (alt Object) {
name := obj.Name()
assert(name != "_")
id := Id(obj.Pkg(), name)
if alt := s.objmap[id]; alt != nil {
return alt
}
if s.objmap == nil {
s.objmap = make(map[string]Object)
}
s.objmap[id] = obj
return nil
}

Просмотреть файл

@ -23,14 +23,29 @@ func (check *checker) reportAltDecl(obj Object) {
}
}
func (check *checker) declare(scope *Scope, id *ast.Ident, obj Object) {
func (check *checker) declareObj(scope *Scope, id *ast.Ident, obj Object) {
if obj.Name() == "_" {
// blank identifiers are not declared
obj.setParent(scope)
obj = nil
} else if alt := scope.Insert(obj); alt != nil {
check.errorf(obj.Pos(), "%s redeclared in this block", obj.Name())
check.reportAltDecl(alt)
obj = nil // for callIdent below
obj = nil
}
if id != nil {
check.recordObject(id, obj)
}
}
func (check *checker) declareFld(oset *objset, id *ast.Ident, obj Object) {
if obj.Name() == "_" {
// blank identifiers are not declared
obj = nil
} else if alt := oset.insert(obj); alt != nil {
check.errorf(obj.Pos(), "%s redeclared", obj.Name())
check.reportAltDecl(alt)
obj = nil
}
if id != nil {
check.recordObject(id, obj)
@ -128,7 +143,7 @@ func (check *checker) resolveFiles(files []*ast.File) {
return
}
check.declare(pkg.scope, ident, obj)
check.declareObj(pkg.scope, ident, obj)
objList = append(objList, obj)
objMap[obj] = declInfo{scope, typ, init, nil}
}
@ -195,13 +210,13 @@ func (check *checker) resolveFiles(files []*ast.File) {
if obj.IsExported() {
// Note: This will change each imported object's scope!
// May be an issue for types aliases.
check.declare(scope, nil, obj)
check.declareObj(scope, nil, obj)
check.recordImplicit(s, obj)
}
}
} else {
// declare imported package object in file scope
check.declare(scope, nil, imp2)
check.declareObj(scope, nil, imp2)
}
case *ast.ValueSpec:
@ -281,7 +296,7 @@ func (check *checker) resolveFiles(files []*ast.File) {
obj.parent = pkg.scope
check.recordObject(d.Name, obj)
} else {
check.declare(pkg.scope, d.Name, obj)
check.declareObj(pkg.scope, d.Name, obj)
}
} else {
// Associate method with receiver base type name, if possible.
@ -313,7 +328,7 @@ func (check *checker) resolveFiles(files []*ast.File) {
for _, scope := range scopes {
for _, obj := range scope.entries {
if alt := pkg.scope.Lookup(nil, obj.Name()); alt != nil {
if alt := pkg.scope.Lookup(obj.Name()); alt != nil {
check.errorf(alt.Pos(), "%s already declared in this file through import of package %s", obj.Name(), obj.Pkg().Name())
}
}
@ -503,41 +518,39 @@ func (check *checker) typeDecl(obj *TypeName, typ ast.Expr, def *Named, cycleOk
// spec: "For a base type, the non-blank names of methods bound
// to it must be unique."
// => use a scope to determine redeclarations
scope := NewScope(nil)
// => use an objset to determine redeclarations
var mset objset
// spec: "If the base type is a struct type, the non-blank method
// and field names must be distinct."
// => pre-populate the scope to find conflicts
// => pre-populate the objset to find conflicts
// TODO(gri) consider keeping the objset with the struct instead
if t, _ := named.underlying.(*Struct); t != nil {
for _, fld := range t.fields {
if fld.name != "_" {
scope.Insert(fld)
assert(mset.insert(fld) == nil)
}
}
}
// check each method
for _, m := range methods {
assert(m.name != "_") // _ methods were excluded before
mdecl := check.objMap[m]
alt := scope.Insert(m)
m.parent = mdecl.file // correct parent scope (scope.Insert used scope)
ident := check.objMap[m].fdecl.Name
if alt != nil {
switch alt := alt.(type) {
if alt := mset.insert(m); alt != nil {
switch alt.(type) {
case *Var:
check.errorf(m.pos, "field and method with the same name %s", m.name)
check.reportAltDecl(alt)
m = nil
case *Func:
check.errorf(m.pos, "method %s already declared for %s", m.name, named)
check.reportAltDecl(alt)
m = nil
default:
unreachable()
}
check.reportAltDecl(alt)
m = nil
}
check.recordObject(mdecl.fdecl.Name, m)
check.recordObject(ident, m)
// If the method is valid, type-check its signature,
// and collect it with the named base type.
@ -604,7 +617,7 @@ func (check *checker) declStmt(decl ast.Decl) {
check.arityMatch(s, last)
for i, name := range s.Names {
check.declare(check.topScope, name, lhs[i])
check.declareObj(check.topScope, name, lhs[i])
}
case token.VAR:
@ -637,7 +650,7 @@ func (check *checker) declStmt(decl ast.Decl) {
check.arityMatch(s, nil)
for i, name := range s.Names {
check.declare(check.topScope, name, lhs[i])
check.declareObj(check.topScope, name, lhs[i])
}
default:
@ -646,7 +659,7 @@ func (check *checker) declStmt(decl ast.Decl) {
case *ast.TypeSpec:
obj := NewTypeName(s.Name.Pos(), pkg, s.Name.Name, nil)
check.declare(check.topScope, s.Name, obj)
check.declareObj(check.topScope, s.Name, obj)
check.typeDecl(obj, s.Type, nil, false)
default:

Просмотреть файл

@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file implements Scopes.
package types
import (
@ -16,15 +18,16 @@ import (
// objects can use that information for better printing.
// A Scope maintains a set of objects and links to its containing
// (parent) and contained (children) scopes.
// 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.
// (parent) and contained (children) scopes. Objects may be inserted
// and looked up by name. The zero value for Scope is a ready-to-use
// empty scope.
type Scope struct {
parent *Scope
children []*Scope
entries []Object
node ast.Node
entries []Object
objmap map[string]Object // lazily allocated for large scopes
}
// NewScope returns a new, empty scope contained in the given parent
@ -60,74 +63,39 @@ func (s *Scope) Parent() *Scope { return s.parent }
func (s *Scope) Node() ast.Node { return s.node }
// 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)
}
func (s *Scope) NumEntries() int { return len(s.entries) }
// At returns the i'th scope entry for 0 <= i < NumEntries().
func (s *Scope) At(i int) Object { return s.entries[i] }
// NumChildren() returns the number of scopes nested in s.
// If s == nil, the result is 0.
func (s *Scope) NumChildren() int {
if s == nil {
return 0
}
return len(s.children)
}
func (s *Scope) NumChildren() int { return len(s.children) }
// Child returns the i'th child scope for 0 <= i < NumChildren().
func (s *Scope) Child(i int) *Scope { return s.children[i] }
// Lookup returns the object in scope s with the given package
// and name if such an object exists; otherwise the result is nil.
// A nil scope acts like an empty scope, and parent scopes are ignored.
//
// 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) Lookup(pkg *Package, name string) Object {
if s == nil {
return nil // empty scope
// Lookup returns the object in scope s with the given name if such an
// object exists; otherwise the result is nil.
func (s *Scope) Lookup(name string) Object {
if s.objmap != nil {
return s.objmap[name]
}
// fast path: only the name must match
if pkg == nil {
for _, obj := range s.entries {
if obj.Name() == name {
return obj
}
}
return nil
}
// slow path: both pkg path and name must match
for _, obj := range s.entries {
if obj.sameId(pkg, name) {
if obj.Name() == name {
return obj
}
}
// not found
return nil
// TODO(gri) Optimize Lookup by also maintaining a map representation
// for larger scopes.
}
// LookupParent follows the parent chain of scopes starting with s until it finds
// a scope where Lookup(nil, name) returns a non-nil object, and then returns that
// object. If no such scope exists, the result is nil.
// LookupParent follows the parent chain of scopes starting with s until
// it finds a scope where Lookup(name) returns a non-nil object, and then
// returns that object. If no such scope exists, the result is nil.
func (s *Scope) LookupParent(name string) Object {
for s != nil {
if obj := s.Lookup(nil, name); obj != nil {
for ; s != nil; s = s.parent {
if obj := s.Lookup(name); obj != nil {
return obj
}
s = s.parent
}
return nil
}
@ -135,19 +103,34 @@ func (s *Scope) LookupParent(name string) Object {
// TODO(gri): Should Insert not be exported?
// Insert attempts to insert an object obj into scope s.
// 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. The object must not have the blank _ name.
//
// If s already contains an alternative object alt with
// the same name, Insert leaves s unchanged and returns alt.
// Otherwise it inserts obj, sets the object's scope to
// s, and returns nil. The object name must not be blank _.
func (s *Scope) Insert(obj Object) Object {
name := obj.Name()
assert(name != "_")
if alt := s.Lookup(obj.Pkg(), name); alt != nil {
if alt := s.Lookup(name); alt != nil {
return alt
}
// populate parallel objmap for larger scopes
// TODO(gri) what is the right threshold? should we only use a map?
if len(s.entries) == 32 {
m := make(map[string]Object)
for _, obj := range s.entries {
m[obj.Name()] = obj
}
s.objmap = m
}
// add object
s.entries = append(s.entries, obj)
if s.objmap != nil {
s.objmap[name] = obj
}
obj.setParent(s)
return nil
}
@ -159,7 +142,7 @@ func (s *Scope) WriteTo(w io.Writer, n int, recurse bool) {
const ind = ". "
indn := strings.Repeat(ind, n)
if s.NumEntries() == 0 {
if len(s.entries) == 0 {
fmt.Fprintf(w, "%sscope %p {}\n", indn, s)
return
}

Просмотреть файл

@ -85,7 +85,7 @@ func (check *checker) stmt(s ast.Stmt) {
check.funcSig.labels = scope
}
label := s.Label
check.declare(scope, label, NewLabel(label.Pos(), label.Name))
check.declareObj(scope, label, NewLabel(label.Pos(), label.Name))
check.stmt(s.Stmt)
case *ast.ExprStmt:
@ -267,7 +267,7 @@ func (check *checker) stmt(s ast.Stmt) {
if tag == nil {
// use fake true tag value and position it at the opening { of the switch
ident := &ast.Ident{NamePos: s.Body.Lbrace, Name: "true"}
check.recordObject(ident, Universe.Lookup(nil, "true"))
check.recordObject(ident, Universe.Lookup("true"))
tag = ident
}
check.expr(&x, tag)
@ -416,7 +416,7 @@ func (check *checker) stmt(s ast.Stmt) {
typ = x.typ
}
obj := NewVar(lhs.Pos(), check.pkg, lhs.Name, typ)
check.declare(check.topScope, nil, obj)
check.declareObj(check.topScope, nil, obj)
check.recordImplicit(clause, obj)
}
check.stmtList(clause.Body)
@ -552,7 +552,7 @@ func (check *checker) stmt(s ast.Stmt) {
// declare variables
for i, ident := range idents {
check.declare(check.topScope, ident, vars[i])
check.declareObj(check.topScope, ident, vars[i])
}
} else {
// ordinary assignment

Просмотреть файл

@ -193,7 +193,7 @@ func (t *Tuple) At(i int) *Var { return t.vars[i] }
// A Signature represents a (non-builtin) function type.
type Signature struct {
scope *Scope // function scope
scope *Scope // function scope, always present
labels *Scope // label scope, or nil (lazily allocated)
recv *Var // nil if not a method
params *Tuple // (incoming) parameters from left to right; or nil
@ -205,7 +205,9 @@ type Signature struct {
// and results, either of which may be nil. If isVariadic is set, the function
// is variadic, it must have at least one parameter, and the last parameter
// must be of unnamed slice type.
func NewSignature(recv *Var, params, results *Tuple, isVariadic bool) *Signature {
func NewSignature(scope *Scope, recv *Var, params, results *Tuple, isVariadic bool) *Signature {
// TODO(gri) Should we rely on the correct (non-nil) incoming scope
// or should this function allocate and populate a scope?
if isVariadic {
n := params.Len()
if n == 0 {
@ -215,7 +217,7 @@ func NewSignature(recv *Var, params, results *Tuple, isVariadic bool) *Signature
panic("types.NewSignature: variadic parameter must be of unnamed slice type")
}
}
return &Signature{nil, nil, recv, params, results, isVariadic}
return &Signature{scope, nil, recv, params, results, isVariadic}
}
// Recv returns the receiver of signature s, or nil.

Просмотреть файл

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

Просмотреть файл

@ -360,7 +360,7 @@ func (check *checker) collectParams(scope *Scope, list *ast.FieldList, variadicO
// named parameter
for _, name := range field.Names {
par := NewVar(name.Pos(), check.pkg, name.Name, typ)
check.declare(scope, name, par)
check.declareObj(scope, name, par)
params = append(params, par)
}
} else {
@ -385,7 +385,8 @@ func (check *checker) collectMethods(recv Type, list *ast.FieldList, cycleOk boo
return nil
}
scope := NewScope(nil)
var mset objset
for _, f := range list.List {
// TODO(gri) Consider calling funcType here.
typ := check.typ(f.Type, nil, cycleOk)
@ -402,7 +403,7 @@ func (check *checker) collectMethods(recv Type, list *ast.FieldList, cycleOk boo
sig.recv = NewVar(token.NoPos, check.pkg, "", recv)
for _, name := range f.Names {
m := NewFunc(name.Pos(), check.pkg, name.Name, sig)
check.declare(scope, name, m)
check.declareFld(&mset, name, m)
methods = append(methods, m)
}
} else {
@ -417,7 +418,7 @@ func (check *checker) collectMethods(recv Type, list *ast.FieldList, cycleOk boo
check.errorf(f.Type.Pos(), "reference to incomplete type %s - unimplemented", f.Type)
case *Interface:
for _, m := range t.methods {
check.declare(scope, nil, m)
check.declareFld(&mset, nil, m)
methods = append(methods, m)
}
default:
@ -448,7 +449,7 @@ func (check *checker) collectFields(list *ast.FieldList, cycleOk bool) (fields [
return
}
scope := NewScope(nil)
var fset objset
var typ Type // current field typ
var tag string // current field tag
@ -461,7 +462,7 @@ func (check *checker) collectFields(list *ast.FieldList, cycleOk bool) (fields [
}
fld := NewFieldVar(pos, check.pkg, name, typ, anonymous)
check.declare(scope, ident, fld)
check.declareFld(&fset, ident, fld)
fields = append(fields, fld)
}

Просмотреть файл

@ -116,7 +116,7 @@ func init() {
def(NewFunc(token.NoPos, nil, f.name, f))
}
universeIota = Universe.Lookup(nil, "iota").(*Const)
universeIota = Universe.Lookup("iota").(*Const)
}
// Objects with names containing blanks are internal and not entered into

Просмотреть файл

@ -116,7 +116,7 @@ func (info *PackageInfo) IsType(e ast.Expr) bool {
func (info *PackageInfo) 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(nil, sel.Sel.Name)
return pkg.Scope().Lookup(sel.Sel.Name)
}
}
return nil
@ -245,5 +245,5 @@ func (info *PackageInfo) BuiltinCallSignature(e *ast.CallExpr) *types.Signature
panic("unknown builtin: " + builtin)
}
return types.NewSignature(nil, types.NewTuple(params...), nil, isVariadic)
return types.NewSignature(nil, nil, types.NewTuple(params...), nil, isVariadic)
}

Просмотреть файл

@ -1716,7 +1716,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 = fn.Prog.builtins[types.Universe.Lookup(nil, "len")]
c.Call.Func = fn.Prog.builtins[types.Universe.Lookup("len")]
c.Call.Args = []Value{x}
c.setType(tInt)
length = fn.emit(&c)

Просмотреть файл

@ -394,7 +394,7 @@ func newMethod(pkg *ssa.Package, recvType types.Type, name string) *ssa.Function
// that is needed is the "pointerness" of Recv.Type, and for
// now, we'll set it to always be false since we're only
// concerned with rtype. Encapsulate this better.
sig := types.NewSignature(types.NewVar(token.NoPos, nil, "recv", recvType), nil, nil, false)
sig := types.NewSignature(nil, types.NewVar(token.NoPos, nil, "recv", recvType), nil, nil, false)
fn := ssa.NewFunction(name, sig, "fake reflect method")
fn.Pkg = pkg
fn.Prog = pkg.Prog

Просмотреть файл

@ -140,7 +140,7 @@ func makeMethod(prog *Program, typ types.Type, obj *types.Method) *Function {
//
func promotionWrapper(prog *Program, typ types.Type, obj *types.Method) *Function {
old := obj.Func.Type().(*types.Signature)
sig := types.NewSignature(types.NewVar(token.NoPos, nil, "recv", typ), old.Params(), old.Results(), old.IsVariadic())
sig := types.NewSignature(nil, types.NewVar(token.NoPos, nil, "recv", typ), old.Params(), old.Results(), old.IsVariadic())
// TODO(adonovan): include implicit field path in description.
description := fmt.Sprintf("promotion wrapper for (%s).%s", old.Recv(), obj.Func.Name())
@ -310,7 +310,7 @@ func boundMethodWrapper(meth *Function) *Function {
s := meth.Signature
fn = &Function{
name: "bound$" + meth.String(),
Signature: types.NewSignature(nil, s.Params(), s.Results(), s.IsVariadic()), // drop recv
Signature: types.NewSignature(nil, nil, s.Params(), s.Results(), s.IsVariadic()), // drop recv
Synthetic: "bound method wrapper for " + meth.String(),
Prog: prog,
pos: meth.Pos(),
@ -359,7 +359,7 @@ func indirectionWrapper(meth *Function) *Function {
// TODO(adonovan): is there a *types.Func for this method?
fn = &Function{
name: meth.Name(),
Signature: types.NewSignature(recv, s.Params(), s.Results(), s.IsVariadic()),
Signature: types.NewSignature(nil, recv, s.Params(), s.Results(), s.IsVariadic()),
Prog: prog,
Synthetic: "receiver indirection wrapper for " + meth.String(),
pos: meth.Pos(),