diff --git a/go/types/check.go b/go/types/check.go index ca5a2c5fdf..0032726e59 100644 --- a/go/types/check.go +++ b/go/types/check.go @@ -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), } diff --git a/go/types/expr.go b/go/types/expr.go index 397bc5cfe5..157392b93f 100644 --- a/go/types/expr.go +++ b/go/types/expr.go @@ -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)} diff --git a/go/types/gcimporter.go b/go/types/gcimporter.go index d40f9268b9..c01d896f8c 100644 --- a/go/types/gcimporter.go +++ b/go/types/gcimporter.go @@ -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}) } diff --git a/go/types/gcimporter_test.go b/go/types/gcimporter_test.go index bc0378a816..ad9e8ddfe9 100644 --- a/go/types/gcimporter_test.go +++ b/go/types/gcimporter_test.go @@ -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 diff --git a/go/types/objects.go b/go/types/objects.go index de8a9ab07f..c425c992bf 100644 --- a/go/types/objects.go +++ b/go/types/objects.go @@ -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 } diff --git a/go/types/objset.go b/go/types/objset.go deleted file mode 100644 index bd3ea01dd9..0000000000 --- a/go/types/objset.go +++ /dev/null @@ -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() -} diff --git a/go/types/predicates.go b/go/types/predicates.go index 521cd54433..97c88991d8 100644 --- a/go/types/predicates.go +++ b/go/types/predicates.go @@ -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) diff --git a/go/types/resolver.go b/go/types/resolver.go index 7fb8acea9b..0a8d003d78 100644 --- a/go/types/resolver.go +++ b/go/types/resolver.go @@ -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 diff --git a/go/types/return.go b/go/types/return.go index 6c69ef4ff8..68f3f6eaa9 100644 --- a/go/types/return.go +++ b/go/types/return.go @@ -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- diff --git a/go/types/scope.go b/go/types/scope.go index c52266e752..a150593e9a 100644 --- a/go/types/scope.go +++ b/go/types/scope.go @@ -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) } } diff --git a/go/types/stmt.go b/go/types/stmt.go index 3b5c0906f4..90676ddda0 100644 --- a/go/types/stmt.go +++ b/go/types/stmt.go @@ -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) diff --git a/go/types/testdata/decls2a.src b/go/types/testdata/decls2a.src index 93e3d172f4..80dea53f95 100644 --- a/go/types/testdata/decls2a.src +++ b/go/types/testdata/decls2a.src @@ -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() {} diff --git a/go/types/types.go b/go/types/types.go index 6d02c4c1b4..f8ae5884e5 100644 --- a/go/types/types.go +++ b/go/types/types.go @@ -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. diff --git a/go/types/types_test.go b/go/types/types_test.go index 6b08872275..54fbb87152 100644 --- a/go/types/types_test.go +++ b/go/types/types_test.go @@ -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) diff --git a/go/types/universe.go b/go/types/universe.go index ee5ac8a615..3dc8bbd124 100644 --- a/go/types/universe.go +++ b/go/types/universe.go @@ -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 diff --git a/ssa/builder.go b/ssa/builder.go index e43c26e8fd..21b2ce043b 100644 --- a/ssa/builder.go +++ b/ssa/builder.go @@ -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)]) + } } } diff --git a/ssa/interp/reflect.go b/ssa/interp/reflect.go index 19a4500a14..e00a5149d3 100644 --- a/ssa/interp/reflect.go +++ b/ssa/interp/reflect.go @@ -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 { diff --git a/ssa/promote.go b/ssa/promote.go index 71fc48ecbc..aa78918d7f 100644 --- a/ssa/promote.go +++ b/ssa/promote.go @@ -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++ - }) + } } } diff --git a/ssa/typeinfo.go b/ssa/typeinfo.go index c3200a53df..3bc7566f1f 100644 --- a/ssa/typeinfo.go +++ b/ssa/typeinfo.go @@ -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