go.tools/go/types: add TypeString, ObjectString utilities

These are variants of Type.String(), Object.String() that
accept a 'from *Package' argument.  If provided, package
qualification is omitted when printing named types belonging
to that package.

This is useful for UIs where a package is implied by context
e.g. ssadump disassembly, oracle output.

+ Test.

R=gri, gri, gordon.klaus
CC=golang-dev
https://golang.org/cl/22190048
This commit is contained in:
Alan Donovan 2013-11-15 09:20:46 -05:00
parent 91e5190eb9
commit 0820934407
6 changed files with 175 additions and 113 deletions

View File

@ -280,3 +280,26 @@ func TestInitOrder(t *testing.T) {
}
}
}
func TestTypeString(t *testing.T) {
p, _ := pkgFor("p.go", "package p; type T int", nil)
q, _ := pkgFor("q.go", "package q", nil)
pT := p.Scope().Lookup("T").Type()
for _, test := range []struct {
typ Type
this *Package
want string
}{
{pT, nil, "p.T"},
{pT, p, "T"},
{pT, q, "p.T"},
{NewPointer(pT), p, "*T"},
{NewPointer(pT), q, "*p.T"},
} {
if got := TypeString(test.this, test.typ); got != test.want {
t.Errorf("TypeString(%s, %s) = %s, want %s",
test.this, test.typ, got, test.want)
}
}
}

View File

@ -88,7 +88,7 @@ func ExprString(expr ast.Expr) string {
return buf.String()
}
// TODO(gri) Need to merge with typeString since some expressions are types (try: ([]int)(a))
// TODO(gri) Need to merge with TypeString since some expressions are types (try: ([]int)(a))
func WriteExpr(buf *bytes.Buffer, expr ast.Expr) {
switch x := expr.(type) {
case *ast.Ident:
@ -173,14 +173,17 @@ func WriteExpr(buf *bytes.Buffer, expr ast.Expr) {
}
}
// typeString returns a string representation for typ.
func typeString(typ Type) string {
// TypeString returns the string form of typ.
// Named types are printed package-qualified only
// if they do not belong to this package.
//
func TypeString(this *Package, typ Type) string {
var buf bytes.Buffer
writeType(&buf, typ)
writeType(&buf, this, typ)
return buf.String()
}
func writeTuple(buf *bytes.Buffer, tup *Tuple, isVariadic bool) {
func writeTuple(buf *bytes.Buffer, this *Package, tup *Tuple, isVariadic bool) {
buf.WriteByte('(')
if tup != nil {
for i, v := range tup.vars {
@ -196,14 +199,14 @@ func writeTuple(buf *bytes.Buffer, tup *Tuple, isVariadic bool) {
buf.WriteString("...")
typ = typ.(*Slice).elem
}
writeType(buf, typ)
writeType(buf, this, typ)
}
}
buf.WriteByte(')')
}
func writeSignature(buf *bytes.Buffer, sig *Signature) {
writeTuple(buf, sig.params, sig.isVariadic)
func writeSignature(buf *bytes.Buffer, this *Package, sig *Signature) {
writeTuple(buf, this, sig.params, sig.isVariadic)
n := sig.results.Len()
if n == 0 {
@ -214,15 +217,15 @@ func writeSignature(buf *bytes.Buffer, sig *Signature) {
buf.WriteByte(' ')
if n == 1 && sig.results.vars[0].name == "" {
// single unnamed result
writeType(buf, sig.results.vars[0].typ)
writeType(buf, this, sig.results.vars[0].typ)
return
}
// multiple or named result(s)
writeTuple(buf, sig.results, false)
writeTuple(buf, this, sig.results, false)
}
func writeType(buf *bytes.Buffer, typ Type) {
func writeType(buf *bytes.Buffer, this *Package, typ Type) {
switch t := typ.(type) {
case nil:
buf.WriteString("<nil>")
@ -235,11 +238,11 @@ func writeType(buf *bytes.Buffer, typ Type) {
case *Array:
fmt.Fprintf(buf, "[%d]", t.len)
writeType(buf, t.elem)
writeType(buf, this, t.elem)
case *Slice:
buf.WriteString("[]")
writeType(buf, t.elem)
writeType(buf, this, t.elem)
case *Struct:
buf.WriteString("struct{")
@ -251,7 +254,7 @@ func writeType(buf *bytes.Buffer, typ Type) {
buf.WriteString(f.name)
buf.WriteByte(' ')
}
writeType(buf, f.typ)
writeType(buf, this, f.typ)
if tag := t.Tag(i); tag != "" {
fmt.Fprintf(buf, " %q", tag)
}
@ -260,14 +263,14 @@ func writeType(buf *bytes.Buffer, typ Type) {
case *Pointer:
buf.WriteByte('*')
writeType(buf, t.base)
writeType(buf, this, t.base)
case *Tuple:
writeTuple(buf, t, false)
writeTuple(buf, this, t, false)
case *Signature:
buf.WriteString("func")
writeSignature(buf, t)
writeSignature(buf, this, t)
case *Interface:
// We write the source-level methods and embedded types rather
@ -287,21 +290,21 @@ func writeType(buf *bytes.Buffer, typ Type) {
buf.WriteString("; ")
}
buf.WriteString(m.name)
writeSignature(buf, m.typ.(*Signature))
writeSignature(buf, this, m.typ.(*Signature))
}
for i, typ := range t.types {
if i > 0 || len(t.methods) > 0 {
buf.WriteString("; ")
}
writeType(buf, typ)
writeType(buf, this, typ)
}
buf.WriteByte('}')
case *Map:
buf.WriteString("map[")
writeType(buf, t.key)
writeType(buf, this, t.key)
buf.WriteByte(']')
writeType(buf, t.elem)
writeType(buf, this, t.elem)
case *Chan:
var s string
@ -314,28 +317,29 @@ func writeType(buf *bytes.Buffer, typ Type) {
s = "chan "
}
buf.WriteString(s)
writeType(buf, t.elem)
writeType(buf, this, t.elem)
case *Named:
s := "<Named w/o object>"
if obj := t.obj; obj != nil {
if obj.pkg != nil {
if obj.pkg != this {
buf.WriteString(obj.pkg.path)
buf.WriteByte('.')
}
// TODO(gri) Ideally we only want the qualification
// if we are referring to a type that was imported;
// but not when we are at the "top". We don't have
// this information easily available here.
//
// TODO(gri): define variants of Type.String()
// and Object.String() that accept the referring *Package
// as a parameter and omit the package qualification for
// intra-package references to named types.
//
// Some applications may want another variant that accepts a
// file Scope and prints packages using that file's local
// import names. (Printing just pkg.name may be ambiguous
// or incorrect in other scopes.)
buf.WriteString(obj.pkg.path)
buf.WriteByte('.')
// TODO(gri): function-local named types should be displayed
// differently from named types at package level to avoid
// ambiguity.
}
s = t.obj.name
}

View File

@ -83,25 +83,6 @@ func (obj *object) String() string { panic("abstract") }
func (obj *object) isUsed() bool { return obj.used }
func (obj *object) toString(kind string, typ Type) string {
var buf bytes.Buffer
buf.WriteString(kind)
buf.WriteByte(' ')
// For package-level objects, package-qualify the name.
if obj.pkg != nil && obj.pkg.scope.Lookup(obj.name) == obj {
buf.WriteString(obj.pkg.name)
buf.WriteByte('.')
}
buf.WriteString(obj.name)
if typ != nil {
buf.WriteByte(' ')
writeType(&buf, typ)
}
return buf.String()
}
func (obj *object) setParent(parent *Scope) { obj.parent = parent }
func (obj *object) sameId(pkg *Package, name string) bool {
@ -135,8 +116,6 @@ func NewPkgName(pos token.Pos, pkg *Package, name string) *PkgName {
return &PkgName{object{nil, pos, pkg, name, Typ[Invalid], false}}
}
func (obj *PkgName) String() string { return obj.toString("package", nil) }
// A Const represents a declared constant.
type Const struct {
object
@ -149,7 +128,6 @@ func NewConst(pos token.Pos, pkg *Package, name string, typ Type, val exact.Valu
return &Const{object: object{nil, pos, pkg, name, typ, false}, val: val}
}
func (obj *Const) String() string { return obj.toString("const", obj.typ) }
func (obj *Const) Val() exact.Value { return obj.val }
// A TypeName represents a declared type.
@ -161,8 +139,6 @@ func NewTypeName(pos token.Pos, pkg *Package, name string, typ Type) *TypeName {
return &TypeName{object{nil, pos, pkg, name, typ, false}}
}
func (obj *TypeName) String() string { return obj.toString("type", obj.typ.Underlying()) }
// A Variable represents a declared variable (including function parameters and results, and struct fields).
type Var struct {
object
@ -185,13 +161,7 @@ func NewField(pos token.Pos, pkg *Package, name string, typ Type, anonymous bool
}
func (obj *Var) Anonymous() bool { return obj.anonymous }
func (obj *Var) String() string {
kind := "var"
if obj.isField {
kind = "field"
}
return obj.toString(kind, obj.typ)
}
func (obj *Var) IsField() bool { return obj.isField }
// A Func represents a declared function, concrete method, or abstract
@ -214,41 +184,7 @@ func NewFunc(pos token.Pos, pkg *Package, name string, sig *Signature) *Func {
// function or method obj.
func (obj *Func) FullName() string {
var buf bytes.Buffer
obj.fullname(&buf)
return buf.String()
}
func (obj *Func) fullname(buf *bytes.Buffer) {
if obj.typ != nil {
sig := obj.typ.(*Signature)
if recv := sig.Recv(); recv != nil {
buf.WriteByte('(')
if _, ok := recv.Type().(*Interface); ok {
// gcimporter creates abstract methods of
// named interfaces using the interface type
// (not the named type) as the receiver.
// Don't print it in full.
buf.WriteString("interface")
} else {
writeType(buf, recv.Type())
}
buf.WriteByte(')')
buf.WriteByte('.')
} else if obj.pkg != nil {
buf.WriteString(obj.pkg.name)
buf.WriteByte('.')
}
}
buf.WriteString(obj.name)
}
func (obj *Func) String() string {
var buf bytes.Buffer
buf.WriteString("func ")
obj.fullname(&buf)
if obj.typ != nil {
writeSignature(&buf, obj.typ.(*Signature))
}
writeFuncName(&buf, nil, obj)
return buf.String()
}
@ -261,8 +197,6 @@ func NewLabel(pos token.Pos, name string) *Label {
return &Label{object{pos: pos, name: name, typ: Typ[Invalid]}}
}
func (obj *Label) String() string { return fmt.Sprintf("label %s", obj.Name()) }
// A Builtin represents a built-in function.
// Builtins don't have a valid type.
type Builtin struct {
@ -280,4 +214,105 @@ type Nil struct {
object
}
func (*Nil) String() string { return "nil" }
func writeObject(buf *bytes.Buffer, this *Package, obj Object) {
typ := obj.Type()
switch obj := obj.(type) {
case *PkgName:
buf.WriteString("package")
typ = nil
case *Const:
buf.WriteString("const")
case *TypeName:
buf.WriteString("type")
typ = typ.Underlying()
case *Var:
if obj.isField {
buf.WriteString("field")
} else {
buf.WriteString("var")
}
case *Func:
buf.WriteString("func ")
writeFuncName(buf, this, obj)
if typ != nil {
writeSignature(buf, this, typ.(*Signature))
}
return
case *Label:
buf.WriteString("label")
typ = nil
case *Builtin:
buf.WriteString("builtin")
typ = nil
case *Nil:
buf.WriteString("nil")
return
default:
panic(fmt.Sprintf("writeObject(%T)", obj))
}
buf.WriteByte(' ')
// For package-level objects, package-qualify the name,
// except for intra-package references (this != nil).
if pkg := obj.Pkg(); pkg != nil && this != pkg && pkg.scope.Lookup(obj.Name()) == obj {
buf.WriteString(pkg.path)
buf.WriteByte('.')
}
buf.WriteString(obj.Name())
if typ != nil {
buf.WriteByte(' ')
writeType(buf, this, typ)
}
}
// ObjectString returns the string form of obj.
// Object and type names are printed package-qualified
// only if they do not belong to this package.
//
func ObjectString(this *Package, obj Object) string {
var buf bytes.Buffer
writeObject(&buf, this, obj)
return buf.String()
}
func (obj *PkgName) String() string { return ObjectString(nil, obj) }
func (obj *Const) String() string { return ObjectString(nil, obj) }
func (obj *TypeName) String() string { return ObjectString(nil, obj) }
func (obj *Var) String() string { return ObjectString(nil, obj) }
func (obj *Func) String() string { return ObjectString(nil, obj) }
func (obj *Label) String() string { return ObjectString(nil, obj) }
func (obj *Builtin) String() string { return ObjectString(nil, obj) }
func (obj *Nil) String() string { return ObjectString(nil, obj) }
func writeFuncName(buf *bytes.Buffer, this *Package, f *Func) {
if f.typ != nil {
sig := f.typ.(*Signature)
if recv := sig.Recv(); recv != nil {
buf.WriteByte('(')
if _, ok := recv.Type().(*Interface); ok {
// gcimporter creates abstract methods of
// named interfaces using the interface type
// (not the named type) as the receiver.
// Don't print it in full.
buf.WriteString("interface")
} else {
writeType(buf, this, recv.Type())
}
buf.WriteByte(')')
buf.WriteByte('.')
} else if f.pkg != nil && f.pkg != this {
buf.WriteString(f.pkg.path)
buf.WriteByte('.')
}
}
buf.WriteString(f.name)
}

View File

@ -105,7 +105,7 @@ func (x *operand) String() string {
case builtin:
expr = predeclaredFuncs[x.id].name
case typexpr:
expr = typeString(x.typ)
expr = TypeString(nil, x.typ)
case constant:
expr = x.val.String()
}
@ -147,7 +147,7 @@ func (x *operand) String() string {
if hasType {
if x.typ != Typ[Invalid] {
buf.WriteString(" of type ")
writeType(&buf, x.typ)
writeType(&buf, nil, x.typ)
} else {
buf.WriteString(" with invalid type")
}

View File

@ -132,6 +132,6 @@ func (s *Selection) String() string {
}
var buf bytes.Buffer
fmt.Fprintf(&buf, "%s (%s) %s", k, s.Recv(), s.obj.Name())
writeSignature(&buf, s.Type().(*Signature))
writeSignature(&buf, nil, s.Type().(*Signature))
return buf.String()
}

View File

@ -404,14 +404,14 @@ 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(t) }
func (t *Array) String() string { return typeString(t) }
func (t *Slice) String() string { return typeString(t) }
func (t *Struct) String() string { return typeString(t) }
func (t *Pointer) String() string { return typeString(t) }
func (t *Tuple) String() string { return typeString(t) }
func (t *Signature) String() string { return typeString(t) }
func (t *Interface) String() string { return typeString(t) }
func (t *Map) String() string { return typeString(t) }
func (t *Chan) String() string { return typeString(t) }
func (t *Named) String() string { return typeString(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) }
func (t *Struct) String() string { return TypeString(nil, t) }
func (t *Pointer) String() string { return TypeString(nil, t) }
func (t *Tuple) String() string { return TypeString(nil, t) }
func (t *Signature) String() string { return TypeString(nil, t) }
func (t *Interface) String() string { return TypeString(nil, t) }
func (t *Map) String() string { return TypeString(nil, t) }
func (t *Chan) String() string { return TypeString(nil, t) }
func (t *Named) String() string { return TypeString(nil, t) }