mirror of
https://github.com/golang/go
synced 2024-11-26 06:38:00 -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}
|
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 @@
|
|||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"sort"
|
"sort"
|
||||||
)
|
)
|
||||||
@ -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 {
|
|||||||
// - 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 (
|
|||||||
"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 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return pkg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return pkg
|
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user