1
0
mirror of https://github.com/golang/go synced 2024-11-18 15:54:42 -07:00

exp/ssa: (#2 of 5): core utilities

This CL includes the implementation of Literal, all the
Value.String and Instruction.String methods, the sanity
checker, and other misc utilities.

R=gri, iant, iant
CC=golang-dev
https://golang.org/cl/7199052
This commit is contained in:
Alan Donovan 2013-01-28 18:06:14 -05:00
parent 961195ae69
commit 66bf59712e
5 changed files with 961 additions and 15 deletions

137
src/pkg/exp/ssa/literal.go Normal file
View File

@ -0,0 +1,137 @@
package ssa
// This file defines the Literal SSA value type.
import (
"fmt"
"go/types"
"math/big"
"strconv"
)
// newLiteral returns a new literal of the specified value and type.
// val must be valid according to the specification of Literal.Value.
//
func newLiteral(val interface{}, typ types.Type) *Literal {
// This constructor exists to provide a single place to
// insert logging/assertions during debugging.
return &Literal{typ, val}
}
// intLiteral returns an untyped integer literal that evaluates to i.
func intLiteral(i int64) *Literal {
return newLiteral(i, types.Typ[types.UntypedInt])
}
// nilLiteral returns a nil literal of the specified (reference) type.
func nilLiteral(typ types.Type) *Literal {
return newLiteral(types.NilType{}, typ)
}
func (l *Literal) Name() string {
var s string
switch x := l.Value.(type) {
case bool:
s = fmt.Sprintf("%v", l.Value)
case int64:
s = fmt.Sprintf("%d", l.Value)
case *big.Int:
s = x.String()
case *big.Rat:
s = x.FloatString(20)
case string:
if len(x) > 20 {
x = x[:17] + "..." // abbreviate
}
s = strconv.Quote(x)
case types.Complex:
r := x.Re.FloatString(20)
i := x.Im.FloatString(20)
s = fmt.Sprintf("%s+%si", r, i)
case types.NilType:
s = "nil"
default:
panic(fmt.Sprintf("unexpected literal value: %T", x))
}
return s + ":" + l.Type_.String()
}
func (l *Literal) Type() types.Type {
return l.Type_
}
// IsNil returns true if this literal represents a typed or untyped nil value.
func (l *Literal) IsNil() bool {
_, ok := l.Value.(types.NilType)
return ok
}
// Int64 returns the numeric value of this literal truncated to fit
// a signed 64-bit integer.
//
func (l *Literal) Int64() int64 {
switch x := l.Value.(type) {
case int64:
return x
case *big.Int:
return x.Int64()
case *big.Rat:
// TODO(adonovan): fix: is this the right rounding mode?
var q big.Int
return q.Quo(x.Num(), x.Denom()).Int64()
}
panic(fmt.Sprintf("unexpected literal value: %T", l.Value))
}
// Uint64 returns the numeric value of this literal truncated to fit
// an unsigned 64-bit integer.
//
func (l *Literal) Uint64() uint64 {
switch x := l.Value.(type) {
case int64:
if x < 0 {
return 0
}
return uint64(x)
case *big.Int:
return x.Uint64()
case *big.Rat:
// TODO(adonovan): fix: is this right?
var q big.Int
return q.Quo(x.Num(), x.Denom()).Uint64()
}
panic(fmt.Sprintf("unexpected literal value: %T", l.Value))
}
// Float64 returns the numeric value of this literal truncated to fit
// a float64.
//
func (l *Literal) Float64() float64 {
switch x := l.Value.(type) {
case int64:
return float64(x)
case *big.Int:
var r big.Rat
f, _ := r.SetInt(x).Float64()
return f
case *big.Rat:
f, _ := x.Float64()
return f
}
panic(fmt.Sprintf("unexpected literal value: %T", l.Value))
}
// Complex128 returns the complex value of this literal truncated to
// fit a complex128.
//
func (l *Literal) Complex128() complex128 {
switch x := l.Value.(type) {
case int64, *big.Int, *big.Rat:
return complex(l.Float64(), 0)
case types.Complex:
re64, _ := x.Re.Float64()
im64, _ := x.Im.Float64()
return complex(re64, im64)
}
panic(fmt.Sprintf("unexpected literal value: %T", l.Value))
}

383
src/pkg/exp/ssa/print.go Normal file
View File

@ -0,0 +1,383 @@
package ssa
// This file implements the String() methods for all Value and
// Instruction types.
import (
"bytes"
"fmt"
"go/ast"
"go/types"
)
func (id Id) String() string {
if id.Pkg == nil {
return id.Name
}
return fmt.Sprintf("%s/%s", id.Pkg.Path, id.Name)
}
// relName returns the name of v relative to i.
// In most cases, this is identical to v.Name(), but for cross-package
// references to Functions (including methods) and Globals, the
// package-qualified FullName is used instead.
//
func relName(v Value, i Instruction) string {
switch v := v.(type) {
case *Global:
if v.Pkg == i.Block().Func.Pkg {
return v.Name()
}
return v.FullName()
case *Function:
if v.Pkg == nil || v.Pkg == i.Block().Func.Pkg {
return v.Name()
}
return v.FullName()
}
return v.Name()
}
// Value.String()
//
// This method is provided only for debugging.
// It never appears in disassembly, which uses Value.Name().
func (v *Literal) String() string {
return fmt.Sprintf("literal %s rep=%T", v.Name(), v.Value)
}
func (v *Parameter) String() string {
return fmt.Sprintf("parameter %s : %s", v.Name(), v.Type())
}
func (v *Capture) String() string {
return fmt.Sprintf("capture %s : %s", v.Name(), v.Type())
}
func (v *Global) String() string {
return fmt.Sprintf("global %s : %s", v.Name(), v.Type())
}
func (v *Builtin) String() string {
return fmt.Sprintf("builtin %s : %s", v.Name(), v.Type())
}
func (r *Function) String() string {
return fmt.Sprintf("function %s : %s", r.Name(), r.Type())
}
// FullName returns the name of this function qualified by the
// package name, unless it is anonymous or synthetic.
//
// TODO(adonovan): move to func.go when it's submitted.
//
func (f *Function) FullName() string {
if f.Enclosing != nil || f.Pkg == nil {
return f.Name_ // anonymous or synthetic
}
return fmt.Sprintf("%s.%s", f.Pkg.ImportPath, f.Name_)
}
// FullName returns g's package-qualified name.
func (g *Global) FullName() string {
return fmt.Sprintf("%s.%s", g.Pkg.ImportPath, g.Name_)
}
// Instruction.String()
func (v *Alloc) String() string {
op := "local"
if v.Heap {
op = "new"
}
return fmt.Sprintf("%s %s", op, indirectType(v.Type()))
}
func (v *Phi) String() string {
var b bytes.Buffer
b.WriteString("phi [")
for i, edge := range v.Edges {
if i > 0 {
b.WriteString(", ")
}
// Be robust against malformed CFG.
blockname := "?"
if v.Block_ != nil && i < len(v.Block_.Preds) {
blockname = v.Block_.Preds[i].Name
}
b.WriteString(blockname)
b.WriteString(": ")
b.WriteString(relName(edge, v))
}
b.WriteString("]")
return b.String()
}
func printCall(v *CallCommon, prefix string, instr Instruction) string {
var b bytes.Buffer
b.WriteString(prefix)
if v.Func != nil {
b.WriteString(relName(v.Func, instr))
} else {
name := underlyingType(v.Recv.Type()).(*types.Interface).Methods[v.Method].Name
fmt.Fprintf(&b, "invoke %s.%s [#%d]", relName(v.Recv, instr), name, v.Method)
}
b.WriteString("(")
for i, arg := range v.Args {
if i > 0 {
b.WriteString(", ")
}
b.WriteString(relName(arg, instr))
}
if v.HasEllipsis {
b.WriteString("...")
}
b.WriteString(")")
return b.String()
}
func (v *Call) String() string {
return printCall(&v.CallCommon, "", v)
}
func (v *BinOp) String() string {
return fmt.Sprintf("%s %s %s", relName(v.X, v), v.Op.String(), relName(v.Y, v))
}
func (v *UnOp) String() string {
return fmt.Sprintf("%s%s%s", v.Op, relName(v.X, v), commaOk(v.CommaOk))
}
func (v *Conv) String() string {
return fmt.Sprintf("convert %s <- %s (%s)", v.Type(), v.X.Type(), relName(v.X, v))
}
func (v *ChangeInterface) String() string {
return fmt.Sprintf("change interface %s <- %s (%s)", v.Type(), v.X.Type(), relName(v.X, v))
}
func (v *MakeInterface) String() string {
return fmt.Sprintf("make interface %s <- %s (%s)", v.Type(), v.X.Type(), relName(v.X, v))
}
func (v *MakeClosure) String() string {
var b bytes.Buffer
fmt.Fprintf(&b, "make closure %s", relName(v.Fn, v))
if v.Bindings != nil {
b.WriteString(" [")
for i, c := range v.Bindings {
if i > 0 {
b.WriteString(", ")
}
b.WriteString(relName(c, v))
}
b.WriteString("]")
}
return b.String()
}
func (v *MakeSlice) String() string {
var b bytes.Buffer
b.WriteString("make slice ")
b.WriteString(v.Type().String())
b.WriteString(" ")
b.WriteString(relName(v.Len, v))
b.WriteString(" ")
b.WriteString(relName(v.Cap, v))
return b.String()
}
func (v *Slice) String() string {
var b bytes.Buffer
b.WriteString("slice ")
b.WriteString(relName(v.X, v))
b.WriteString("[")
if v.Low != nil {
b.WriteString(relName(v.Low, v))
}
b.WriteString(":")
if v.High != nil {
b.WriteString(relName(v.High, v))
}
b.WriteString("]")
return b.String()
}
func (v *MakeMap) String() string {
res := ""
if v.Reserve != nil {
res = relName(v.Reserve, v)
}
return fmt.Sprintf("make %s %s", v.Type(), res)
}
func (v *MakeChan) String() string {
return fmt.Sprintf("make %s %s", v.Type(), relName(v.Size, v))
}
func (v *FieldAddr) String() string {
fields := underlyingType(indirectType(v.X.Type())).(*types.Struct).Fields
// Be robust against a bad index.
name := "?"
if v.Field >= 0 && v.Field < len(fields) {
name = fields[v.Field].Name
}
return fmt.Sprintf("&%s.%s [#%d]", relName(v.X, v), name, v.Field)
}
func (v *Field) String() string {
fields := underlyingType(v.X.Type()).(*types.Struct).Fields
// Be robust against a bad index.
name := "?"
if v.Field >= 0 && v.Field < len(fields) {
name = fields[v.Field].Name
}
return fmt.Sprintf("%s.%s [#%d]", relName(v.X, v), name, v.Field)
}
func (v *IndexAddr) String() string {
return fmt.Sprintf("&%s[%s]", relName(v.X, v), relName(v.Index, v))
}
func (v *Index) String() string {
return fmt.Sprintf("%s[%s]", relName(v.X, v), relName(v.Index, v))
}
func (v *Lookup) String() string {
return fmt.Sprintf("%s[%s]%s", relName(v.X, v), relName(v.Index, v), commaOk(v.CommaOk))
}
func (v *Range) String() string {
return "range " + relName(v.X, v)
}
func (v *Next) String() string {
return "next " + relName(v.Iter, v)
}
func (v *TypeAssert) String() string {
return fmt.Sprintf("typeassert%s %s.(%s)", commaOk(v.CommaOk), relName(v.X, v), v.AssertedType)
}
func (v *Extract) String() string {
return fmt.Sprintf("extract %s #%d", relName(v.Tuple, v), v.Index)
}
func (s *Jump) String() string {
// Be robust against malformed CFG.
blockname := "?"
if s.Block_ != nil && len(s.Block_.Succs) == 1 {
blockname = s.Block_.Succs[0].Name
}
return fmt.Sprintf("jump %s", blockname)
}
func (s *If) String() string {
// Be robust against malformed CFG.
tblockname, fblockname := "?", "?"
if s.Block_ != nil && len(s.Block_.Succs) == 2 {
tblockname = s.Block_.Succs[0].Name
fblockname = s.Block_.Succs[1].Name
}
return fmt.Sprintf("if %s goto %s else %s", relName(s.Cond, s), tblockname, fblockname)
}
func (s *Go) String() string {
return printCall(&s.CallCommon, "go ", s)
}
func (s *Ret) String() string {
var b bytes.Buffer
b.WriteString("ret")
for i, r := range s.Results {
if i == 0 {
b.WriteString(" ")
} else {
b.WriteString(", ")
}
b.WriteString(relName(r, s))
}
return b.String()
}
func (s *Send) String() string {
return fmt.Sprintf("send %s <- %s", relName(s.Chan, s), relName(s.X, s))
}
func (s *Defer) String() string {
return printCall(&s.CallCommon, "defer ", s)
}
func (s *Select) String() string {
var b bytes.Buffer
for i, st := range s.States {
if i > 0 {
b.WriteString(", ")
}
if st.Dir == ast.RECV {
b.WriteString("<-")
b.WriteString(relName(st.Chan, s))
} else {
b.WriteString(relName(st.Chan, s))
b.WriteString("<-")
b.WriteString(relName(st.Send, s))
}
}
non := ""
if !s.Blocking {
non = "non"
}
return fmt.Sprintf("select %sblocking [%s]", non, b.String())
}
func (s *Store) String() string {
return fmt.Sprintf("*%s = %s", relName(s.Addr, s), relName(s.Val, s))
}
func (s *MapUpdate) String() string {
return fmt.Sprintf("%s[%s] = %s", relName(s.Map, s), relName(s.Key, s), relName(s.Value, s))
}
func (p *Package) String() string {
// TODO(adonovan): prettify output.
var b bytes.Buffer
fmt.Fprintf(&b, "Package %s at %s:\n", p.ImportPath, p.Prog.Files.File(p.Pos).Name())
// TODO(adonovan): make order deterministic.
maxname := 0
for name := range p.Members {
if l := len(name); l > maxname {
maxname = l
}
}
for name, mem := range p.Members {
switch mem := mem.(type) {
case *Literal:
fmt.Fprintf(&b, " const %-*s %s\n", maxname, name, mem.Name())
case *Function:
fmt.Fprintf(&b, " func %-*s %s\n", maxname, name, mem.Type())
case *Type:
fmt.Fprintf(&b, " type %-*s %s\n", maxname, name, mem.NamedType.Underlying)
// TODO(adonovan): make order deterministic.
for name, method := range mem.Methods {
fmt.Fprintf(&b, " method %s %s\n", name, method.Signature)
}
case *Global:
fmt.Fprintf(&b, " var %-*s %s\n", maxname, name, mem.Type())
}
}
return b.String()
}
func commaOk(x bool) string {
if x {
return ",ok"
}
return ""
}

263
src/pkg/exp/ssa/sanity.go Normal file
View File

@ -0,0 +1,263 @@
package ssa
// An optional pass for sanity checking invariants of the SSA representation.
// Currently it checks CFG invariants but little at the instruction level.
import (
"bytes"
"fmt"
"io"
"os"
)
type sanity struct {
reporter io.Writer
fn *Function
block *BasicBlock
insane bool
}
// SanityCheck performs integrity checking of the SSA representation
// of the function fn and returns true if it was valid. Diagnostics
// are written to reporter if non-nil, os.Stderr otherwise. Some
// diagnostics are only warnings and do not imply a negative result.
//
// Sanity checking is intended to facilitate the debugging of code
// transformation passes.
//
func SanityCheck(fn *Function, reporter io.Writer) bool {
if reporter == nil {
reporter = os.Stderr
}
return (&sanity{reporter: reporter}).checkFunction(fn)
}
// MustSanityCheck is like SanityCheck but panics instead of returning
// a negative result.
//
func MustSanityCheck(fn *Function, reporter io.Writer) {
if !SanityCheck(fn, reporter) {
panic("SanityCheck failed")
}
}
// blockNames returns the names of the specified blocks as a
// human-readable string.
//
func blockNames(blocks []*BasicBlock) string {
var buf bytes.Buffer
for i, b := range blocks {
if i > 0 {
io.WriteString(&buf, ", ")
}
io.WriteString(&buf, b.Name)
}
return buf.String()
}
func (s *sanity) diagnostic(prefix, format string, args ...interface{}) {
fmt.Fprintf(s.reporter, "%s: function %s", prefix, s.fn.FullName())
if s.block != nil {
fmt.Fprintf(s.reporter, ", block %s", s.block.Name)
}
io.WriteString(s.reporter, ": ")
fmt.Fprintf(s.reporter, format, args...)
io.WriteString(s.reporter, "\n")
}
func (s *sanity) errorf(format string, args ...interface{}) {
s.insane = true
s.diagnostic("Error", format, args...)
}
func (s *sanity) warnf(format string, args ...interface{}) {
s.diagnostic("Warning", format, args...)
}
// findDuplicate returns an arbitrary basic block that appeared more
// than once in blocks, or nil if all were unique.
func findDuplicate(blocks []*BasicBlock) *BasicBlock {
if len(blocks) < 2 {
return nil
}
if blocks[0] == blocks[1] {
return blocks[0]
}
// Slow path:
m := make(map[*BasicBlock]bool)
for _, b := range blocks {
if m[b] {
return b
}
m[b] = true
}
return nil
}
func (s *sanity) checkInstr(idx int, instr Instruction) {
switch instr := instr.(type) {
case *If, *Jump, *Ret:
s.errorf("control flow instruction not at end of block")
case *Phi:
if idx == 0 {
// It suffices to apply this check to just the first phi node.
if dup := findDuplicate(s.block.Preds); dup != nil {
s.errorf("phi node in block with duplicate predecessor %s", dup.Name)
}
} else {
prev := s.block.Instrs[idx-1]
if _, ok := prev.(*Phi); !ok {
s.errorf("Phi instruction follows a non-Phi: %T", prev)
}
}
if ne, np := len(instr.Edges), len(s.block.Preds); ne != np {
s.errorf("phi node has %d edges but %d predecessors", ne, np)
}
case *Alloc:
case *Call:
case *BinOp:
case *UnOp:
case *MakeClosure:
case *MakeChan:
case *MakeMap:
case *MakeSlice:
case *Slice:
case *Field:
case *FieldAddr:
case *IndexAddr:
case *Index:
case *Select:
case *Range:
case *TypeAssert:
case *Extract:
case *Go:
case *Defer:
case *Send:
case *Store:
case *MapUpdate:
case *Next:
case *Lookup:
case *Conv:
case *ChangeInterface:
case *MakeInterface:
// TODO(adonovan): implement checks.
default:
panic(fmt.Sprintf("Unknown instruction type: %T", instr))
}
}
func (s *sanity) checkFinalInstr(idx int, instr Instruction) {
switch instr.(type) {
case *If:
if nsuccs := len(s.block.Succs); nsuccs != 2 {
s.errorf("If-terminated block has %d successors; expected 2", nsuccs)
return
}
if s.block.Succs[0] == s.block.Succs[1] {
s.errorf("If-instruction has same True, False target blocks: %s", s.block.Succs[0].Name)
return
}
case *Jump:
if nsuccs := len(s.block.Succs); nsuccs != 1 {
s.errorf("Jump-terminated block has %d successors; expected 1", nsuccs)
return
}
case *Ret:
if nsuccs := len(s.block.Succs); nsuccs != 0 {
s.errorf("Ret-terminated block has %d successors; expected none", nsuccs)
return
}
// TODO(adonovan): check number and types of results
default:
s.errorf("non-control flow instruction at end of block")
}
}
func (s *sanity) checkBlock(b *BasicBlock, isEntry bool) {
s.block = b
// Check all blocks are reachable.
// (The entry block is always implicitly reachable.)
if !isEntry && len(b.Preds) == 0 {
s.warnf("unreachable block")
if b.Instrs == nil {
// Since this block is about to be pruned,
// tolerating transient problems in it
// simplifies other optimisations.
return
}
}
// Check predecessor and successor relations are dual.
for _, a := range b.Preds {
found := false
for _, bb := range a.Succs {
if bb == b {
found = true
break
}
}
if !found {
s.errorf("expected successor edge in predecessor %s; found only: %s", a.Name, blockNames(a.Succs))
}
}
for _, c := range b.Succs {
found := false
for _, bb := range c.Preds {
if bb == b {
found = true
break
}
}
if !found {
s.errorf("expected predecessor edge in successor %s; found only: %s", c.Name, blockNames(c.Preds))
}
}
// Check each instruction is sane.
n := len(b.Instrs)
if n == 0 {
s.errorf("basic block contains no instructions")
}
for j, instr := range b.Instrs {
if b2 := instr.Block(); b2 == nil {
s.errorf("nil Block() for instruction at index %d", j)
continue
} else if b2 != b {
s.errorf("wrong Block() (%s) for instruction at index %d ", b2.Name, j)
continue
}
if j < n-1 {
s.checkInstr(j, instr)
} else {
s.checkFinalInstr(j, instr)
}
}
}
func (s *sanity) checkFunction(fn *Function) bool {
// TODO(adonovan): check Function invariants:
// - check owning Package (if any) contains this function.
// - check params match signature
// - check locals are all !Heap
// - check transient fields are nil
// - check block labels are unique (warning)
s.fn = fn
if fn.Prog == nil {
s.errorf("nil Prog")
}
for i, b := range fn.Blocks {
if b == nil {
s.warnf("nil *BasicBlock at f.Blocks[%d]", i)
continue
}
s.checkBlock(b, i == 0)
}
s.block = nil
s.fn = nil
return !s.insane
}

View File

@ -222,12 +222,12 @@ type Function struct {
// The following fields are set transiently during building,
// then cleared.
currentBlock *BasicBlock // where to emit code
objects map[types.Object]Value // addresses of local variables
results []*Alloc // tuple of named results
// syntax *funcSyntax // abstract syntax trees for Go source functions
// targets *targets // linked stack of branch targets
// lblocks map[*ast.Object]*lblock // labelled blocks
currentBlock *BasicBlock // where to emit code
objects map[types.Object]Value // addresses of local variables
results []*Alloc // tuple of named results
syntax *funcSyntax // abstract syntax trees for Go source functions
targets *targets // linked stack of branch targets
lblocks map[*ast.Object]*lblock // labelled blocks
}
// An SSA basic block.
@ -984,18 +984,9 @@ func (v *Capture) Name() string { return v.Outer.Name() }
func (v *Global) Type() types.Type { return v.Type_ }
func (v *Global) Name() string { return v.Name_ }
func (v *Global) String() string { return v.Name_ } // placeholder
func (v *Function) Name() string { return v.Name_ }
func (v *Function) Type() types.Type { return v.Signature }
func (v *Function) String() string { return v.Name_ } // placeholder
// FullName returns v's package-qualified name.
func (v *Global) FullName() string { return fmt.Sprintf("%s.%s", v.Pkg.ImportPath, v.Name_) }
func (v *Literal) Name() string { return "Literal" } // placeholder
func (v *Literal) String() string { return "Literal" } // placeholder
func (v *Literal) Type() types.Type { return v.Type_ } // placeholder
func (v *Parameter) Type() types.Type { return v.Type_ }
func (v *Parameter) Name() string { return v.Name_ }

172
src/pkg/exp/ssa/util.go Normal file
View File

@ -0,0 +1,172 @@
package ssa
// This file defines a number of miscellaneous utility functions.
import (
"fmt"
"go/ast"
"go/types"
)
func unreachable() {
panic("unreachable")
}
//// AST utilities
// noparens returns e with any enclosing parentheses stripped.
func noparens(e ast.Expr) ast.Expr {
for {
p, ok := e.(*ast.ParenExpr)
if !ok {
break
}
e = p.X
}
return e
}
// isBlankIdent returns true iff e is an Ident with name "_".
// They have no associated types.Object, and thus no type.
//
// TODO(gri): consider making typechecker not treat them differently.
// It's one less thing for clients like us to worry about.
//
func isBlankIdent(e ast.Expr) bool {
id, ok := e.(*ast.Ident)
return ok && id.Name == "_"
}
//// Type utilities. Some of these belong in go/types.
// underlyingType returns the underlying type of typ.
// TODO(gri): this is a copy of go/types.underlying; export that function.
//
func underlyingType(typ types.Type) types.Type {
if typ, ok := typ.(*types.NamedType); ok {
return typ.Underlying // underlying types are never NamedTypes
}
if typ == nil {
panic("underlyingType(nil)")
}
return typ
}
// isPointer returns true for types whose underlying type is a pointer.
func isPointer(typ types.Type) bool {
if nt, ok := typ.(*types.NamedType); ok {
typ = nt.Underlying
}
_, ok := typ.(*types.Pointer)
return ok
}
// pointer(typ) returns the type that is a pointer to typ.
func pointer(typ types.Type) *types.Pointer {
return &types.Pointer{Base: typ}
}
// indirect(typ) assumes that typ is a pointer type,
// or named alias thereof, and returns its base type.
// Panic ensures if it is not a pointer.
//
func indirectType(ptr types.Type) types.Type {
if v, ok := underlyingType(ptr).(*types.Pointer); ok {
return v.Base
}
// When debugging it is convenient to comment out this line
// and let it continue to print the (illegal) SSA form.
panic("indirect() of non-pointer type: " + ptr.String())
return nil
}
// deref returns a pointer's base type; otherwise it returns typ.
func deref(typ types.Type) types.Type {
if typ, ok := underlyingType(typ).(*types.Pointer); ok {
return typ.Base
}
return typ
}
// methodIndex returns the method (and its index) named id within the
// method table methods of named or interface type typ. If not found,
// panic ensues.
//
func methodIndex(typ types.Type, methods []*types.Method, id Id) (i int, m *types.Method) {
for i, m = range methods {
if IdFromQualifiedName(m.QualifiedName) == id {
return
}
}
panic(fmt.Sprint("method not found: ", id, " in interface ", typ))
}
// objKind returns the syntactic category of the named entity denoted by obj.
func objKind(obj types.Object) ast.ObjKind {
switch obj.(type) {
case *types.Package:
return ast.Pkg
case *types.TypeName:
return ast.Typ
case *types.Const:
return ast.Con
case *types.Var:
return ast.Var
case *types.Func:
return ast.Fun
}
panic(fmt.Sprintf("unexpected Object type: %T", obj))
}
// DefaultType returns the default "typed" type for an "untyped" type;
// it returns the incoming type for all other types. If there is no
// corresponding untyped type, the result is types.Typ[types.Invalid].
//
// Exported to exp/ssa/interp.
//
// TODO(gri): this is a copy of go/types.defaultType; export that function.
//
func DefaultType(typ types.Type) types.Type {
if t, ok := typ.(*types.Basic); ok {
k := types.Invalid
switch t.Kind {
// case UntypedNil:
// There is no default type for nil. For a good error message,
// catch this case before calling this function.
case types.UntypedBool:
k = types.Bool
case types.UntypedInt:
k = types.Int
case types.UntypedRune:
k = types.Rune
case types.UntypedFloat:
k = types.Float64
case types.UntypedComplex:
k = types.Complex128
case types.UntypedString:
k = types.String
}
typ = types.Typ[k]
}
return typ
}
// makeId returns the Id (name, pkg) if the name is exported or
// (name, nil) otherwise.
//
func makeId(name string, pkg *types.Package) (id Id) {
id.Name = name
if !ast.IsExported(name) {
id.Pkg = pkg
}
return
}
// IdFromQualifiedName returns the Id (qn.Name, qn.Pkg) if qn is an
// exported name or (qn.Name, nil) otherwise.
//
// Exported to exp/ssa/interp.
//
func IdFromQualifiedName(qn types.QualifiedName) Id {
return makeId(qn.Name, qn.Pkg)
}