mirror of
https://github.com/golang/go
synced 2024-11-22 05:34:39 -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:
parent
9f3b00579e
commit
dcb6f59811
@ -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)}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user