go.tools/go/types: first cut at sorted method sets
- moved single field and method lookup functionality from operand.go to new file lookup.go and cleaned up the lookup implementation - implemented method set computation using the same basic structure as for field/method lookup, in new file methodset.go - minor related changes - the method set computation ignores pointer-ness of the receiver type at the moment (next CL) - fixed a couple of bugs (missing pkg info for imported embedded types, wrong test for method expressions) The method set computation is currently verified by comparing a regular method lookup with a method-set based method lookup. R=adonovan CC=golang-dev https://golang.org/cl/10235049
This commit is contained in:
parent
7517d8bae3
commit
9a50e157b4
|
|
@ -342,12 +342,12 @@ func (check *checker) builtin(x *operand, call *ast.CallExpr, bin *Builtin, iota
|
|||
goto Error
|
||||
}
|
||||
sel := arg.Sel.Name
|
||||
res := lookupField(x.typ, check.pkg, arg.Sel.Name)
|
||||
if res.index == nil {
|
||||
_, index, _ := LookupFieldOrMethod(x.typ, check.pkg, arg.Sel.Name)
|
||||
if index == nil {
|
||||
check.invalidArg(x.pos(), "%s has no single field %s", x, sel)
|
||||
goto Error
|
||||
}
|
||||
offs := check.ctxt.offsetof(x.typ.Deref(), res.index)
|
||||
offs := check.ctxt.offsetof(x.typ.Deref(), index)
|
||||
if offs < 0 {
|
||||
check.invalidArg(x.pos(), "field %s is embedded via a pointer in %s", sel, x)
|
||||
goto Error
|
||||
|
|
|
|||
|
|
@ -1158,7 +1158,7 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle
|
|||
check.errorf(kv.Pos(), "invalid field name %s in struct literal", kv.Key)
|
||||
continue
|
||||
}
|
||||
i := utyp.index(check.pkg, key.Name)
|
||||
i := fieldIndex(utyp.fields, check.pkg, key.Name)
|
||||
if i < 0 {
|
||||
check.errorf(kv.Pos(), "unknown field %s in struct literal", key.Name)
|
||||
continue
|
||||
|
|
@ -1310,24 +1310,27 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle
|
|||
if x.mode == invalid {
|
||||
goto Error
|
||||
}
|
||||
res := lookupField(x.typ, check.pkg, sel)
|
||||
if res.mode == invalid {
|
||||
// TODO(gri) use collision information for better error message
|
||||
obj, _, _ := LookupFieldOrMethod(x.typ, check.pkg, sel)
|
||||
if obj == nil {
|
||||
check.invalidOp(e.Pos(), "%s has no single field or method %s", x, sel)
|
||||
goto Error
|
||||
}
|
||||
check.callIdent(e.Sel, res.obj)
|
||||
check.callIdent(e.Sel, obj)
|
||||
if x.mode == typexpr {
|
||||
// method expression
|
||||
sig, ok := res.obj.Type().(*Signature)
|
||||
m, ok := obj.(*Func)
|
||||
if !ok {
|
||||
check.invalidOp(e.Pos(), "%s has no method %s", x, sel)
|
||||
goto Error
|
||||
}
|
||||
|
||||
// the receiver type becomes the type of the first function
|
||||
// argument of the method expression's function type
|
||||
// TODO(gri) at the moment, method sets don't correctly track
|
||||
// pointer vs non-pointer receivers => typechecker is too lenient
|
||||
var params []*Var
|
||||
sig := m.typ.(*Signature)
|
||||
if sig.params != nil {
|
||||
params = sig.params.vars
|
||||
}
|
||||
|
|
@ -1339,8 +1342,19 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle
|
|||
}
|
||||
} else {
|
||||
// regular selector
|
||||
x.mode = res.mode
|
||||
x.typ = res.obj.Type()
|
||||
switch obj := obj.(type) {
|
||||
case *Field:
|
||||
x.mode = variable
|
||||
x.typ = obj.typ
|
||||
case *Func:
|
||||
// TODO(gri) Temporary assert to verify corresponding lookup via method sets.
|
||||
// Remove eventually.
|
||||
assert(NewMethodSet(x.typ).Lookup(check.pkg, sel) == obj)
|
||||
x.mode = value
|
||||
x.typ = obj.typ
|
||||
default:
|
||||
unreachable()
|
||||
}
|
||||
}
|
||||
|
||||
case *ast.IndexExpr:
|
||||
|
|
@ -1509,7 +1523,7 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle
|
|||
if typ == Typ[Invalid] {
|
||||
goto Error
|
||||
}
|
||||
if method, wrongType := missingMethod(typ, T); method != nil {
|
||||
if method, wrongType := MissingMethod(typ, T); method != nil {
|
||||
var msg string
|
||||
if wrongType {
|
||||
msg = "%s cannot have dynamic type %s (wrong type for method %s)"
|
||||
|
|
|
|||
|
|
@ -463,8 +463,10 @@ func (p *gcParser) parseField() (*Field, string) {
|
|||
// anonymous field - typ must be T or *T and T must be a type name
|
||||
switch typ := typ.Deref().(type) {
|
||||
case *Basic: // basic types are named types
|
||||
pkg = nil
|
||||
name = typ.name
|
||||
case *Named:
|
||||
pkg = typ.obj.pkg
|
||||
name = typ.obj.name
|
||||
default:
|
||||
p.errorf("anonymous field expected")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,236 @@
|
|||
// 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 various field and method lookup functions.
|
||||
|
||||
package types
|
||||
|
||||
import "go/ast"
|
||||
|
||||
// LookupFieldOrMethod looks up a field or method with given package and name in typ.
|
||||
// If an entry is found, obj is the corresponding *Field or *Func. For fields, index
|
||||
// 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) {
|
||||
if name == "_" {
|
||||
return // empty fields/methods are never found
|
||||
}
|
||||
|
||||
isPtr := false
|
||||
if p, ok := typ.Underlying().(*Pointer); ok {
|
||||
typ = p.base
|
||||
isPtr = true
|
||||
}
|
||||
|
||||
// TODO(gri) consult isPtr for precise method set computation
|
||||
_ = isPtr
|
||||
|
||||
// named types that we have seen already
|
||||
seen := make(map[*Named]bool)
|
||||
|
||||
// We treat the top-most level separately because it's simpler
|
||||
// (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 {
|
||||
typ *Named
|
||||
index []int // field index sequence
|
||||
multiples bool
|
||||
}
|
||||
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:
|
||||
if m := lookupMethod(t.methods, pkg, name); m != nil {
|
||||
assert(m.typ != nil)
|
||||
return m, nil, false
|
||||
}
|
||||
}
|
||||
|
||||
// search the next depth if we don't have a match yet and there's work to do
|
||||
for obj == nil && len(next) > 0 {
|
||||
// Consolidate next: collect multiple entries with the same
|
||||
// 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
|
||||
// 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 {
|
||||
if seen[e.typ] {
|
||||
continue
|
||||
}
|
||||
seen[e.typ] = true
|
||||
|
||||
// look for a matching attached method
|
||||
if m := lookupMethod(e.typ.methods, pkg, name); m != nil {
|
||||
// potential match
|
||||
assert(m.typ != nil)
|
||||
if obj != nil || e.multiples {
|
||||
return nil, nil, true
|
||||
}
|
||||
obj = m
|
||||
index = nil
|
||||
}
|
||||
|
||||
switch t := e.typ.underlying.(type) {
|
||||
case *Struct:
|
||||
// look for a matching field and collect embedded types
|
||||
for i, f := range t.fields {
|
||||
if f.isMatch(pkg, name) {
|
||||
assert(f.typ != nil)
|
||||
if obj != nil || e.multiples {
|
||||
return nil, nil, true
|
||||
}
|
||||
obj = f
|
||||
index = append(index[:0], e.index...)
|
||||
index = append(index, i)
|
||||
continue
|
||||
}
|
||||
// Collect embedded struct fields for searching the next
|
||||
// lower depth, but only if we have not seen a match yet
|
||||
// (if we have a match it is either the desired field or
|
||||
// we have a name collision on the same depth; in either
|
||||
// case we don't need to look further).
|
||||
// Embedded fields are always of the form T or *T where
|
||||
// T is a named type. If e.typ appeared multiple times at
|
||||
// this depth, f.typ appears multiple times at the next
|
||||
// depth.
|
||||
if obj == nil && f.anonymous {
|
||||
// Ignore embedded basic types - only user-defined
|
||||
// named types can have methods or have struct fields.
|
||||
if t, _ := f.typ.Deref().(*Named); t != nil {
|
||||
var copy []int
|
||||
copy = append(copy, e.index...)
|
||||
copy = append(copy, i)
|
||||
next = append(next, embedded{t, copy, e.multiples})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case *Interface:
|
||||
// look for a matching method
|
||||
if m := lookupMethod(t.methods, pkg, name); m != nil {
|
||||
assert(m.typ != nil)
|
||||
if obj != nil || e.multiples {
|
||||
return nil, nil, true
|
||||
}
|
||||
obj = m
|
||||
index = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// MissingMethod returns (nil, false) if typ implements T, otherwise
|
||||
// it returns the first missing method required by T and whether it
|
||||
// is missing or simply has the wrong type.
|
||||
//
|
||||
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?
|
||||
|
||||
// fast path for common case
|
||||
if T.IsEmpty() {
|
||||
return
|
||||
}
|
||||
|
||||
if ityp, _ := typ.Underlying().(*Interface); ityp != nil {
|
||||
for _, m := range T.methods {
|
||||
obj := lookupMethod(ityp.methods, m.pkg, m.name)
|
||||
if obj != nil && !IsIdentical(obj.Type(), m.typ) {
|
||||
return m, true
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// a concrete type implements T if it implements all methods of T.
|
||||
for _, m := range T.methods {
|
||||
obj, _, _ := LookupFieldOrMethod(typ, m.pkg, m.name)
|
||||
if obj == nil {
|
||||
return m, false
|
||||
}
|
||||
if !IsIdentical(obj.Type(), m.typ) {
|
||||
return m, true
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// fieldIndex returns the index for the field with matching package and name, or a value < 0.
|
||||
func fieldIndex(fields []*Field, pkg *Package, name string) int {
|
||||
if name == "_" {
|
||||
return -1 // blank identifiers are never found
|
||||
}
|
||||
for i, f := range fields {
|
||||
// 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 f.name == name && (ast.IsExported(name) || f.pkg.path == pkg.path) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// lookupMethod returns the method with matching package and name, or nil.
|
||||
func lookupMethod(methods []*Func, pkg *Package, name string) *Func {
|
||||
assert(name != "_")
|
||||
for _, m := range methods {
|
||||
// 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 m.name == name && (ast.IsExported(name) || m.pkg.path == pkg.path) {
|
||||
return m
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,265 @@
|
|||
// 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 method sets.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// A MethodSet is an ordered set of methods.
|
||||
type MethodSet struct {
|
||||
list []*Func
|
||||
}
|
||||
|
||||
func (s *MethodSet) String() string {
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprint(&buf, "MethodSet{")
|
||||
if len(s.list) > 0 {
|
||||
fmt.Fprintln(&buf)
|
||||
}
|
||||
for _, m := range s.list {
|
||||
fmt.Fprintf(&buf, "\t%s\n", key(m.pkg, m.name))
|
||||
}
|
||||
fmt.Fprintln(&buf, "}")
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Len returns the number of methods in s.
|
||||
func (s *MethodSet) Len() int { return len(s.list) }
|
||||
|
||||
// At returns the i'th method in s.
|
||||
func (s *MethodSet) At(i int) *Func { return s.list[i] }
|
||||
|
||||
// Lookup returns the method with matching package and name, or nil if not found.
|
||||
func (s *MethodSet) Lookup(pkg *Package, name string) *Func {
|
||||
k := key(pkg, name)
|
||||
|
||||
i := sort.Search(len(s.list), func(i int) bool {
|
||||
m := s.list[i]
|
||||
return key(m.pkg, m.name) >= k
|
||||
})
|
||||
|
||||
if i < len(s.list) {
|
||||
m := s.list[i]
|
||||
if key(m.pkg, m.name) == k {
|
||||
return m
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewMethodSet computes the method set for the given type.
|
||||
// BUG(gri): The pointer-ness of the receiver type is still ignored.
|
||||
func NewMethodSet(typ Type) *MethodSet {
|
||||
isPtr := false
|
||||
if p, ok := typ.Underlying().(*Pointer); ok {
|
||||
typ = p.base
|
||||
isPtr = true
|
||||
}
|
||||
|
||||
// TODO(gri) consult isPtr for precise method set computation
|
||||
_ = isPtr
|
||||
|
||||
// method set up to the current depth
|
||||
// TODO(gri) allocate lazily, method sets are often empty
|
||||
base := make(methodSet)
|
||||
|
||||
// named types that we have seen already
|
||||
seen := make(map[*Named]bool)
|
||||
|
||||
// We treat the top-most level separately because it's simpler
|
||||
// (no incoming multiples) and because it's the common case.
|
||||
|
||||
if t, _ := typ.(*Named); t != nil {
|
||||
seen[t] = true
|
||||
base.add(t.methods, false)
|
||||
typ = t.underlying
|
||||
}
|
||||
|
||||
// embedded named types at the current and next lower depth
|
||||
type embedded struct {
|
||||
typ *Named
|
||||
multiples bool
|
||||
}
|
||||
var current, next []embedded
|
||||
|
||||
switch t := typ.(type) {
|
||||
case *Struct:
|
||||
for _, f := range t.fields {
|
||||
// Fields and methods must be distinct at the most shallow depth.
|
||||
// If they are not, the type checker reported an error before, so
|
||||
// we are ignoring potential conflicts here.
|
||||
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, false})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case *Interface:
|
||||
base.add(t.methods, false)
|
||||
}
|
||||
|
||||
// collect methods at next lower depth
|
||||
for len(next) > 0 {
|
||||
// Consolidate next: collect multiple entries with the same
|
||||
// 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
|
||||
// swapping is safe and they never share the same underlying array.
|
||||
current, next = next[:n], current[:0] // don't waste underlying array
|
||||
|
||||
// field and method sets at this depth
|
||||
fset := make(fieldSet)
|
||||
mset := make(methodSet)
|
||||
|
||||
for _, e := range current {
|
||||
if seen[e.typ] {
|
||||
continue
|
||||
}
|
||||
seen[e.typ] = true
|
||||
|
||||
mset.add(e.typ.methods, e.multiples)
|
||||
|
||||
switch t := e.typ.underlying.(type) {
|
||||
case *Struct:
|
||||
for _, f := range t.fields {
|
||||
fset.add(f, e.multiples)
|
||||
|
||||
// Embedded fields are always of the form T or *T where
|
||||
// T is a named type. If typ appeared multiple times at
|
||||
// this depth, f.Type appears multiple times at the next
|
||||
// depth.
|
||||
if f.anonymous {
|
||||
// Ignore embedded basic types - only user-defined
|
||||
// named types can have methods or have struct fields.
|
||||
if t, _ := f.typ.Deref().(*Named); t != nil {
|
||||
next = append(next, embedded{t, e.multiples})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case *Interface:
|
||||
mset.add(t.methods, e.multiples)
|
||||
}
|
||||
}
|
||||
|
||||
// Add methods and collisions at this depth to base if no entries with matching
|
||||
// names exist already.
|
||||
for k, m := range mset {
|
||||
if _, found := base[k]; !found {
|
||||
// Fields collide with methods of the same name at this depth.
|
||||
if _, found := fset[k]; found {
|
||||
m = nil // collision
|
||||
}
|
||||
base[k] = m
|
||||
}
|
||||
}
|
||||
|
||||
// Multiple fields with matching names collide at this depth and shadow all
|
||||
// entries further down; add them as collisions to base if no entries with
|
||||
// matching names exist already.
|
||||
for k, f := range fset {
|
||||
if f == nil {
|
||||
if _, found := base[k]; !found {
|
||||
base[k] = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// collect methods
|
||||
var list []*Func
|
||||
for _, m := range base {
|
||||
if m != nil {
|
||||
list = append(list, m)
|
||||
}
|
||||
}
|
||||
sort.Sort(byKey(list))
|
||||
|
||||
return &MethodSet{list}
|
||||
}
|
||||
|
||||
// key computes a unique (lookup and sort) key given a package and name.
|
||||
func key(pkg *Package, name string) string {
|
||||
if ast.IsExported(name) {
|
||||
return name
|
||||
}
|
||||
if pkg == nil {
|
||||
panic("unexported object without package information: " + name)
|
||||
}
|
||||
return pkg.path + "." + name
|
||||
}
|
||||
|
||||
// A fieldSet is a set of fields and name collisions.
|
||||
// A conflict indicates that multiple fields with the same package and name appeared.
|
||||
type fieldSet map[string]*Field // a nil entry indicates a name collision
|
||||
|
||||
// Add adds field f to the field set s.
|
||||
// If multiples is set, f appears multiple times
|
||||
// and is treated as a collision at this level.
|
||||
func (s fieldSet) add(f *Field, multiples bool) {
|
||||
k := key(f.pkg, f.name)
|
||||
// if f is not in the set, add it
|
||||
if !multiples {
|
||||
if _, found := s[k]; !found {
|
||||
s[k] = f
|
||||
return
|
||||
}
|
||||
}
|
||||
s[k] = nil // collision
|
||||
}
|
||||
|
||||
// A methodSet is a set of methods and name collisions.
|
||||
// A conflict indicates that multiple methods with the same package and name appeared.
|
||||
type methodSet map[string]*Func // a nil entry indicates a name collision
|
||||
|
||||
// Add adds all methods in list to the method set s.
|
||||
// If multiples is set, every method in list appears multiple times
|
||||
// and is treated as a collision at this level.
|
||||
func (s methodSet) add(list []*Func, multiples bool) {
|
||||
for _, m := range list {
|
||||
k := key(m.pkg, m.name)
|
||||
// if m is not in the set, add it
|
||||
if !multiples {
|
||||
if _, found := s[k]; !found {
|
||||
s[k] = m
|
||||
continue
|
||||
}
|
||||
}
|
||||
s[k] = nil // collision
|
||||
}
|
||||
}
|
||||
|
||||
// byKey function lists can be sorted by key(pkg, name).
|
||||
type byKey []*Func
|
||||
|
||||
func (a byKey) Len() int { return len(a) }
|
||||
func (a byKey) Less(i, j int) bool {
|
||||
x := a[i]
|
||||
y := a[j]
|
||||
return key(x.pkg, x.name) < key(y.pkg, y.name)
|
||||
}
|
||||
func (a byKey) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
|
@ -142,7 +142,7 @@ func (x *operand) isAssignable(ctxt *Context, T Type) bool {
|
|||
// T is an interface type and x implements T
|
||||
// (Do this check first as it might succeed early.)
|
||||
if Ti, ok := Tu.(*Interface); ok {
|
||||
if m, _ := missingMethod(x.typ, Ti); m == nil {
|
||||
if m, _ := MissingMethod(x.typ, Ti); m == nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
@ -206,215 +206,3 @@ func (x *operand) isInteger() bool {
|
|||
isInteger(x.typ) ||
|
||||
x.mode == constant && isRepresentableConst(x.val, nil, UntypedInt) // no context required for UntypedInt
|
||||
}
|
||||
|
||||
// lookupResult represents the result of a struct field/method lookup.
|
||||
type lookupResult struct {
|
||||
mode operandMode
|
||||
obj Object // *Field or *Func; valid if mode != invalid
|
||||
index []int // field index sequence; nil for methods
|
||||
}
|
||||
|
||||
type embeddedType struct {
|
||||
typ *Named
|
||||
index []int // field index sequence
|
||||
multiples bool // if set, typ is embedded multiple times at the same level
|
||||
}
|
||||
|
||||
func lookupMethod(methods []*Func, pkg *Package, name string) *Func {
|
||||
if name == "_" {
|
||||
return nil // blank identifiers are never found
|
||||
}
|
||||
for _, m := range methods {
|
||||
// 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 m.name == name && (ast.IsExported(name) || m.pkg.path == pkg.path) {
|
||||
return m
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// lookupFieldBreadthFirst searches all types in list for a single entry (field
|
||||
// or method) of the given name from the given package. If such a field is found,
|
||||
// the result describes the field mode and type; otherwise the result mode is invalid.
|
||||
// (This function is similar in structure to FieldByNameFunc in reflect/type.go)
|
||||
//
|
||||
func lookupFieldBreadthFirst(list []embeddedType, pkg *Package, name string) (res lookupResult) {
|
||||
// visited records the types that have been searched already.
|
||||
visited := make(map[*Named]bool)
|
||||
|
||||
// embedded types of the next lower level
|
||||
var next []embeddedType
|
||||
|
||||
// potentialMatch is invoked every time a match is found.
|
||||
potentialMatch := func(multiples bool, mode operandMode, obj Object) bool {
|
||||
if multiples || res.mode != invalid {
|
||||
// name appeared already at this level - annihilate
|
||||
res.mode = invalid
|
||||
return false
|
||||
}
|
||||
// first appearance of name
|
||||
res.mode = mode
|
||||
res.obj = obj
|
||||
res.index = nil
|
||||
return true
|
||||
}
|
||||
|
||||
// Search the current level if there is any work to do and collect
|
||||
// embedded types of the next lower level in the next list.
|
||||
for len(list) > 0 {
|
||||
// The res.mode indicates whether we have found a match already
|
||||
// on this level (mode != invalid), or not (mode == invalid).
|
||||
assert(res.mode == invalid)
|
||||
|
||||
// start with empty next list (don't waste underlying array)
|
||||
next = next[:0]
|
||||
|
||||
// look for name in all types at this level
|
||||
for _, e := range list {
|
||||
typ := e.typ
|
||||
if visited[typ] {
|
||||
continue
|
||||
}
|
||||
visited[typ] = true
|
||||
|
||||
// look for a matching attached method
|
||||
if m := lookupMethod(typ.methods, pkg, name); m != nil {
|
||||
assert(m.typ != nil)
|
||||
if !potentialMatch(e.multiples, value, m) {
|
||||
return // name collision
|
||||
}
|
||||
}
|
||||
|
||||
switch t := typ.underlying.(type) {
|
||||
case *Struct:
|
||||
// look for a matching field and collect embedded types
|
||||
for i, f := range t.fields {
|
||||
if f.isMatch(pkg, name) {
|
||||
assert(f.typ != nil)
|
||||
if !potentialMatch(e.multiples, variable, f) {
|
||||
return // name collision
|
||||
}
|
||||
var index []int
|
||||
index = append(index, e.index...) // copy e.index
|
||||
index = append(index, i)
|
||||
res.index = index
|
||||
continue
|
||||
}
|
||||
// Collect embedded struct fields for searching the next
|
||||
// lower level, but only if we have not seen a match yet
|
||||
// (if we have a match it is either the desired field or
|
||||
// we have a name collision on the same level; in either
|
||||
// case we don't need to look further).
|
||||
// Embedded fields are always of the form T or *T where
|
||||
// T is a named type. If typ appeared multiple times at
|
||||
// this level, f.Type appears multiple times at the next
|
||||
// level.
|
||||
if f.anonymous && res.mode == invalid {
|
||||
// Ignore embedded basic types - only user-defined
|
||||
// named types can have methods or have struct fields.
|
||||
if t, _ := f.typ.Deref().(*Named); t != nil {
|
||||
var index []int
|
||||
index = append(index, e.index...) // copy e.index
|
||||
index = append(index, i)
|
||||
next = append(next, embeddedType{t, index, e.multiples})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case *Interface:
|
||||
// look for a matching method
|
||||
if m := lookupMethod(t.methods, pkg, name); m != nil {
|
||||
assert(m.typ != nil)
|
||||
if !potentialMatch(e.multiples, value, m) {
|
||||
return // name collision
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if res.mode != invalid {
|
||||
// we found a single match on this level
|
||||
return
|
||||
}
|
||||
|
||||
// No match and no collision so far.
|
||||
// Compute the list to search for the next level.
|
||||
list = list[:0] // don't waste underlying array
|
||||
for _, e := range next {
|
||||
// Instead of adding the same type multiple times, look for
|
||||
// it in the list and mark it as multiple if it was added
|
||||
// before.
|
||||
// We use a sequential search (instead of a map for next)
|
||||
// because the lists tend to be small, can easily be reused,
|
||||
// and explicit search appears to be faster in this case.
|
||||
if alt := findType(list, e.typ); alt != nil {
|
||||
alt.multiples = true
|
||||
} else {
|
||||
list = append(list, e)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func findType(list []embeddedType, typ *Named) *embeddedType {
|
||||
for i := range list {
|
||||
if p := &list[i]; p.typ == typ {
|
||||
return p
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func lookupField(typ Type, pkg *Package, name string) lookupResult {
|
||||
if name == "_" {
|
||||
return lookupResult{mode: invalid} // empty fields/methods are never found
|
||||
}
|
||||
|
||||
typ = typ.Deref()
|
||||
|
||||
if t, ok := typ.(*Named); ok {
|
||||
if m := lookupMethod(t.methods, pkg, name); m != nil {
|
||||
assert(m.typ != nil)
|
||||
return lookupResult{value, m, nil}
|
||||
}
|
||||
typ = t.underlying
|
||||
}
|
||||
|
||||
switch t := typ.(type) {
|
||||
case *Struct:
|
||||
var next []embeddedType
|
||||
for i, f := range t.fields {
|
||||
if f.isMatch(pkg, name) {
|
||||
return lookupResult{variable, f, []int{i}}
|
||||
}
|
||||
if f.anonymous {
|
||||
// Possible optimization: If the embedded type
|
||||
// is a pointer to the current type we could
|
||||
// ignore it.
|
||||
// Ignore embedded basic types - only user-defined
|
||||
// named types can have methods or have struct fields.
|
||||
if t, _ := f.typ.Deref().(*Named); t != nil {
|
||||
next = append(next, embeddedType{t, []int{i}, false})
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(next) > 0 {
|
||||
return lookupFieldBreadthFirst(next, pkg, name)
|
||||
}
|
||||
|
||||
case *Interface:
|
||||
if m := lookupMethod(t.methods, pkg, name); m != nil {
|
||||
assert(m.typ != nil)
|
||||
return lookupResult{value, m, nil}
|
||||
}
|
||||
}
|
||||
|
||||
// not found
|
||||
return lookupResult{mode: invalid}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -274,41 +274,3 @@ func defaultType(typ Type) Type {
|
|||
}
|
||||
return typ
|
||||
}
|
||||
|
||||
// missingMethod returns (nil, false) if typ implements T, otherwise
|
||||
// it returns the first missing method required by T and whether it
|
||||
// is missing or simply has the wrong type.
|
||||
// TODO(gri) make method of Type and/or stand-alone predicate.
|
||||
//
|
||||
func missingMethod(typ Type, T *Interface) (method *Func, wrongType bool) {
|
||||
// TODO(gri): this needs to correctly compare method names (taking package into account)
|
||||
// TODO(gri): distinguish pointer and non-pointer receivers
|
||||
// an interface type implements T if it has no methods with conflicting signatures
|
||||
// Note: This is stronger than the current spec. Should the spec require this?
|
||||
if T.IsEmpty() {
|
||||
return
|
||||
}
|
||||
// T.methods.NumEntries() > 0
|
||||
|
||||
if ityp, _ := typ.Underlying().(*Interface); ityp != nil {
|
||||
for _, m := range T.methods {
|
||||
res := lookupField(ityp, m.pkg, m.name) // TODO(gri) no need to go via lookupField
|
||||
if res.mode != invalid && !IsIdentical(res.obj.Type(), m.typ) {
|
||||
return m, true
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// a concrete type implements T if it implements all methods of T.
|
||||
for _, m := range T.methods {
|
||||
res := lookupField(typ, m.pkg, m.name)
|
||||
if res.mode == invalid {
|
||||
return m, false
|
||||
}
|
||||
if !IsIdentical(res.obj.Type(), m.typ) {
|
||||
return m, true
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -670,7 +670,7 @@ func (check *checker) stmt(s ast.Stmt) {
|
|||
for _, expr := range clause.List {
|
||||
typ = check.typOrNil(expr, false)
|
||||
if typ != nil && typ != Typ[Invalid] {
|
||||
if method, wrongType := missingMethod(typ, T); method != nil {
|
||||
if method, wrongType := MissingMethod(typ, T); method != nil {
|
||||
var msg string
|
||||
if wrongType {
|
||||
msg = "%s cannot have dynamic type %s (wrong type for method %s)"
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
package decls2
|
||||
|
||||
import "time"
|
||||
import "unsafe"
|
||||
|
||||
// T1 declared before its methods.
|
||||
type T1 struct{
|
||||
|
|
@ -17,6 +18,22 @@ func (T1) m() {}
|
|||
func (T1) m /* ERROR "redeclared" */ () {}
|
||||
func (x *T1) f /* ERROR "field and method" */ () {}
|
||||
|
||||
// Conflict between embedded field and method name,
|
||||
// with the embedded field being a basic type.
|
||||
type T1b struct {
|
||||
int
|
||||
}
|
||||
|
||||
func (T1b) int /* ERROR "field and method" */ () {}
|
||||
|
||||
type T1c struct {
|
||||
unsafe.Pointer
|
||||
}
|
||||
|
||||
func (T1c) Pointer /* ERROR "field and method" */ () int { return 0 }
|
||||
|
||||
var _ = T1c{}.Pointer
|
||||
|
||||
// T2's method declared before the type.
|
||||
func (*T2) f /* ERROR "field and method" */ () {}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
|
||||
package decls3
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// fields with the same name at the same level cancel each other out
|
||||
|
||||
func _() {
|
||||
|
|
@ -44,6 +46,16 @@ func issue4355() {
|
|||
_ = t /* ERROR "no single field or method" */ .X
|
||||
}
|
||||
|
||||
func _() {
|
||||
type Pointer int
|
||||
type A struct{ Pointer }
|
||||
type B struct{ unsafe.Pointer }
|
||||
type T struct{ A; B }
|
||||
|
||||
var t T
|
||||
_ = t /* ERROR "no single field or method" */ .Pointer
|
||||
}
|
||||
|
||||
// Embedded fields can be predeclared types.
|
||||
|
||||
func _() {
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@ func indexes() {
|
|||
|
||||
type T struct {
|
||||
x int
|
||||
y func()
|
||||
}
|
||||
|
||||
func (*T) m() {}
|
||||
|
|
@ -108,6 +109,9 @@ func method_expressions() {
|
|||
_ = T.m
|
||||
var f func(*T) = (*T).m
|
||||
var g func(*T) = ( /* ERROR "cannot initialize" */ T).m
|
||||
|
||||
_ = T /* ERROR "has no method" */ .y
|
||||
_ = ( /* ERROR "has no method" */ *T).y
|
||||
}
|
||||
|
||||
func struct_literals() {
|
||||
|
|
|
|||
|
|
@ -157,24 +157,6 @@ func (s *Struct) Tag(i int) string {
|
|||
return ""
|
||||
}
|
||||
|
||||
// Index returns the index for the field in s with matching package and name.
|
||||
// TODO(gri) should this be exported?
|
||||
func (s *Struct) index(pkg *Package, name string) int {
|
||||
if name == "_" {
|
||||
return -1 // blank identifiers are never found
|
||||
}
|
||||
for i, f := range s.fields {
|
||||
// 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 f.name == name && (ast.IsExported(name) || f.pkg.path == pkg.path) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// A Pointer represents a pointer type.
|
||||
type Pointer struct {
|
||||
base Type
|
||||
|
|
|
|||
Loading…
Reference in New Issue