func underlying(v store.Key) store.Key {
	switch v := v.(type) {
	case *ssa.MakeInterface:
		return underlying(v.X)
	case *ssa.TypeAssert:
		return underlying(v.X)
	}
	return v
}
underlying returns the underlying value after type assertion/interface.
package migoinfer
import (
	"bytes"
	"fmt"
	"go/token"
	"go/types"
	"golang.org/x/tools/go/ssa"
	"github.com/nickng/gospal/callctx"
	"github.com/nickng/gospal/funcs"
	"github.com/nickng/gospal/store"
	"github.com/nickng/gospal/store/chans"
	"github.com/nickng/gospal/store/structs"
	"github.com/nickng/migo"
)
type Exported struct {
	names []store.Key
}
func (e *Exported) Export(k store.Key) {
	for _, name := range e.names {
		if name.Name() == k.Name() {
			return
		}
	}
	e.names = append(e.names, k)
}
func (e *Exported) Unexport(k store.Key) {
	for i := 0; i < len(e.names); i++ {
		if e.names[i].Name() == k.Name() {
			e.names = append(e.names[:i], e.names[i+1:]...)
			return
		}
	}
}
func (e *Exported) FindExported(ctx callctx.Context, v store.Value) store.Key {
	if e.names == nil {
		return Unexported{Key: store.MockKey{Description: "Unexported value"}, Value: v}
	}
	for _, name := range e.names {
		if ctx.Get(name) == v {
			return name
		}
	}
	return Unexported{Key: store.MockKey{Description: "Unexported value"}, Value: v}
}
func (e *Exported) String() string {
	var buf bytes.Buffer
	buf.WriteString("┌─ Exported\n")
	for _, n := range e.names {
		buf.WriteString(fmt.Sprintf("│ %s\n", n.Name()))
	}
	buf.WriteString("└─")
	return buf.String()
}
type Unexported struct {
	store.Key
	Value store.Value
}
func migoCall(fn string, blk *ssa.BasicBlock, exported *Exported) migo.Statement {
	var params []*migo.Parameter
	for _, name := range exported.names {
		params = append(params, &migo.Parameter{Caller: name, Callee: name})
	}
	for _, instr := range blk.Instrs {
		switch instr := instr.(type) {
		case *ssa.Phi:
			removed := 0
			for i := range params {
				if instr.Name() == params[i-removed].Caller.Name() {
					params = append(params[:i-removed], params[i-removed+1:]...)
					removed++
				}
			}
		}
	}
	if blk.Index == 0 {
		return &migo.CallStatement{Name: fn, Params: params}
	}
	return &migo.CallStatement{Name: fmt.Sprintf("%s#%d", fn, blk.Index), Params: params}
}
func migoNewChan(v *Logger, name migo.NamedVar, ch *chans.Chan) migo.Statement {
	v.Debugf("%s migo newchan name=%v value=%v", v.Module(), name, ch)
	return &migo.NewChanStatement{Name: name, Chan: ch.UniqName(), Size: ch.Size()}
}
func migoNilChan(v *Instruction, k store.Key) migo.Statement {
	v.Debugf("%s migo nilchan name=%v", v.Module(), k.Name())
	return &migo.NewChanStatement{Name: k, Chan: "nilchan", Size: 0}
}
type freshNilChan struct {
	count int         
	typ   types.Type  
}
func newFreshNilChan(t types.Type) freshNilChan {
	defer func() { nextNilChan++ }()
	return freshNilChan{count: nextNilChan, typ: t}
}
var nextNilChan int
func (n freshNilChan) Name() string     { return fmt.Sprintf("nil%d", n.count) }
func (n freshNilChan) Pos() token.Pos   { return token.NoPos }
func (n freshNilChan) Type() types.Type { return n.typ }
func (n freshNilChan) String() string {
	return fmt.Sprintf("[nilchan%d:%s]", n.count, n.Type().String())
}
func timeChan(ch store.Key) bool {
	isTimeFunc := func(c *ssa.Call) bool {
		fn := c.Call.StaticCallee()
		return fn != nil && fn.Pkg != nil && fn.Pkg.Pkg.Path() == "time"
	}
	switch instr := ch.(type) {
	case *ssa.Call:
		return isTimeFunc(instr)
	case *ssa.UnOp:
		if instr.Op == token.MUL {
			if fa, ok := instr.X.(*ssa.FieldAddr); ok {
				if c, ok := fa.X.(*ssa.Call); ok {
					return isTimeFunc(c)
				}
			}
		}
	}
	return false
}
func migoRecv(v *Instruction, local store.Key, ch store.Value) migo.Statement {
	if timeChan(local) {
		v.Debugf("%s migo recv name=%v (time chan, replace with τ)", v.Module(), local)
		return &migo.TauStatement{}
	}
	v.Debugf("%s migo recv name=%v, value=%s", v.Module(), local, ch.UniqName())
	if c, ok := local.(*ssa.Const); ok {
		if c.IsNil() {
			nc := newFreshNilChan(local.Type())
			v.MiGo.AddStmts(migoNilChan(v, nc))
			return &migo.RecvStatement{Chan: nc.Name()}
		}
	}
	if u, ok := local.(*ssa.UnOp); ok && u.Op == token.MUL {  
		local, ch = u.X, v.Get(u.X)
	}
	switch exported := v.FindExported(v.Context, ch).(type) {
	case Unexported:
		v.Warnf("%s Channel %s/%s unavail. in current scope (unexported)\n\t%s",
			v.Module(), local.Name(), ch.UniqName(), v.Env.getPos(local))
		if _, isField := local.(structs.SField); !isField {  
			v.MiGo.AddStmts(migoNilChan(v, local))
		}
		return &migo.RecvStatement{Chan: local.Name()}
	default:
		v.Debugf("%s Receive %s⇔%s ↦ %s\t%s",
			v.Module(), local.Name(), exported.Name(), ch.UniqName(), local.Type())
		return &migo.RecvStatement{Chan: exported.Name()}
	}
}
func migoSend(v *Instruction, local store.Key, ch store.Value) migo.Statement {
	v.Debugf("%s migo send name=%v, value=%s", v.Module(), local, ch.UniqName())
	if c, ok := local.(*ssa.Const); ok {
		if c.IsNil() {
			nc := newFreshNilChan(local.Type())
			v.MiGo.AddStmts(migoNilChan(v, nc))
			return &migo.SendStatement{Chan: nc.Name()}
		}
	}
	if u, ok := local.(*ssa.UnOp); ok && u.Op == token.MUL {  
		local, ch = u.X, v.Get(u.X)
	}
	switch exported := v.FindExported(v.Context, ch).(type) {
	case Unexported:
		v.Warnf("%s Channel %s/%s unavail. in current scope (unexported)\n\t%s",
			v.Module(), local.Name(), ch.UniqName(), v.Env.getPos(local))
		if _, isField := local.(structs.SField); !isField {  
			v.MiGo.AddStmts(migoNilChan(v, local))
		}
		return &migo.SendStatement{Chan: local.Name()}
	default:
		v.Debugf("%s Send %s⇔%s ↦ %s\t%s",
			v.Module(), local.Name(), exported.Name(), ch.UniqName(), local.Type())
		return &migo.SendStatement{Chan: exported.Name()}
	}
}
func isDefinedMiGoName(v *Instruction, name store.Key) bool {
	for _, param := range v.MiGo.Params {
		if param.Callee.Name() == name.Name() {
			v.Debugf("%s %s was a MiGo parameter in def %s",
				v.Module(), name.Name(), v.MiGo.SimpleName())
			return true
		}
	}
	defined := false
	for _, stmt := range v.MiGo.Stmts {
		switch stmt := stmt.(type) {
		case *migo.NewChanStatement:
			if stmt.Name.Name() == name.Name() {
				v.Debugf("%s %s was a MiGo name defined in def %s",
					v.Module(), name.Name(), v.MiGo.SimpleName())
				defined = true
			}
		case *migo.SpawnStatement:
			for _, param := range stmt.Params {
				if param.Caller.Name() == name.Name() {
					defined = false
				}
			}
		case *migo.CallStatement:
			for _, param := range stmt.Params {
				if param.Caller.Name() == name.Name() {
					defined = false
				}
			}
		}
	}
	return defined
}
func paramsToMigoParam(v *Instruction, fn *Function, call *funcs.Call) []*migo.Parameter {
	convertToMigoParam := func(arg, param store.Key) *migo.Parameter {
		switch ch := v.Get(arg).(type) {
		case store.MockValue:
			if _, isPhi := arg.(*ssa.Phi); isPhi {
				v.Warnf("%s Undefined argument %s is Phi ⇔ %v",
					v.Module(), arg,
					&migo.Parameter{Caller: arg, Callee: param})
			} else {
				field, isField := arg.(structs.SField)
				if isField && field.Key != nil {
				} else {
					v.Warnf("%s Argument %v undefined → nil chan.\n\t%s",
						v.Module(), arg, v.Env.getPos(arg))
					if isField && !isDefinedMiGoName(v, field) {
						v.MiGo.AddStmts(migoNilChan(v, field))
					} else if !isDefinedMiGoName(v, arg) {
						v.MiGo.AddStmts(migoNilChan(v, arg))
					}
				}
			}
		case *chans.Chan:
			if exported := v.FindExported(v.Context, ch); exported != nil {
				arg = exported
			}
		}
		return &migo.Parameter{Caller: arg, Callee: param}
	}
	var migoParams []*migo.Parameter
	for i, arg := range call.Parameters[:call.NParam()+call.NBind()] {
		arg := underlying(arg)
		param := underlying(call.Definition().Param(i))
		if isStruct(arg) && isStruct(param) {
			argStruct := v.Get(arg)
			paramStruct := fn.Get(param)
			if mock, ok := argStruct.(store.MockValue); ok {
				v.Debugf("%s %s is a nil struct (arg) (type:%s)",
					v.Module(), arg.Name(), arg.Type().String())
				argStruct = structs.New(mock, arg)
			} else if _, ok := argStruct.(*structs.Struct); !ok {
				argStruct = structs.New(v.Callee, arg.(ssa.Value))
			}
			if mock, ok := paramStruct.(store.MockValue); ok {
				v.Debugf("%s %s is a nil struct (param) (type:%s)",
					v.Module(), param.Name(), param.Type().String())
				paramStruct = structs.New(mock, param.(ssa.Value))
			} else if _, ok := paramStruct.(*structs.Struct); !ok {
				paramStruct = structs.New(v.Callee, arg.(ssa.Value))
			}
			argFields := argStruct.(*structs.Struct).Expand()
			paramFields := paramStruct.(*structs.Struct).Expand()
			for i := 0; i < len(argFields); i++ {
				switch argField := argFields[i].(type) {
				case structs.SField:
					if isChan(argField) {
						migoParams = append(migoParams, convertToMigoParam(argField, paramFields[i]))
					}
				case *structs.Struct:
				}
			}
		} else if isStruct(arg) && !isStruct(param) && types.IsInterface(param.Type()) {
			v.Debugf("%s Function argument is struct (type:%s), parameter is not (type:%s), likely a wildcard interface{}",
				v.Module(), arg.Type().String(), param.Type().String())
		}
		if isChan(arg) {
			migoParams = append(migoParams, convertToMigoParam(arg, call.Definition().Param(i)))
		}
	}
	for i, param := range call.Parameters[call.NParam()+call.NBind():] {
		if isChan(param) {
			migoParam := &migo.Parameter{Caller: param, Callee: call.Definition().Return(i)}
			if exported := fn.FindExported(fn.Context, fn.Get(call.Definition().Return(i))); exported != nil {
				migoParam.Callee = exported
				for j := range migoParams {
					if migoParams[j].Callee.Name() == exported.Name() {
						migoParam.Caller = migoParams[j].Caller
					}
				}
			}
			migoParams = append(migoParams, migoParam)
		}
	}
	return migoParams
}