Commit dcb6f598 authored by Robert Griesemer's avatar Robert Griesemer

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
parent 9f3b0057
...@@ -168,7 +168,7 @@ func (c *checker) makeType(x ast.Expr, cycleOk bool) (typ Type) { ...@@ -168,7 +168,7 @@ func (c *checker) makeType(x ast.Expr, cycleOk bool) (typ Type) {
return &Interface{Methods: methods} return &Interface{Methods: methods}
case *ast.MapType: 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: case *ast.ChanType:
return &Chan{Dir: t.Dir, Elt: c.makeType(t.Value, true)} return &Chan{Dir: t.Dir, Elt: c.makeType(t.Value, true)}
......
...@@ -8,6 +8,8 @@ ...@@ -8,6 +8,8 @@
package types package types
import ( import (
"bytes"
"fmt"
"go/ast" "go/ast"
"sort" "sort"
) )
...@@ -15,43 +17,61 @@ import ( ...@@ -15,43 +17,61 @@ import (
// All types implement the Type interface. // All types implement the Type interface.
type Type interface { type Type interface {
isType() isType()
String() string
} }
// All concrete types embed ImplementsType which // All concrete types embed implementsType which
// ensures that all types implement the Type interface. // 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. // A Bad type is a non-nil placeholder type when we don't know a type.
type Bad struct { type Bad struct {
ImplementsType implementsType
Msg string // for better error reporting/debugging 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. // A Basic represents a (unnamed) basic type.
type Basic struct { type Basic struct {
ImplementsType implementsType
// TODO(gri) need a field specifying the exact basic type // 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. // An Array represents an array type [Len]Elt.
type Array struct { type Array struct {
ImplementsType implementsType
Len uint64 Len uint64
Elt Type Elt Type
} }
func (t *Array) String() string {
return fmt.Sprintf("[%d]%s", t.Len, t.Elt)
}
// A Slice represents a slice type []Elt. // A Slice represents a slice type []Elt.
type Slice struct { type Slice struct {
ImplementsType implementsType
Elt Type Elt Type
} }
func (t *Slice) String() string {
return "[]" + t.Elt.String()
}
// A Struct represents a struct type struct{...}. // A Struct represents a struct type struct{...}.
// Anonymous fields are represented by objects with empty names. // Anonymous fields are represented by objects with empty names.
type Struct struct { type Struct struct {
ImplementsType implementsType
Fields ObjList // struct fields; or nil Fields ObjList // struct fields; or nil
Tags []string // corresponding tags; or nil Tags []string // corresponding tags; or nil
// TODO(gri) This type needs some rethinking: // TODO(gri) This type needs some rethinking:
...@@ -60,49 +80,148 @@ type Struct struct { ...@@ -60,49 +80,148 @@ type Struct struct {
// - there is no scope for fast lookup (but the parser creates one) // - 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. // A Pointer represents a pointer type *Base.
type Pointer struct { type Pointer struct {
ImplementsType implementsType
Base Type Base Type
} }
func (t *Pointer) String() string {
return "*" + t.Base.String()
}
// A Func represents a function type func(...) (...). // A Func represents a function type func(...) (...).
// Unnamed parameters are represented by objects with empty names. // Unnamed parameters are represented by objects with empty names.
type Func struct { type Func struct {
ImplementsType implementsType
Recv *ast.Object // nil if not a method Recv *ast.Object // nil if not a method
Params ObjList // (incoming) parameters from left to right; or nil Params ObjList // (incoming) parameters from left to right; or nil
Results ObjList // (outgoing) results 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 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{...}. // An Interface represents an interface type interface{...}.
type Interface struct { type Interface struct {
ImplementsType implementsType
Methods ObjList // interface methods sorted by name; or nil 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. // A Map represents a map type map[Key]Elt.
type Map struct { type Map struct {
ImplementsType implementsType
Key, Elt Type 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. // A Chan represents a channel type chan Elt, <-chan Elt, or chan<-Elt.
type Chan struct { type Chan struct {
ImplementsType implementsType
Dir ast.ChanDir Dir ast.ChanDir
Elt Type 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. // A Name represents a named type as declared in a type declaration.
type Name struct { type Name struct {
ImplementsType implementsType
Underlying Type // nil if not fully declared Underlying Type // nil if not fully declared
Obj *ast.Object // corresponding declared object Obj *ast.Object // corresponding declared object
// TODO(gri) need to remember fields and methods. // 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; // If typ is a pointer type, Deref returns the pointer's base type;
// otherwise it returns typ. // otherwise it returns typ.
func Deref(typ Type) Type { func Deref(typ Type) Type {
......
...@@ -13,55 +13,116 @@ import ( ...@@ -13,55 +13,116 @@ import (
"testing" "testing"
) )
func checkSource(t *testing.T, src string) *ast.Package { func makePkg(t *testing.T, src string) (*ast.Package, error) {
const filename = "<src>" const filename = "<src>"
file, err := parser.ParseFile(fset, filename, src, parser.DeclarationErrors) file, err := parser.ParseFile(fset, filename, src, parser.DeclarationErrors)
if err != nil { if err != nil {
t.Fatal(err) return nil, err
} }
files := map[string]*ast.File{filename: file} files := map[string]*ast.File{filename: file}
pkg, err := ast.NewPackage(fset, files, GcImport, Universe) pkg, err := ast.NewPackage(fset, files, GcImport, Universe)
if err != nil { if err != nil {
t.Fatal(err) return nil, err
} }
_, err = Check(fset, pkg) if _, err := Check(fset, pkg); err != nil {
if err != nil { return nil, err
t.Fatal(err)
} }
return pkg return pkg, nil
} }
func TestVariadicFunctions(t *testing.T) { type testEntry struct {
pkg := checkSource(t, ` src, str string
package p }
func f1(arg ...int)
func f2(arg1 string, arg2 ...int) // dup returns a testEntry where both src and str are the same.
func f3() func dup(s string) testEntry {
func f4(arg int) return testEntry{s, s}
`) }
f1 := pkg.Scope.Lookup("f1")
f2 := pkg.Scope.Lookup("f2") var testTypes = []testEntry{
for _, f := range [...](*ast.Object){f1, f2} { // basic types
ftype := f.Type.(*Func) dup("int"),
if !ftype.IsVariadic { dup("float32"),
t.Errorf("expected %s to be variadic", f.Name) dup("string"),
}
param := ftype.Params[len(ftype.Params)-1] // arrays
if param.Type != Int { {"[10]int", "[0]int"}, // TODO(gri) fix array length, add more array tests
t.Errorf("expected last parameter of %s to have type int, found %T", f.Name, param.Type)
} // 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}`},
f3 := pkg.Scope.Lookup("f3") // pointers
f4 := pkg.Scope.Lookup("f4") dup("*int"),
for _, f := range [...](*ast.Object){f3, f4} { dup("***struct{}"),
ftype := f.Type.(*Func) dup("*struct{a int; b float32}"),
if ftype.IsVariadic {
t.Fatalf("expected %s to not be variadic", f.Name) // 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)
} }
} }
// 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.
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment