go.tools/go/types: simplified lookup
- much simpler lookup - more result information - will make tracking of pointer-ness easier TODO: apply the same changes to method set computation R=adonovan CC=golang-dev https://golang.org/cl/10379049
This commit is contained in:
parent
33ae2b030f
commit
9ff248b00d
|
|
@ -342,8 +342,8 @@ func (check *checker) builtin(x *operand, call *ast.CallExpr, bin *Builtin, iota
|
||||||
goto Error
|
goto Error
|
||||||
}
|
}
|
||||||
sel := arg.Sel.Name
|
sel := arg.Sel.Name
|
||||||
_, index, _ := LookupFieldOrMethod(x.typ, check.pkg, arg.Sel.Name)
|
obj, index := LookupFieldOrMethod(x.typ, check.pkg, arg.Sel.Name)
|
||||||
if index == nil {
|
if obj == nil {
|
||||||
check.invalidArg(x.pos(), "%s has no single field %s", x, sel)
|
check.invalidArg(x.pos(), "%s has no single field %s", x, sel)
|
||||||
goto Error
|
goto Error
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1311,7 +1311,7 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle
|
||||||
goto Error
|
goto Error
|
||||||
}
|
}
|
||||||
// TODO(gri) use collision information for better error message
|
// TODO(gri) use collision information for better error message
|
||||||
obj, _, _ := LookupFieldOrMethod(x.typ, check.pkg, sel)
|
obj, _ := LookupFieldOrMethod(x.typ, check.pkg, sel)
|
||||||
if obj == nil {
|
if obj == nil {
|
||||||
check.invalidOp(e.Pos(), "%s has no single field or method %s", x, sel)
|
check.invalidOp(e.Pos(), "%s has no single field or method %s", x, sel)
|
||||||
goto Error
|
goto Error
|
||||||
|
|
|
||||||
|
|
@ -889,7 +889,7 @@ func (p *gcParser) parseMethodDecl() {
|
||||||
// add method to type unless type was imported before
|
// add method to type unless type was imported before
|
||||||
// and method exists already
|
// and method exists already
|
||||||
// TODO(gri) This is a quadratic algorithm - ok for now because method counts are small.
|
// TODO(gri) This is a quadratic algorithm - ok for now because method counts are small.
|
||||||
if lookupMethod(base.methods, pkg, name) == nil {
|
if _, m := lookupMethod(base.methods, pkg, name); m == nil {
|
||||||
base.methods = append(base.methods, NewFunc(token.NoPos, pkg, name, sig))
|
base.methods = append(base.methods, NewFunc(token.NoPos, pkg, name, sig))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,23 @@ package types
|
||||||
|
|
||||||
import "go/ast"
|
import "go/ast"
|
||||||
|
|
||||||
// LookupFieldOrMethod looks up a field or method with given package and name in typ.
|
// LookupFieldOrMethod looks up a field or method with given package and name
|
||||||
// If an entry is found, obj is the corresponding *Field or *Func. For fields, index
|
// in typ and returns the corresponding *Field or *Func, and an index sequence.
|
||||||
// is the index sequence to reach the (possibly embedded) field; for methods, index
|
|
||||||
// is nil; and collision is false. If no entry is found, obj is nil, index is undefined,
|
|
||||||
// and collision indicates if the reason for not finding an entry was a name collision.
|
|
||||||
//
|
//
|
||||||
func LookupFieldOrMethod(typ Type, pkg *Package, name string) (obj Object, index []int, collision bool) {
|
// The last index entry is the field or method index in the (possibly embedded)
|
||||||
|
// type where the entry was found, either:
|
||||||
|
//
|
||||||
|
// 1) the list of declared methods of a named type; or
|
||||||
|
// 2) the list of all methods (method set) of an interface type; or
|
||||||
|
// 3) the list of fields of a struct type.
|
||||||
|
//
|
||||||
|
// The earlier index entries are the indices of the embedded fields traversed
|
||||||
|
// to get to the found entry, starting at depth 0.
|
||||||
|
//
|
||||||
|
// If no entry is found, a nil object is returned. In this case, the returned
|
||||||
|
// index sequence points to an ambiguous entry if it exists, or it is nil.
|
||||||
|
//
|
||||||
|
func LookupFieldOrMethod(typ Type, pkg *Package, name string) (obj Object, index []int) {
|
||||||
if name == "_" {
|
if name == "_" {
|
||||||
return // empty fields/methods are never found
|
return // empty fields/methods are never found
|
||||||
}
|
}
|
||||||
|
|
@ -31,101 +41,61 @@ func LookupFieldOrMethod(typ Type, pkg *Package, name string) (obj Object, index
|
||||||
// named types that we have seen already
|
// named types that we have seen already
|
||||||
seen := make(map[*Named]bool)
|
seen := make(map[*Named]bool)
|
||||||
|
|
||||||
// We treat the top-most level separately because it's simpler
|
// embedded represents an embedded named type
|
||||||
// (no incoming multiples) and because it's the common case.
|
|
||||||
|
|
||||||
if t, _ := typ.(*Named); t != nil {
|
|
||||||
seen[t] = true
|
|
||||||
if m := lookupMethod(t.methods, pkg, name); m != nil {
|
|
||||||
assert(m.typ != nil)
|
|
||||||
return m, nil, false
|
|
||||||
}
|
|
||||||
typ = t.underlying
|
|
||||||
}
|
|
||||||
|
|
||||||
// embedded named types at the current and next lower depth
|
|
||||||
type embedded struct {
|
type embedded struct {
|
||||||
typ *Named
|
typ *Named // nil means use the outer typ variable instead
|
||||||
index []int // field index sequence
|
index []int // field/method indices, starting with index at depth 0
|
||||||
multiples bool
|
multiples bool // if set, type appears multiple times at its depth
|
||||||
}
|
|
||||||
var current, next []embedded
|
|
||||||
|
|
||||||
switch t := typ.(type) {
|
|
||||||
case *Struct:
|
|
||||||
for i, f := range t.fields {
|
|
||||||
if f.isMatch(pkg, name) {
|
|
||||||
assert(f.typ != nil)
|
|
||||||
return f, []int{i}, false
|
|
||||||
}
|
|
||||||
if f.anonymous {
|
|
||||||
// Ignore embedded basic types - only user-defined
|
|
||||||
// named types can have methods or struct fields.
|
|
||||||
if t, _ := f.typ.Deref().(*Named); t != nil {
|
|
||||||
next = append(next, embedded{t, []int{i}, false})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case *Interface:
|
// Start with typ as single entry at lowest depth.
|
||||||
if m := lookupMethod(t.methods, pkg, name); m != nil {
|
// If typ is not a named type, insert a nil type instead.
|
||||||
assert(m.typ != nil)
|
t, _ := typ.(*Named)
|
||||||
return m, nil, false
|
current := []embedded{{t, nil, false}}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// search the next depth if we don't have a match yet and there's work to do
|
// search current depth if there's work to do
|
||||||
for obj == nil && len(next) > 0 {
|
for len(current) > 0 {
|
||||||
// Consolidate next: collect multiple entries with the same
|
var next []embedded // embedded types found at current depth
|
||||||
// type into a single entry marked as containing multiples.
|
|
||||||
n := 0 // number of entries w/ unique type
|
|
||||||
prev := make(map[*Named]int) // index at which type was previously seen
|
|
||||||
for _, e := range next {
|
|
||||||
if i, found := prev[e.typ]; found {
|
|
||||||
next[i].multiples = true
|
|
||||||
// ignore this entry
|
|
||||||
} else {
|
|
||||||
prev[e.typ] = n
|
|
||||||
next[n] = e
|
|
||||||
n++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// next[:n] is the list of embedded entries to process
|
|
||||||
|
|
||||||
// The underlying arrays of current and next are different, thus
|
// look for (pkg, name) in all types at this depth
|
||||||
// swapping is safe and they never share the same underlying array.
|
|
||||||
current, next = next[:n], current[:0] // don't waste underlying array
|
|
||||||
|
|
||||||
// look for name in all types at this depth
|
|
||||||
for _, e := range current {
|
for _, e := range current {
|
||||||
|
// The very first time only, e.typ may be nil.
|
||||||
|
// In this case, we don't have a named type and
|
||||||
|
// we simply continue with the underlying type.
|
||||||
|
if e.typ != nil {
|
||||||
if seen[e.typ] {
|
if seen[e.typ] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
seen[e.typ] = true
|
seen[e.typ] = true
|
||||||
|
|
||||||
// look for a matching attached method
|
// look for a matching attached method
|
||||||
if m := lookupMethod(e.typ.methods, pkg, name); m != nil {
|
if i, m := lookupMethod(e.typ.methods, pkg, name); m != nil {
|
||||||
// potential match
|
// potential match
|
||||||
assert(m.typ != nil)
|
assert(m.typ != nil)
|
||||||
|
index = concat(e.index, i)
|
||||||
if obj != nil || e.multiples {
|
if obj != nil || e.multiples {
|
||||||
return nil, nil, true
|
obj = nil // collision
|
||||||
|
return
|
||||||
}
|
}
|
||||||
obj = m
|
obj = m
|
||||||
index = nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch t := e.typ.underlying.(type) {
|
// continue with underlying type
|
||||||
|
typ = e.typ.underlying
|
||||||
|
}
|
||||||
|
|
||||||
|
switch t := typ.(type) {
|
||||||
case *Struct:
|
case *Struct:
|
||||||
// look for a matching field and collect embedded types
|
// look for a matching field and collect embedded types
|
||||||
for i, f := range t.fields {
|
for i, f := range t.fields {
|
||||||
if f.isMatch(pkg, name) {
|
if f.isMatch(pkg, name) {
|
||||||
assert(f.typ != nil)
|
assert(f.typ != nil)
|
||||||
|
index = concat(e.index, i)
|
||||||
if obj != nil || e.multiples {
|
if obj != nil || e.multiples {
|
||||||
return nil, nil, true
|
obj = nil // collision
|
||||||
|
return
|
||||||
}
|
}
|
||||||
obj = f
|
obj = f
|
||||||
index = append(index[:0], e.index...)
|
|
||||||
index = append(index, i)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Collect embedded struct fields for searching the next
|
// Collect embedded struct fields for searching the next
|
||||||
|
|
@ -141,29 +111,59 @@ func LookupFieldOrMethod(typ Type, pkg *Package, name string) (obj Object, index
|
||||||
// Ignore embedded basic types - only user-defined
|
// Ignore embedded basic types - only user-defined
|
||||||
// named types can have methods or have struct fields.
|
// named types can have methods or have struct fields.
|
||||||
if t, _ := f.typ.Deref().(*Named); t != nil {
|
if t, _ := f.typ.Deref().(*Named); t != nil {
|
||||||
var copy []int
|
next = append(next, embedded{t, concat(e.index, i), e.multiples})
|
||||||
copy = append(copy, e.index...)
|
|
||||||
copy = append(copy, i)
|
|
||||||
next = append(next, embedded{t, copy, e.multiples})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case *Interface:
|
case *Interface:
|
||||||
// look for a matching method
|
// look for a matching method
|
||||||
if m := lookupMethod(t.methods, pkg, name); m != nil {
|
if i, m := lookupMethod(t.methods, pkg, name); m != nil {
|
||||||
assert(m.typ != nil)
|
assert(m.typ != nil)
|
||||||
|
index = concat(e.index, i)
|
||||||
if obj != nil || e.multiples {
|
if obj != nil || e.multiples {
|
||||||
return nil, nil, true
|
obj = nil // collision
|
||||||
|
return
|
||||||
}
|
}
|
||||||
obj = m
|
obj = m
|
||||||
index = nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
if obj != nil {
|
||||||
|
return // found a match
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consolidate next: collect multiple entries with the same
|
||||||
|
// type into a single entry marked as containing multiples.
|
||||||
|
n := len(next)
|
||||||
|
if n > 1 {
|
||||||
|
n := 0 // number of entries w/ unique type
|
||||||
|
prev := make(map[*Named]int) // index at which type was previously seen
|
||||||
|
for _, e := range next {
|
||||||
|
if i, found := prev[e.typ]; found {
|
||||||
|
next[i].multiples = true
|
||||||
|
// ignore this entry
|
||||||
|
} else {
|
||||||
|
prev[e.typ] = n
|
||||||
|
next[n] = e
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
current = next[:n]
|
||||||
|
}
|
||||||
|
|
||||||
|
index = nil
|
||||||
|
return // not found
|
||||||
|
}
|
||||||
|
|
||||||
|
// concat returns the result of concatenating list and i.
|
||||||
|
// The result does not share its underlying array with list.
|
||||||
|
func concat(list []int, i int) []int {
|
||||||
|
var t []int
|
||||||
|
t = append(t, list...)
|
||||||
|
return append(t, i)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MissingMethod returns (nil, false) if typ implements T, otherwise
|
// MissingMethod returns (nil, false) if typ implements T, otherwise
|
||||||
|
|
@ -182,7 +182,7 @@ func MissingMethod(typ Type, T *Interface) (method *Func, wrongType bool) {
|
||||||
|
|
||||||
if ityp, _ := typ.Underlying().(*Interface); ityp != nil {
|
if ityp, _ := typ.Underlying().(*Interface); ityp != nil {
|
||||||
for _, m := range T.methods {
|
for _, m := range T.methods {
|
||||||
obj := lookupMethod(ityp.methods, m.pkg, m.name)
|
_, obj := lookupMethod(ityp.methods, m.pkg, m.name)
|
||||||
if obj != nil && !IsIdentical(obj.Type(), m.typ) {
|
if obj != nil && !IsIdentical(obj.Type(), m.typ) {
|
||||||
return m, true
|
return m, true
|
||||||
}
|
}
|
||||||
|
|
@ -192,7 +192,7 @@ func MissingMethod(typ Type, T *Interface) (method *Func, wrongType bool) {
|
||||||
|
|
||||||
// a concrete type implements T if it implements all methods of T.
|
// a concrete type implements T if it implements all methods of T.
|
||||||
for _, m := range T.methods {
|
for _, m := range T.methods {
|
||||||
obj, _, _ := LookupFieldOrMethod(typ, m.pkg, m.name)
|
obj, _ := LookupFieldOrMethod(typ, m.pkg, m.name)
|
||||||
if obj == nil {
|
if obj == nil {
|
||||||
return m, false
|
return m, false
|
||||||
}
|
}
|
||||||
|
|
@ -220,17 +220,17 @@ func fieldIndex(fields []*Field, pkg *Package, name string) int {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
// lookupMethod returns the method with matching package and name, or nil.
|
// lookupMethod returns the index of and method with matching package and name, or (-1, nil).
|
||||||
func lookupMethod(methods []*Func, pkg *Package, name string) *Func {
|
func lookupMethod(methods []*Func, pkg *Package, name string) (int, *Func) {
|
||||||
assert(name != "_")
|
assert(name != "_")
|
||||||
for _, m := range methods {
|
for i, m := range methods {
|
||||||
// spec:
|
// spec:
|
||||||
// "Two identifiers are different if they are spelled differently,
|
// "Two identifiers are different if they are spelled differently,
|
||||||
// or if they appear in different packages and are not exported.
|
// or if they appear in different packages and are not exported.
|
||||||
// Otherwise, they are the same."
|
// Otherwise, they are the same."
|
||||||
if m.name == name && (ast.IsExported(name) || m.pkg.path == pkg.path) {
|
if m.name == name && (ast.IsExported(name) || m.pkg.path == pkg.path) {
|
||||||
return m
|
return i, m
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return -1, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ type T1c struct {
|
||||||
|
|
||||||
func (T1c) Pointer /* ERROR "field and method" */ () int { return 0 }
|
func (T1c) Pointer /* ERROR "field and method" */ () int { return 0 }
|
||||||
|
|
||||||
var _ = T1c{}.Pointer
|
var _ = T1c /* ERROR "no single field or method" */ {}.Pointer
|
||||||
|
|
||||||
// T2's method declared before the type.
|
// T2's method declared before the type.
|
||||||
func (*T2) f /* ERROR "field and method" */ () {}
|
func (*T2) f /* ERROR "field and method" */ () {}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue