1
0
mirror of https://github.com/golang/go synced 2024-11-22 10:44:41 -07:00

exp/types: implement Type.String methods for testing/debugging

Also:
- replaced existing test with a more comprehensive test
- fixed bug in map type creation

R=r
CC=golang-dev
https://golang.org/cl/6450072
This commit is contained in:
Robert Griesemer 2012-07-31 17:09:12 -07:00
parent 9f3b00579e
commit dcb6f59811
3 changed files with 235 additions and 55 deletions

View File

@ -168,7 +168,7 @@ func (c *checker) makeType(x ast.Expr, cycleOk bool) (typ Type) {
return &Interface{Methods: methods}
case *ast.MapType:
return &Map{Key: c.makeType(t.Key, true), Elt: c.makeType(t.Key, true)}
return &Map{Key: c.makeType(t.Key, true), Elt: c.makeType(t.Value, true)}
case *ast.ChanType:
return &Chan{Dir: t.Dir, Elt: c.makeType(t.Value, true)}

View File

@ -8,6 +8,8 @@
package types
import (
"bytes"
"fmt"
"go/ast"
"sort"
)
@ -15,43 +17,61 @@ import (
// All types implement the Type interface.
type Type interface {
isType()
String() string
}
// All concrete types embed ImplementsType which
// All concrete types embed implementsType which
// ensures that all types implement the Type interface.
type ImplementsType struct{}
type implementsType struct{}
func (t *ImplementsType) isType() {}
func (t *implementsType) isType() {}
// A Bad type is a non-nil placeholder type when we don't know a type.
type Bad struct {
ImplementsType
implementsType
Msg string // for better error reporting/debugging
}
func (t *Bad) String() string {
return fmt.Sprintf("badType(%s)", t.Msg)
}
// A Basic represents a (unnamed) basic type.
type Basic struct {
ImplementsType
implementsType
// TODO(gri) need a field specifying the exact basic type
}
func (t *Basic) String() string {
// TODO(gri) print actual type information
return "basicType"
}
// An Array represents an array type [Len]Elt.
type Array struct {
ImplementsType
implementsType
Len uint64
Elt Type
}
func (t *Array) String() string {
return fmt.Sprintf("[%d]%s", t.Len, t.Elt)
}
// A Slice represents a slice type []Elt.
type Slice struct {
ImplementsType
implementsType
Elt Type
}
func (t *Slice) String() string {
return "[]" + t.Elt.String()
}
// A Struct represents a struct type struct{...}.
// Anonymous fields are represented by objects with empty names.
type Struct struct {
ImplementsType
implementsType
Fields ObjList // struct fields; or nil
Tags []string // corresponding tags; or nil
// TODO(gri) This type needs some rethinking:
@ -60,49 +80,148 @@ type Struct struct {
// - there is no scope for fast lookup (but the parser creates one)
}
func (t *Struct) String() string {
buf := bytes.NewBufferString("struct{")
for i, fld := range t.Fields {
if i > 0 {
buf.WriteString("; ")
}
if fld.Name != "" {
buf.WriteString(fld.Name)
buf.WriteByte(' ')
}
buf.WriteString(fld.Type.(Type).String())
if i < len(t.Tags) && t.Tags[i] != "" {
fmt.Fprintf(buf, " %q", t.Tags[i])
}
}
buf.WriteByte('}')
return buf.String()
}
// A Pointer represents a pointer type *Base.
type Pointer struct {
ImplementsType
implementsType
Base Type
}
func (t *Pointer) String() string {
return "*" + t.Base.String()
}
// A Func represents a function type func(...) (...).
// Unnamed parameters are represented by objects with empty names.
type Func struct {
ImplementsType
implementsType
Recv *ast.Object // nil if not a method
Params ObjList // (incoming) parameters from left to right; or nil
Results ObjList // (outgoing) results from left to right; or nil
IsVariadic bool // true if the last parameter's type is of the form ...T
}
func writeParams(buf *bytes.Buffer, params ObjList, isVariadic bool) {
buf.WriteByte('(')
for i, par := range params {
if i > 0 {
buf.WriteString(", ")
}
if par.Name != "" {
buf.WriteString(par.Name)
buf.WriteByte(' ')
}
if isVariadic && i == len(params)-1 {
buf.WriteString("...")
}
buf.WriteString(par.Type.(Type).String())
}
buf.WriteByte(')')
}
func writeSignature(buf *bytes.Buffer, t *Func) {
writeParams(buf, t.Params, t.IsVariadic)
if len(t.Results) == 0 {
// no result
return
}
buf.WriteByte(' ')
if len(t.Results) == 1 && t.Results[0].Name == "" {
// single unnamed result
buf.WriteString(t.Results[0].Type.(Type).String())
return
}
// multiple or named result(s)
writeParams(buf, t.Results, false)
}
func (t *Func) String() string {
buf := bytes.NewBufferString("func")
writeSignature(buf, t)
return buf.String()
}
// An Interface represents an interface type interface{...}.
type Interface struct {
ImplementsType
implementsType
Methods ObjList // interface methods sorted by name; or nil
}
func (t *Interface) String() string {
buf := bytes.NewBufferString("interface{")
for i, m := range t.Methods {
if i > 0 {
buf.WriteString("; ")
}
buf.WriteString(m.Name)
writeSignature(buf, m.Type.(*Func))
}
buf.WriteByte('}')
return buf.String()
}
// A Map represents a map type map[Key]Elt.
type Map struct {
ImplementsType
implementsType
Key, Elt Type
}
func (t *Map) String() string {
return fmt.Sprintf("map[%s]%s", t.Key, t.Elt)
}
// A Chan represents a channel type chan Elt, <-chan Elt, or chan<-Elt.
type Chan struct {
ImplementsType
implementsType
Dir ast.ChanDir
Elt Type
}
func (t *Chan) String() string {
var s string
switch t.Dir {
case ast.SEND:
s = "chan<- "
case ast.RECV:
s = "<-chan "
default:
s = "chan "
}
return s + t.Elt.String()
}
// A Name represents a named type as declared in a type declaration.
type Name struct {
ImplementsType
implementsType
Underlying Type // nil if not fully declared
Obj *ast.Object // corresponding declared object
// TODO(gri) need to remember fields and methods.
}
func (t *Name) String() string {
return t.Obj.Name
}
// If typ is a pointer type, Deref returns the pointer's base type;
// otherwise it returns typ.
func Deref(typ Type) Type {

View File

@ -13,55 +13,116 @@ import (
"testing"
)
func checkSource(t *testing.T, src string) *ast.Package {
func makePkg(t *testing.T, src string) (*ast.Package, error) {
const filename = "<src>"
file, err := parser.ParseFile(fset, filename, src, parser.DeclarationErrors)
if err != nil {
t.Fatal(err)
return nil, err
}
files := map[string]*ast.File{filename: file}
pkg, err := ast.NewPackage(fset, files, GcImport, Universe)
if err != nil {
t.Fatal(err)
return nil, err
}
_, err = Check(fset, pkg)
if err != nil {
t.Fatal(err)
if _, err := Check(fset, pkg); err != nil {
return nil, err
}
return pkg
return pkg, nil
}
func TestVariadicFunctions(t *testing.T) {
pkg := checkSource(t, `
package p
func f1(arg ...int)
func f2(arg1 string, arg2 ...int)
func f3()
func f4(arg int)
`)
f1 := pkg.Scope.Lookup("f1")
f2 := pkg.Scope.Lookup("f2")
for _, f := range [...](*ast.Object){f1, f2} {
ftype := f.Type.(*Func)
if !ftype.IsVariadic {
t.Errorf("expected %s to be variadic", f.Name)
}
param := ftype.Params[len(ftype.Params)-1]
if param.Type != Int {
t.Errorf("expected last parameter of %s to have type int, found %T", f.Name, param.Type)
}
}
f3 := pkg.Scope.Lookup("f3")
f4 := pkg.Scope.Lookup("f4")
for _, f := range [...](*ast.Object){f3, f4} {
ftype := f.Type.(*Func)
if ftype.IsVariadic {
t.Fatalf("expected %s to not be variadic", f.Name)
}
}
// TODO(axw) replace this function's innards with table driven tests.
// We should have a helper function that prints a type signature. Then
// we can have a table of function declarations and expected type
// signatures which can be easily expanded.
type testEntry struct {
src, str string
}
// dup returns a testEntry where both src and str are the same.
func dup(s string) testEntry {
return testEntry{s, s}
}
var testTypes = []testEntry{
// basic types
dup("int"),
dup("float32"),
dup("string"),
// arrays
{"[10]int", "[0]int"}, // TODO(gri) fix array length, add more array tests
// slices
dup("[]int"),
dup("[][]int"),
// structs
dup("struct{}"),
dup("struct{x int}"),
{`struct {
x, y int
z float32 "foo"
}`, `struct{x int; y int; z float32 "foo"}`},
{`struct {
string
elems []T
}`, `struct{string; elems []T}`},
// pointers
dup("*int"),
dup("***struct{}"),
dup("*struct{a int; b float32}"),
// functions
dup("func()"),
dup("func(x int)"),
{"func(x, y int)", "func(x int, y int)"},
{"func(x, y int, z string)", "func(x int, y int, z string)"},
dup("func(int)"),
dup("func(int, string, byte)"),
dup("func() int"),
{"func() (string)", "func() string"},
dup("func() (u int)"),
{"func() (u, v int, w string)", "func() (u int, v int, w string)"},
dup("func(int) string"),
dup("func(x int) string"),
dup("func(x int) (u string)"),
{"func(x, y int) (u string)", "func(x int, y int) (u string)"},
dup("func(...int) string"),
dup("func(x ...int) string"),
dup("func(x ...int) (u string)"),
{"func(x, y ...int) (u string)", "func(x int, y ...int) (u string)"},
// interfaces
dup("interface{}"),
dup("interface{m()}"),
{`interface{
m(int) float32
String() string
}`, `interface{String() string; m(int) float32}`}, // methods are sorted
// TODO(gri) add test for interface w/ anonymous field
// maps
dup("map[string]int"),
{"map[struct{x, y int}][]byte", "map[struct{x int; y int}][]byte"},
// channels
dup("chan int"),
dup("chan<- func()"),
dup("<-chan []func() int"),
}
func TestTypes(t *testing.T) {
for _, test := range testTypes {
src := "package p; type T " + test.src
pkg, err := makePkg(t, src)
if err != nil {
t.Errorf("%s: %s", src, err)
continue
}
typ := Underlying(pkg.Scope.Lookup("T").Type.(Type))
str := typ.String()
if str != test.str {
t.Errorf("%s: got %s, want %s", test.src, str, test.str)
}
}
}