mirror of
https://github.com/golang/go
synced 2024-11-05 14:56:10 -07:00
go.tools/go/types: remove Type.MethodSet() method.
Method-set caching is now performed externally using a MethodSetCache (if desired), not by the Types themselves. This a minor deoptimization due to the extra maps, but avoids a situation in which method-sets are computed and frozen prematurely. (See b/7114) LGTM=gri R=gri CC=golang-codereviews https://golang.org/cl/61430045
This commit is contained in:
parent
fc1d9c5505
commit
1f29e74bfa
@ -82,8 +82,8 @@ func lockPath(tpkg *types.Package, typ types.Type) typePath {
|
||||
// We're looking for cases in which a reference to this type
|
||||
// can be locked, but a value cannot. This differentiates
|
||||
// embedded interfaces from embedded values.
|
||||
if plock := types.NewPointer(typ).MethodSet().Lookup(tpkg, "Lock"); plock != nil {
|
||||
if lock := typ.MethodSet().Lookup(tpkg, "Lock"); lock == nil {
|
||||
if plock := types.NewMethodSet(types.NewPointer(typ)).Lookup(tpkg, "Lock"); plock != nil {
|
||||
if lock := types.NewMethodSet(typ).Lookup(tpkg, "Lock"); lock == nil {
|
||||
return []types.Type{typ}
|
||||
}
|
||||
}
|
||||
|
@ -310,7 +310,7 @@ func (f *File) isErrorMethodCall(call *ast.CallExpr) bool {
|
||||
// It is part of the workaround for Formatters and should be deleted when
|
||||
// that workaround is no longer necessary. TODO: delete when fixed.
|
||||
func hasMethod(typ types.Type, name string) bool {
|
||||
set := typ.MethodSet()
|
||||
set := types.NewMethodSet(typ)
|
||||
for i := 0; i < set.Len(); i++ {
|
||||
if set.At(i).Obj().Name() == name {
|
||||
return true
|
||||
|
@ -207,7 +207,7 @@ func TestCorrectMethodPackage(t *testing.T) {
|
||||
}
|
||||
|
||||
mutex := imports["sync"].Scope().Lookup("Mutex").(*types.TypeName).Type()
|
||||
mset := types.NewPointer(mutex).MethodSet() // methods of *sync.Mutex
|
||||
mset := types.NewMethodSet(types.NewPointer(mutex)) // methods of *sync.Mutex
|
||||
sel := mset.Lookup(nil, "Lock")
|
||||
lock := sel.Obj().(*types.Func)
|
||||
if got, want := lock.Pkg().Path(), "sync"; got != want {
|
||||
|
@ -278,7 +278,7 @@ func Analyze(config *Config) *Result {
|
||||
if reflect := a.prog.ImportedPackage("reflect"); reflect != nil {
|
||||
rV := reflect.Object.Scope().Lookup("Value")
|
||||
a.reflectValueObj = rV
|
||||
a.reflectValueCall = a.prog.Method(rV.Type().MethodSet().Lookup(nil, "Call"))
|
||||
a.reflectValueCall = a.prog.Method(a.prog.MethodSets.MethodSet(rV.Type()).Lookup(nil, "Call"))
|
||||
a.reflectType = reflect.Object.Scope().Lookup("Type").Type().(*types.Named)
|
||||
a.reflectRtypeObj = reflect.Object.Scope().Lookup("rtype")
|
||||
a.reflectRtypePtr = types.NewPointer(a.reflectRtypeObj.Type())
|
||||
|
@ -720,7 +720,7 @@ func (a *analysis) genInvokeReflectType(caller *cgnode, site *callsite, call *ss
|
||||
a.typeAssert(a.reflectRtypePtr, rtype, recv, true)
|
||||
|
||||
// Look up the concrete method.
|
||||
meth := a.reflectRtypePtr.MethodSet().Lookup(call.Method.Pkg(), call.Method.Name())
|
||||
meth := a.prog.MethodSets.MethodSet(a.reflectRtypePtr).Lookup(call.Method.Pkg(), call.Method.Name())
|
||||
fn := a.prog.Method(meth)
|
||||
|
||||
obj := a.makeFunctionObject(fn, site) // new contour for this call
|
||||
@ -1209,7 +1209,7 @@ func (a *analysis) genFunc(cgn *cgnode) {
|
||||
|
||||
// genMethodsOf generates nodes and constraints for all methods of type T.
|
||||
func (a *analysis) genMethodsOf(T types.Type) {
|
||||
mset := T.MethodSet()
|
||||
mset := a.prog.MethodSets.MethodSet(T)
|
||||
for i, n := 0, mset.Len(); i < n; i++ {
|
||||
a.valueNode(a.prog.Method(mset.At(i)))
|
||||
}
|
||||
|
@ -1568,7 +1568,7 @@ func (c *rtypeMethodByNameConstraint) solve(a *analysis, _ *node, delta nodeset)
|
||||
|
||||
// We don't use Lookup(c.name) when c.name != "" to avoid
|
||||
// ambiguity: >1 unexported methods could match.
|
||||
mset := T.MethodSet()
|
||||
mset := a.prog.MethodSets.MethodSet(T)
|
||||
for i, n := 0, mset.Len(); i < n; i++ {
|
||||
sel := mset.At(i)
|
||||
if c.name == "" || c.name == sel.Obj().Name() {
|
||||
|
@ -312,7 +312,7 @@ func (c *invokeConstraint) solve(a *analysis, n *node, delta nodeset) {
|
||||
}
|
||||
|
||||
// Look up the concrete method.
|
||||
meth := tDyn.MethodSet().Lookup(c.method.Pkg(), c.method.Name())
|
||||
meth := a.prog.MethodSets.MethodSet(tDyn).Lookup(c.method.Pkg(), c.method.Name())
|
||||
if meth == nil {
|
||||
panic(fmt.Sprintf("n%d: type %s has no method %s (iface=n%d)",
|
||||
c.iface, tDyn, c.method, ifaceObj))
|
||||
|
@ -104,7 +104,7 @@ func main() {
|
||||
if !isExt {
|
||||
t.Fatalf("unexpected name type in main package: %s", mem)
|
||||
}
|
||||
mset := types.NewPointer(mem.Type()).MethodSet()
|
||||
mset := prog.MethodSets.MethodSet(types.NewPointer(mem.Type()))
|
||||
for i, n := 0, mset.Len(); i < n; i++ {
|
||||
m := prog.Method(mset.At(i))
|
||||
// For external types, only synthetic wrappers have code.
|
||||
|
@ -177,7 +177,7 @@ func lookupMethod(i *interpreter, typ types.Type, meth *types.Func) *ssa.Functio
|
||||
case errorType:
|
||||
return i.errorMethods[meth.Id()]
|
||||
}
|
||||
return i.prog.Method(typ.MethodSet().Lookup(meth.Pkg(), meth.Name()))
|
||||
return i.prog.Method(i.prog.MethodSets.MethodSet(typ).Lookup(meth.Pkg(), meth.Name()))
|
||||
}
|
||||
|
||||
// visitInstr interprets a single ssa.Instruction within the activation
|
||||
|
@ -119,7 +119,7 @@ func ext۰reflect۰rtype۰NumField(fr *frame, args []value) value {
|
||||
|
||||
func ext۰reflect۰rtype۰NumMethod(fr *frame, args []value) value {
|
||||
// Signature: func (t reflect.rtype) int
|
||||
return args[0].(rtype).t.MethodSet().Len()
|
||||
return fr.i.prog.MethodSets.MethodSet(args[0].(rtype).t).Len()
|
||||
}
|
||||
|
||||
func ext۰reflect۰rtype۰NumOut(fr *frame, args []value) value {
|
||||
@ -330,7 +330,7 @@ func ext۰reflect۰Value۰NumField(fr *frame, args []value) value {
|
||||
|
||||
func ext۰reflect۰Value۰NumMethod(fr *frame, args []value) value {
|
||||
// Signature: func (reflect.Value) int
|
||||
return rV2T(args[0]).t.MethodSet().Len()
|
||||
return fr.i.prog.MethodSets.MethodSet(rV2T(args[0]).t).Len()
|
||||
}
|
||||
|
||||
func ext۰reflect۰Value۰Pointer(fr *frame, args []value) value {
|
||||
|
@ -407,7 +407,7 @@ func WritePackage(buf *bytes.Buffer, p *Package) {
|
||||
case *Type:
|
||||
fmt.Fprintf(buf, " type %-*s %s\n",
|
||||
maxname, name, types.TypeString(p.Object, mem.Type().Underlying()))
|
||||
for _, meth := range IntuitiveMethodSet(mem.Type()) {
|
||||
for _, meth := range IntuitiveMethodSet(mem.Type(), &p.Prog.MethodSets) {
|
||||
fmt.Fprintf(buf, " %s\n", types.SelectionString(p.Object, meth))
|
||||
}
|
||||
|
||||
@ -431,15 +431,15 @@ func WritePackage(buf *bytes.Buffer, p *Package) {
|
||||
//
|
||||
// TODO(gri): move this to go/types?
|
||||
//
|
||||
func IntuitiveMethodSet(T types.Type) []*types.Selection {
|
||||
func IntuitiveMethodSet(T types.Type, msets *types.MethodSetCache) []*types.Selection {
|
||||
var result []*types.Selection
|
||||
mset := T.MethodSet()
|
||||
mset := msets.MethodSet(T)
|
||||
if _, ok := T.Underlying().(*types.Interface); ok {
|
||||
for i, n := 0, mset.Len(); i < n; i++ {
|
||||
result = append(result, mset.At(i))
|
||||
}
|
||||
} else {
|
||||
pmset := types.NewPointer(T).MethodSet()
|
||||
pmset := msets.MethodSet(types.NewPointer(T))
|
||||
for i, n := 0, pmset.Len(); i < n; i++ {
|
||||
meth := pmset.At(i)
|
||||
if m := mset.Lookup(meth.Obj().Pkg(), meth.Obj().Name()); m != nil {
|
||||
|
@ -54,7 +54,7 @@ func (prog *Program) Method(meth *types.Selection) *Function {
|
||||
// EXCLUSIVE_LOCKS_ACQUIRED(prog.methodsMu)
|
||||
//
|
||||
func (prog *Program) makeMethods(T types.Type) bool {
|
||||
tmset := T.MethodSet()
|
||||
tmset := prog.MethodSets.MethodSet(T)
|
||||
n := tmset.Len()
|
||||
if n == 0 {
|
||||
return false // empty (common case)
|
||||
|
@ -119,7 +119,7 @@ func findNamedFunc(pkg *Package, pos token.Pos) *Function {
|
||||
return mem
|
||||
}
|
||||
case *Type:
|
||||
mset := types.NewPointer(mem.Type()).MethodSet()
|
||||
mset := pkg.Prog.MethodSets.MethodSet(types.NewPointer(mem.Type()))
|
||||
for i, n := 0, mset.Len(); i < n; i++ {
|
||||
// Don't call Program.Method: avoid creating wrappers.
|
||||
obj := mset.At(i).Obj().(*types.Func)
|
||||
@ -202,7 +202,7 @@ func (prog *Program) FuncValue(obj *types.Func) *Function {
|
||||
return v.(*Function)
|
||||
}
|
||||
// Interface method wrapper?
|
||||
meth := recvType(obj).MethodSet().Lookup(obj.Pkg(), obj.Name())
|
||||
meth := prog.MethodSets.MethodSet(recvType(obj)).Lookup(obj.Pkg(), obj.Name())
|
||||
return prog.Method(meth)
|
||||
}
|
||||
|
||||
|
@ -22,10 +22,11 @@ import (
|
||||
// A Program is a partial or complete Go program converted to SSA form.
|
||||
//
|
||||
type Program struct {
|
||||
Fset *token.FileSet // position information for the files of this Program
|
||||
imported map[string]*Package // all importable Packages, keyed by import path
|
||||
packages map[*types.Package]*Package // all loaded Packages, keyed by object
|
||||
mode BuilderMode // set of mode bits for SSA construction
|
||||
Fset *token.FileSet // position information for the files of this Program
|
||||
imported map[string]*Package // all importable Packages, keyed by import path
|
||||
packages map[*types.Package]*Package // all loaded Packages, keyed by object
|
||||
mode BuilderMode // set of mode bits for SSA construction
|
||||
MethodSets types.MethodSetCache // cache of type-checker's method-sets
|
||||
|
||||
methodsMu sync.Mutex // guards the following maps:
|
||||
methodSets typemap.M // maps type to its concrete methodSet
|
||||
@ -612,8 +613,8 @@ type ChangeInterface struct {
|
||||
// MakeInterface constructs an instance of an interface type from a
|
||||
// value of a concrete type.
|
||||
//
|
||||
// Use X.Type().MethodSet() to find the method-set of X, and
|
||||
// Program.Method(m) to find the implementation of a method.
|
||||
// Use Program.MethodSets.MethodSet(X.Type()) to find the method-set
|
||||
// of X, and Program.Method(m) to find the implementation of a method.
|
||||
//
|
||||
// To construct the zero value of an interface type T, use:
|
||||
// NewConst(exact.MakeNil(), T, pos)
|
||||
|
@ -42,7 +42,7 @@ func (visit *visitor) program() {
|
||||
}
|
||||
}
|
||||
for _, T := range visit.prog.TypesWithMethodSets() {
|
||||
mset := T.MethodSet()
|
||||
mset := visit.prog.MethodSets.MethodSet(T)
|
||||
for i, n := 0, mset.Len(); i < n; i++ {
|
||||
visit.function(visit.prog.Method(mset.At(i)))
|
||||
}
|
||||
|
@ -392,7 +392,7 @@ func (check *checker) selector(x *operand, e *ast.SelectorExpr) {
|
||||
// TODO(gri) Consider also using a method set cache for the lifetime
|
||||
// of checker once we rely on MethodSet lookup instead of individual
|
||||
// lookup.
|
||||
mset := typ.MethodSet()
|
||||
mset := NewMethodSet(typ)
|
||||
if m := mset.Lookup(check.pkg, sel); m == nil || m.obj != obj {
|
||||
check.dump("%s: (%s).%v -> %s", e.Pos(), typ, obj.name, m)
|
||||
check.dump("%s\n", mset)
|
||||
|
@ -10,7 +10,6 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// A MethodSet is an ordered set of concrete or abstract (interface) methods;
|
||||
@ -63,31 +62,11 @@ func (s *MethodSet) Lookup(pkg *Package, name string) *Selection {
|
||||
// Shared empty method set.
|
||||
var emptyMethodSet MethodSet
|
||||
|
||||
// A cachedMethodSet provides access to a method set
|
||||
// for a given type by computing it once on demand,
|
||||
// and then caching it for future use. Threadsafe.
|
||||
type cachedMethodSet struct {
|
||||
mset *MethodSet
|
||||
mu sync.RWMutex // protects mset
|
||||
}
|
||||
|
||||
// Of returns the (possibly cached) method set for typ.
|
||||
// Threadsafe.
|
||||
func (c *cachedMethodSet) of(typ Type) *MethodSet {
|
||||
c.mu.RLock()
|
||||
mset := c.mset
|
||||
c.mu.RUnlock()
|
||||
if mset == nil {
|
||||
mset = NewMethodSet(typ)
|
||||
c.mu.Lock()
|
||||
c.mset = mset
|
||||
c.mu.Unlock()
|
||||
}
|
||||
return mset
|
||||
}
|
||||
|
||||
// NewMethodSet computes the method set for the given type T.
|
||||
// It always returns a non-nil method set, even if it is empty.
|
||||
// NewMethodSet returns the method set for the given type T. It
|
||||
// always returns a non-nil method set, even if it is empty.
|
||||
//
|
||||
// A MethodSetCache handles repeat queries more efficiently.
|
||||
//
|
||||
func NewMethodSet(T Type) *MethodSet {
|
||||
// WARNING: The code in this function is extremely subtle - do not modify casually!
|
||||
// This function and lookupFieldOrMethod should be kept in sync.
|
||||
|
69
go/types/methodsetcache.go
Normal file
69
go/types/methodsetcache.go
Normal file
@ -0,0 +1,69 @@
|
||||
// Copyright 2014 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 a cache of method sets.
|
||||
|
||||
package types
|
||||
|
||||
import "sync"
|
||||
|
||||
// A MethodSetCache records the method set of each type T for which
|
||||
// MethodSet(T) is called so that repeat queries are fast.
|
||||
// The zero value is a ready-to-use cache instance.
|
||||
type MethodSetCache struct {
|
||||
mu sync.Mutex
|
||||
named map[*Named]struct{ value, pointer *MethodSet } // method sets for named N and *N
|
||||
others map[Type]*MethodSet // all other types
|
||||
}
|
||||
|
||||
// MethodSet returns the method set of type T. It is thread-safe.
|
||||
//
|
||||
// If cache is nil, this function is equivalent to NewMethodSet(T).
|
||||
// Utility functions can thus expose an optional *MethodSetCache
|
||||
// parameter to clients that care about performance.
|
||||
//
|
||||
func (cache *MethodSetCache) MethodSet(T Type) *MethodSet {
|
||||
if cache == nil {
|
||||
return NewMethodSet(T)
|
||||
}
|
||||
cache.mu.Lock()
|
||||
defer cache.mu.Unlock()
|
||||
|
||||
switch T := T.(type) {
|
||||
case *Named:
|
||||
return cache.lookupNamed(T).value
|
||||
|
||||
case *Pointer:
|
||||
if N, ok := T.Elem().(*Named); ok {
|
||||
return cache.lookupNamed(N).pointer
|
||||
}
|
||||
}
|
||||
|
||||
// all other types
|
||||
// (The map uses pointer equivalence, not type identity.)
|
||||
mset := cache.others[T]
|
||||
if mset == nil {
|
||||
mset = NewMethodSet(T)
|
||||
if cache.others == nil {
|
||||
cache.others = make(map[Type]*MethodSet)
|
||||
}
|
||||
cache.others[T] = mset
|
||||
}
|
||||
return mset
|
||||
}
|
||||
|
||||
func (cache *MethodSetCache) lookupNamed(named *Named) struct{ value, pointer *MethodSet } {
|
||||
if cache.named == nil {
|
||||
cache.named = make(map[*Named]struct{ value, pointer *MethodSet })
|
||||
}
|
||||
// Avoid recomputing mset(*T) for each distinct Pointer
|
||||
// instance whose underlying type is a named type.
|
||||
msets, ok := cache.named[named]
|
||||
if !ok {
|
||||
msets.value = NewMethodSet(named)
|
||||
msets.pointer = NewMethodSet(NewPointer(named))
|
||||
cache.named[named] = msets
|
||||
}
|
||||
return msets
|
||||
}
|
@ -14,9 +14,6 @@ type Type interface {
|
||||
// Underlying returns the underlying type of a type.
|
||||
Underlying() Type
|
||||
|
||||
// MethodSet returns the method set of a type.
|
||||
MethodSet() *MethodSet
|
||||
|
||||
// String returns a string representation of a type.
|
||||
String() string
|
||||
}
|
||||
@ -126,8 +123,7 @@ type Struct struct {
|
||||
fields []*Var
|
||||
tags []string // field tags; nil if there are no tags
|
||||
// TODO(gri) access to offsets is not threadsafe - fix this
|
||||
offsets []int64 // field offsets in bytes, lazily initialized
|
||||
mset cachedMethodSet // method set, lazily initialized
|
||||
offsets []int64 // field offsets in bytes, lazily initialized
|
||||
}
|
||||
|
||||
// NewStruct returns a new struct with the given fields and corresponding field tags.
|
||||
@ -163,8 +159,7 @@ func (s *Struct) Tag(i int) string {
|
||||
|
||||
// A Pointer represents a pointer type.
|
||||
type Pointer struct {
|
||||
base Type // element type
|
||||
mset cachedMethodSet // method set, lazily initialized
|
||||
base Type // element type
|
||||
}
|
||||
|
||||
// NewPointer returns a new pointer type for the given element (base) type.
|
||||
@ -249,8 +244,7 @@ type Interface struct {
|
||||
methods []*Func // ordered list of explicitly declared methods
|
||||
embeddeds []*Named // ordered list of explicitly embedded types
|
||||
|
||||
allMethods []*Func // ordered list of methods declared with or embedded in this interface (TODO(gri): replace with mset)
|
||||
mset cachedMethodSet // method set for interface, lazily initialized
|
||||
allMethods []*Func // ordered list of methods declared with or embedded in this interface (TODO(gri): replace with mset)
|
||||
}
|
||||
|
||||
// NewInterface returns a new interface for the given methods and embedded types.
|
||||
@ -364,10 +358,9 @@ func (c *Chan) Elem() Type { return c.elem }
|
||||
|
||||
// A Named represents a named type.
|
||||
type Named struct {
|
||||
obj *TypeName // corresponding declared object
|
||||
underlying Type // possibly a *Named during setup; never a *Named once set up completely
|
||||
methods []*Func // methods declared for this type (not the method set of this type)
|
||||
mset, pmset cachedMethodSet // method set for T, *T, lazily initialized
|
||||
obj *TypeName // corresponding declared object
|
||||
underlying Type // possibly a *Named during setup; never a *Named once set up completely
|
||||
methods []*Func // methods declared for this type (not the method set of this type)
|
||||
}
|
||||
|
||||
// NewNamed returns a new named type for the given type name, underlying type, and associated methods.
|
||||
@ -426,25 +419,6 @@ func (t *Map) Underlying() Type { return t }
|
||||
func (t *Chan) Underlying() Type { return t }
|
||||
func (t *Named) Underlying() Type { return t.underlying }
|
||||
|
||||
func (t *Basic) MethodSet() *MethodSet { return &emptyMethodSet }
|
||||
func (t *Array) MethodSet() *MethodSet { return &emptyMethodSet }
|
||||
func (t *Slice) MethodSet() *MethodSet { return &emptyMethodSet }
|
||||
func (t *Struct) MethodSet() *MethodSet { return t.mset.of(t) }
|
||||
func (t *Pointer) MethodSet() *MethodSet {
|
||||
if named, _ := t.base.(*Named); named != nil {
|
||||
// Avoid recomputing mset(*T) for each distinct Pointer
|
||||
// instance whose underlying type is a named type.
|
||||
return named.pmset.of(t)
|
||||
}
|
||||
return t.mset.of(t)
|
||||
}
|
||||
func (t *Tuple) MethodSet() *MethodSet { return &emptyMethodSet }
|
||||
func (t *Signature) MethodSet() *MethodSet { return &emptyMethodSet }
|
||||
func (t *Interface) MethodSet() *MethodSet { return t.mset.of(t) }
|
||||
func (t *Map) MethodSet() *MethodSet { return &emptyMethodSet }
|
||||
func (t *Chan) MethodSet() *MethodSet { return &emptyMethodSet }
|
||||
func (t *Named) MethodSet() *MethodSet { return t.mset.of(t) }
|
||||
|
||||
func (t *Basic) String() string { return TypeString(nil, t) }
|
||||
func (t *Array) String() string { return TypeString(nil, t) }
|
||||
func (t *Slice) String() string { return TypeString(nil, t) }
|
||||
|
@ -712,7 +712,7 @@ func pathToString(path []ast.Node) string {
|
||||
|
||||
func accessibleMethods(t types.Type, from *types.Package) []*types.Selection {
|
||||
var methods []*types.Selection
|
||||
for _, meth := range ssa.IntuitiveMethodSet(t) {
|
||||
for _, meth := range ssa.IntuitiveMethodSet(t, nil) {
|
||||
if isAccessibleFrom(meth.Obj(), from) {
|
||||
methods = append(methods, meth)
|
||||
}
|
||||
|
@ -47,15 +47,17 @@ func implements(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
||||
}
|
||||
allNamed = append(allNamed, types.Universe.Lookup("error").Type())
|
||||
|
||||
var msets types.MethodSetCache
|
||||
|
||||
// Test each named type.
|
||||
var to, from, fromPtr []types.Type
|
||||
for _, U := range allNamed {
|
||||
if isInterface(T) {
|
||||
if T.MethodSet().Len() == 0 {
|
||||
if msets.MethodSet(T).Len() == 0 {
|
||||
continue // empty interface
|
||||
}
|
||||
if isInterface(U) {
|
||||
if U.MethodSet().Len() == 0 {
|
||||
if msets.MethodSet(U).Len() == 0 {
|
||||
continue // empty interface
|
||||
}
|
||||
|
||||
@ -77,7 +79,7 @@ func implements(o *Oracle, qpos *QueryPos) (queryResult, error) {
|
||||
}
|
||||
}
|
||||
} else if isInterface(U) {
|
||||
if U.MethodSet().Len() == 0 {
|
||||
if msets.MethodSet(U).Len() == 0 {
|
||||
continue // empty interface
|
||||
}
|
||||
|
||||
@ -113,7 +115,7 @@ type implementsResult struct {
|
||||
|
||||
func (r *implementsResult) display(printf printfFunc) {
|
||||
if isInterface(r.t) {
|
||||
if r.t.MethodSet().Len() == 0 {
|
||||
if types.NewMethodSet(r.t).Len() == 0 { // TODO(adonovan): cache mset
|
||||
printf(r.pos, "empty interface type %s", r.t)
|
||||
return
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user