1
0
mirror of https://github.com/golang/go synced 2024-11-19 00:44:40 -07:00

go.tools/go/types: handle blank _ methods in interface types

The spec does not exclude blank _ method names in interfaces
from the uniqueness criteria; i.e., at most one blank method
may appear in an interface type.

Arguably the spec is vague (and possibly incorrect) here.
gccgo handles it the same way. gc crashes with an internal
compiler error.

R=adonovan
CC=golang-dev
https://golang.org/cl/16380043
This commit is contained in:
Robert Griesemer 2013-10-24 09:12:28 -07:00
parent 40b09326ee
commit ad46727525
5 changed files with 56 additions and 44 deletions

View File

@ -520,13 +520,15 @@ func (p *gcParser) parseStructType() Type {
if tags != nil { if tags != nil {
tags = append(tags, tag) tags = append(tags, tag)
} }
if alt := fset.insert(fld); alt != nil { if fld.name != "_" {
pname := "<no pkg name>" if alt := fset.insert(fld); alt != nil {
if pkg := alt.Pkg(); pkg != nil { pname := "<no pkg name>"
pname = pkg.name if pkg := alt.Pkg(); pkg != nil {
pname = pkg.name
}
p.errorf("multiple fields named %s.%s", pname, alt.Name())
continue
} }
p.errorf("multiple fields named %s.%s", pname, alt.Name())
continue
} }
fields = append(fields, fld) fields = append(fields, fld)
} }

View File

@ -12,27 +12,20 @@ package types
// An objset is a set of objects identified by their unique id. // An objset is a set of objects identified by their unique id.
// The zero value for objset is a ready-to-use empty objset. // The zero value for objset is a ready-to-use empty objset.
type objset struct { type objset map[string]Object // initialized lazily
elems map[string]Object // allocated lazily
}
// insert attempts to insert an object obj into objset s. // insert attempts to insert an object obj into objset s.
// If s already contains an alternative object alt with // If s already contains an alternative object alt with
// the same name, insert leaves s unchanged and returns alt. // the same name, insert leaves s unchanged and returns alt.
// Otherwise it inserts obj and returns nil. Objects with // Otherwise it inserts obj and returns nil.
// blank "_" names are ignored.
func (s *objset) insert(obj Object) Object { func (s *objset) insert(obj Object) Object {
name := obj.Name() id := obj.Id()
if name == "_" { if alt := (*s)[id]; alt != nil {
return nil
}
id := Id(obj.Pkg(), name)
if alt := s.elems[id]; alt != nil {
return alt return alt
} }
if s.elems == nil { if *s == nil {
s.elems = make(map[string]Object) *s = make(map[string]Object)
} }
s.elems[id] = obj (*s)[id] = obj
return nil return nil
} }

View File

@ -628,23 +628,27 @@ func (check *checker) typeDecl(obj *TypeName, typ ast.Expr, def *Named, cycleOk
// TODO(gri) consider keeping the objset with the struct instead // TODO(gri) consider keeping the objset with the struct instead
if t, _ := named.underlying.(*Struct); t != nil { if t, _ := named.underlying.(*Struct); t != nil {
for _, fld := range t.fields { for _, fld := range t.fields {
assert(mset.insert(fld) == nil) if fld.name != "_" {
assert(mset.insert(fld) == nil)
}
} }
} }
// check each method // check each method
for _, m := range methods { for _, m := range methods {
if alt := mset.insert(m); alt != nil { if m.name != "_" {
switch alt.(type) { if alt := mset.insert(m); alt != nil {
case *Var: switch alt.(type) {
check.errorf(m.pos, "field and method with the same name %s", m.name) case *Var:
case *Func: check.errorf(m.pos, "field and method with the same name %s", m.name)
check.errorf(m.pos, "method %s already declared for %s", m.name, named) case *Func:
default: check.errorf(m.pos, "method %s already declared for %s", m.name, named)
unreachable() default:
unreachable()
}
check.reportAltDecl(alt)
continue
} }
check.reportAltDecl(alt)
continue
} }
check.recordObject(check.objMap[m].fdecl.Name, m) check.recordObject(check.objMap[m].fdecl.Name, m)
check.objDecl(m, nil, true) check.objDecl(m, nil, true)

View File

@ -194,15 +194,13 @@ func (S0) m2(x *S0 /* ERROR "field or method" */ .m2) {}
func (S0) m3() (x S0 /* ERROR "field or method" */ .m3) { return } func (S0) m3() (x S0 /* ERROR "field or method" */ .m3) { return }
func (S0) m4() (x *S0 /* ERROR "field or method" */ .m4) { return } func (S0) m4() (x *S0 /* ERROR "field or method" */ .m4) { return }
// interfaces may have blank methods // interfaces may have at most one blank method
type BlankI interface { type BlankI interface {
_() _()
_(int) _ /* ERROR redeclared */ ()
_() int
_(int) int
} }
// non-interface types may have blank methods // non-interface types may have multiple blank methods
type BlankT struct{} type BlankT struct{}
func (BlankT) _() {} func (BlankT) _() {}
@ -217,3 +215,12 @@ func _() {
i = x /* ERROR "cannot assign" */ i = x /* ERROR "cannot assign" */
_ = i _ = i
} }
// assignability of interfaces with blank methods
func _() {
var x1, x1b interface { _() }
var x2 interface { _() }
x1 = x2
_ = x1
x1 = x1b
}

View File

@ -54,7 +54,7 @@ func (check *checker) ident(x *operand, e *ast.Ident, def *Named, cycleOk bool)
case *Const: case *Const:
// The constant may be dot-imported. Mark it as used so that // The constant may be dot-imported. Mark it as used so that
// later we can determine if the corresponding dot-imported // later we can determine if the corresponding dot-imported
// packages was used. Same applies for other objects, below. // package was used. Same applies for other objects, below.
// (This code is only used for dot-imports. Without them, we // (This code is only used for dot-imports. Without them, we
// would only have to mark Vars.) // would only have to mark Vars.)
obj.used = true obj.used = true
@ -407,15 +407,12 @@ func (check *checker) collectParams(scope *Scope, list *ast.FieldList, variadicO
return return
} }
func (check *checker) declareInSet(oset *objset, pos token.Pos, id *ast.Ident, obj Object) bool { func (check *checker) declareInSet(oset *objset, pos token.Pos, obj Object) bool {
if alt := oset.insert(obj); alt != nil { if alt := oset.insert(obj); alt != nil {
check.errorf(pos, "%s redeclared", obj.Name()) check.errorf(pos, "%s redeclared", obj.Name())
check.reportAltDecl(alt) check.reportAltDecl(alt)
return false return false
} }
if id != nil {
check.recordObject(id, obj)
}
return true return true
} }
@ -453,10 +450,15 @@ func (check *checker) interfaceType(ityp *ast.InterfaceType, def *Named, cycleOk
// Don't type-check signature yet - use an // Don't type-check signature yet - use an
// empty signature now and update it later. // empty signature now and update it later.
m := NewFunc(pos, check.pkg, name.Name, new(Signature)) m := NewFunc(pos, check.pkg, name.Name, new(Signature))
if check.declareInSet(&mset, pos, name, m) { // spec: "As with all method sets, in an interface type,
// each method must have a unique name."
// (The spec does not exclude blank _ identifiers for
// interface methods.)
if check.declareInSet(&mset, pos, m) {
iface.methods = append(iface.methods, m) iface.methods = append(iface.methods, m)
iface.allMethods = append(iface.allMethods, m) iface.allMethods = append(iface.allMethods, m)
signatures = append(signatures, f.Type) signatures = append(signatures, f.Type)
check.recordObject(name, m)
} }
} else { } else {
// embedded type // embedded type
@ -507,7 +509,7 @@ func (check *checker) interfaceType(ityp *ast.InterfaceType, def *Named, cycleOk
iface.types = append(iface.types, named) iface.types = append(iface.types, named)
// collect embedded methods // collect embedded methods
for _, m := range embed.allMethods { for _, m := range embed.allMethods {
if check.declareInSet(&mset, pos, nil, m) { if check.declareInSet(&mset, pos, m) {
iface.allMethods = append(iface.allMethods, m) iface.allMethods = append(iface.allMethods, m)
} }
} }
@ -583,8 +585,12 @@ func (check *checker) collectFields(list *ast.FieldList, cycleOk bool) (fields [
} }
fld := NewField(pos, check.pkg, name, typ, anonymous) fld := NewField(pos, check.pkg, name, typ, anonymous)
if check.declareInSet(&fset, pos, ident, fld) { // spec: "Within a struct, non-blank field names must be unique."
if name == "_" || check.declareInSet(&fset, pos, fld) {
fields = append(fields, fld) fields = append(fields, fld)
if ident != nil {
check.recordObject(ident, fld)
}
} }
} }