From dcb6f598116563de1f46babd40c62952621784ae Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Tue, 31 Jul 2012 17:09:12 -0700 Subject: [PATCH] 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 --- src/pkg/exp/types/check.go | 2 +- src/pkg/exp/types/types.go | 147 +++++++++++++++++++++++++++++--- src/pkg/exp/types/types_test.go | 141 +++++++++++++++++++++--------- 3 files changed, 235 insertions(+), 55 deletions(-) diff --git a/src/pkg/exp/types/check.go b/src/pkg/exp/types/check.go index aebabd6421b..9d7474a4469 100644 --- a/src/pkg/exp/types/check.go +++ b/src/pkg/exp/types/check.go @@ -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)} diff --git a/src/pkg/exp/types/types.go b/src/pkg/exp/types/types.go index 85d244cf04a..0e844107cba 100644 --- a/src/pkg/exp/types/types.go +++ b/src/pkg/exp/types/types.go @@ -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 { diff --git a/src/pkg/exp/types/types_test.go b/src/pkg/exp/types/types_test.go index feb39485531..b2c22d747a9 100644 --- a/src/pkg/exp/types/types_test.go +++ b/src/pkg/exp/types/types_test.go @@ -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 = "" 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) + } + } }