blob: 902ed6289f10df97a0c7c035a94c6dfe0b130294 [file] [log] [blame]
package ssa
// This file defines algorithms related to "promotion" of field and
// method selector expressions e.x, such as desugaring implicit field
// and method selections, method-set computation, and construction of
// synthetic "bridge" methods.
import (
"fmt"
"code.google.com/p/go.exp/go/types"
)
// anonFieldPath is a linked list of anonymous fields entered by
// breadth-first traversal has entered, rightmost (outermost) first.
// e.g. "e.f" denoting "e.A.B.C.f" would have a path [C, B, A].
// Common tails may be shared.
//
// It is used by various "promotion"-related algorithms.
//
type anonFieldPath struct {
tail *anonFieldPath
index int // index of field within enclosing types.Struct.Fields
field *types.Field
}
func (p *anonFieldPath) contains(f *types.Field) bool {
for ; p != nil; p = p.tail {
if p.field == f {
return true
}
}
return false
}
// reverse returns the linked list reversed, as a slice.
func (p *anonFieldPath) reverse() []*anonFieldPath {
n := 0
for q := p; q != nil; q = q.tail {
n++
}
s := make([]*anonFieldPath, n)
n = 0
for ; p != nil; p = p.tail {
s[len(s)-1-n] = p
n++
}
return s
}
// isIndirect returns true if the path indirects a pointer.
func (p *anonFieldPath) isIndirect() bool {
for ; p != nil; p = p.tail {
if isPointer(p.field.Type) {
return true
}
}
return false
}
// Method Set construction ----------------------------------------
// A candidate is a method eligible for promotion: a method of an
// abstract (interface) or concrete (anonymous struct or named) type,
// along with the anonymous field path via which it is implicitly
// reached. If there is exactly one candidate for a given id, it will
// be promoted to membership of the original type's method-set.
//
// Candidates with path=nil are trivially members of the original
// type's method-set.
//
type candidate struct {
method *types.Method // method object of abstract or concrete type
concrete *Function // actual method (iff concrete)
path *anonFieldPath // desugared selector path
}
// For debugging.
func (c candidate) String() string {
s := ""
// Inefficient!
for p := c.path; p != nil; p = p.tail {
s = "." + p.field.Name + s
}
return "@" + s + "." + c.method.Name
}
// ptrRecv returns true if this candidate has a pointer receiver.
func (c candidate) ptrRecv() bool {
return c.concrete != nil && isPointer(c.concrete.Signature.Recv.Type)
}
// MethodSet returns the method set for type typ,
// building bridge methods as needed for promoted methods.
// A nil result indicates an empty set.
//
// Thread-safe.
func (p *Program) MethodSet(typ types.Type) MethodSet {
if !canHaveConcreteMethods(typ, true) {
return nil
}
p.methodSetsMu.Lock()
defer p.methodSetsMu.Unlock()
// TODO(adonovan): Using Types as map keys doesn't properly
// de-dup. e.g. *NamedType are canonical but *Struct and
// others are not. Need to de-dup based on using a two-level
// hash-table with hash function types.Type.String and
// equivalence relation types.IsIdentical.
mset := p.methodSets[typ]
if mset == nil {
mset = buildMethodSet(p, typ)
p.methodSets[typ] = mset
}
return mset
}
// buildMethodSet computes the concrete method set for type typ.
// It is the implementation of Program.MethodSet.
//
func buildMethodSet(prog *Program, typ types.Type) MethodSet {
if prog.mode&LogSource != 0 {
defer logStack("buildMethodSet %s %T", typ, typ)()
}
// cands maps ids (field and method names) encountered at any
// level of of the breadth-first traversal to a unique
// promotion candidate. A nil value indicates a "blocked" id
// (i.e. a field or ambiguous method).
//
// nextcands is the same but carries just the level in progress.
cands, nextcands := make(map[Id]*candidate), make(map[Id]*candidate)
var next, list []*anonFieldPath
list = append(list, nil) // hack: nil means "use typ"
// For each level of the type graph...
for len(list) > 0 {
// Invariant: next=[], nextcands={}.
// Collect selectors from one level into 'nextcands'.
// Record the next levels into 'next'.
for _, node := range list {
t := typ // first time only
if node != nil {
t = node.field.Type
}
t = deref(t)
if nt, ok := t.(*types.NamedType); ok {
for _, meth := range nt.Methods {
addCandidate(nextcands, IdFromQualifiedName(meth.QualifiedName), meth, prog.concreteMethods[meth], node)
}
t = nt.Underlying
}
switch t := t.(type) {
case *types.Interface:
for _, meth := range t.Methods {
addCandidate(nextcands, IdFromQualifiedName(meth.QualifiedName), meth, nil, node)
}
case *types.Struct:
for i, f := range t.Fields {
nextcands[IdFromQualifiedName(f.QualifiedName)] = nil // a field: block id
// Queue up anonymous fields for next iteration.
// Break cycles to ensure termination.
if f.IsAnonymous && !node.contains(f) {
next = append(next, &anonFieldPath{node, i, f})
}
}
}
}
// Examine collected selectors.
// Promote unique, non-blocked ones to cands.
for id, cand := range nextcands {
delete(nextcands, id)
if cand == nil {
// Update cands so we ignore it at all deeper levels.
// Don't clobber existing (shallower) binding!
if _, ok := cands[id]; !ok {
cands[id] = nil // block id
}
continue
}
if _, ok := cands[id]; ok {
// Ignore candidate: a shallower binding exists.
} else {
cands[id] = cand
}
}
list, next = next, list[:0] // reuse array
}
// Build method sets and bridge methods.
mset := make(MethodSet)
for id, cand := range cands {
if cand == nil {
continue // blocked; ignore
}
if cand.ptrRecv() && !(isPointer(typ) || cand.path.isIndirect()) {
// A candidate concrete method f with receiver
// *C is promoted into the method set of
// (non-pointer) E iff the implicit path selection
// is indirect, e.g. e.A->B.C.f
continue
}
var method *Function
if cand.path == nil {
// Trivial member of method-set; no bridge needed.
method = cand.concrete
} else {
method = makeBridgeMethod(prog, typ, cand)
}
if method == nil {
panic("unexpected nil method in method set")
}
mset[id] = method
}
return mset
}
// addCandidate adds the promotion candidate (method, node) to m[id].
// If m[id] already exists (whether nil or not), m[id] is set to nil.
// If method denotes a concrete method, concrete is its implementation.
//
func addCandidate(m map[Id]*candidate, id Id, method *types.Method, concrete *Function, node *anonFieldPath) {
prev, found := m[id]
switch {
case prev != nil:
// Two candidates for same selector: ambiguous; block it.
m[id] = nil
case found:
// Already blocked.
default:
// A viable candidate.
m[id] = &candidate{method, concrete, node}
}
}
// makeBridgeMethod creates a synthetic Function that delegates to a
// "promoted" method. For example, given these decls:
//
// type A struct {B}
// type B struct {*C}
// type C ...
// func (*C) f()
//
// then makeBridgeMethod(typ=A, cand={method:(*C).f, path:[B,*C]}) will
// synthesize this bridge method:
//
// func (a A) f() { return a.B.C->f() }
//
// prog is the program to which the synthesized method will belong.
// typ is the receiver type of the bridge method. cand is the
// candidate method to be promoted; it may be concrete or an interface
// method.
//
func makeBridgeMethod(prog *Program, typ types.Type, cand *candidate) *Function {
sig := *cand.method.Type // make a copy, sharing underlying Values
sig.Recv = &types.Var{Name: "recv", Type: typ}
if prog.mode&LogSource != 0 {
defer logStack("makeBridgeMethod %s, %s, type %s", typ, cand, &sig)()
}
fn := &Function{
Name_: cand.method.Name,
Signature: &sig,
Prog: prog,
}
fn.start(nil)
fn.addSpilledParam(sig.Recv)
createParams(fn)
// Each bridge method performs a sequence of selections,
// then tailcalls the promoted method.
// We use pointer arithmetic (FieldAddr possibly followed by
// Load) in preference to value extraction (Field possibly
// preceded by Load).
var v Value = fn.Locals[0] // spilled receiver
if isPointer(typ) {
v = emitLoad(fn, v)
}
// Iterate over selections e.A.B.C.f in the natural order [A,B,C].
for _, p := range cand.path.reverse() {
// Loop invariant: v holds a pointer to a struct.
if _, ok := underlyingType(indirectType(v.Type())).(*types.Struct); !ok {
panic(fmt.Sprint("not a *struct: ", v.Type(), p.field.Type))
}
sel := &FieldAddr{
X: v,
Field: p.index,
}
sel.setType(pointer(p.field.Type))
v = fn.emit(sel)
if isPointer(p.field.Type) {
v = emitLoad(fn, v)
}
}
if !cand.ptrRecv() {
v = emitLoad(fn, v)
}
var c Call
if cand.concrete != nil {
c.Func = cand.concrete
fn.Pos = c.Func.(*Function).Pos // TODO(adonovan): fix: wrong.
c.Pos = fn.Pos // TODO(adonovan): fix: wrong.
c.Args = append(c.Args, v)
} else {
c.Recv = v
c.Method = 0
}
emitTailCall(fn, &c)
fn.finish()
return fn
}
// createParams creates parameters for bridge method fn based on its Signature.
func createParams(fn *Function) {
var last *Parameter
for i, p := range fn.Signature.Params {
name := p.Name
if name == "" {
name = fmt.Sprintf("arg%d", i)
}
last = fn.addParam(name, p.Type)
}
if fn.Signature.IsVariadic {
last.Type_ = &types.Slice{Elt: last.Type_}
}
}
// Thunks for standalone interface methods ----------------------------------------
// makeImethodThunk returns a synthetic thunk function permitting an
// method id of interface typ to be called like a standalone function,
// e.g.:
//
// type I interface { f(x int) R }
// m := I.f // thunk
// var i I
// m(i, 0)
//
// The thunk is defined as if by:
//
// func I.f(i I, x int, ...) R {
// return i.f(x, ...)
// }
//
// The generated thunks do not belong to any package. (Arguably they
// belong in the package that defines the interface, but we have no
// way to determine that on demand; we'd have to create all possible
// thunks a priori.)
//
// TODO(adonovan): opt: currently the stub is created even when used
// in call position: I.f(i, 0). Clearly this is suboptimal.
//
// TODO(adonovan): memoize creation of these functions in the Program.
//
func makeImethodThunk(prog *Program, typ types.Type, id Id) *Function {
if prog.mode&LogSource != 0 {
defer logStack("makeImethodThunk %s.%s", typ, id)()
}
itf := underlyingType(typ).(*types.Interface)
index, meth := methodIndex(itf, itf.Methods, id)
sig := *meth.Type // copy; shared Values
fn := &Function{
Name_: meth.Name,
Signature: &sig,
Prog: prog,
}
// TODO(adonovan): set fn.Pos to location of interface method ast.Field.
fn.start(nil)
fn.addParam("recv", typ)
createParams(fn)
var c Call
c.Method = index
c.Recv = fn.Params[0]
emitTailCall(fn, &c)
fn.finish()
return fn
}
// Implicit field promotion ----------------------------------------
// For a given struct type and (promoted) field Id, findEmbeddedField
// returns the path of implicit anonymous field selections, and the
// field index of the explicit (=outermost) selection.
//
// TODO(gri): if go/types/operand.go's lookupFieldBreadthFirst were to
// record (e.g. call a client-provided callback) the implicit field
// selection path discovered for a particular ast.SelectorExpr, we could
// eliminate this function.
//
func findPromotedField(st *types.Struct, id Id) (*anonFieldPath, int) {
// visited records the types that have been searched already.
// Invariant: keys are all *types.NamedType.
// (types.Type is not a sound map key in general.)
visited := make(map[types.Type]bool)
var list, next []*anonFieldPath
for i, f := range st.Fields {
if f.IsAnonymous {
list = append(list, &anonFieldPath{nil, i, f})
}
}
// 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 {
// look for name in all types at this level
for _, node := range list {
typ := deref(node.field.Type).(*types.NamedType)
if visited[typ] {
continue
}
visited[typ] = true
switch typ := typ.Underlying.(type) {
case *types.Struct:
for i, f := range typ.Fields {
if IdFromQualifiedName(f.QualifiedName) == id {
return node, i
}
}
for i, f := range typ.Fields {
if f.IsAnonymous {
next = append(next, &anonFieldPath{node, i, f})
}
}
}
}
if len(next) == 0 {
panic("field not found: " + id.String())
}
// No match so far.
list, next = next, list[:0] // reuse arrays
}
panic("unreachable")
}