| // Copyright 2013 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package types_test |
| |
| import ( |
| "fmt" |
| "go/ast" |
| "go/parser" |
| "go/token" |
| "strings" |
| "testing" |
| |
| _ "code.google.com/p/go.tools/go/gcimporter" |
| . "code.google.com/p/go.tools/go/types" |
| ) |
| |
| func pkgFor(path, source string, info *Info) (*Package, error) { |
| fset := token.NewFileSet() |
| f, err := parser.ParseFile(fset, path, source, 0) |
| if err != nil { |
| return nil, err |
| } |
| |
| var conf Config |
| return conf.Check(f.Name.Name, fset, []*ast.File{f}, info) |
| } |
| |
| func mustTypecheck(t *testing.T, path, source string, info *Info) string { |
| pkg, err := pkgFor(path, source, info) |
| if err != nil { |
| name := path |
| if pkg != nil { |
| name = "package " + pkg.Name() |
| } |
| t.Fatalf("%s: didn't type-check (%s)", name, err) |
| } |
| return pkg.Name() |
| } |
| |
| func TestValuesInfo(t *testing.T) { |
| var tests = []struct { |
| src string |
| expr string // constant expression |
| typ string // constant type |
| val string // constant value |
| }{ |
| {`package a0; const _ = false`, `false`, `untyped bool`, `false`}, |
| {`package a1; const _ = 0`, `0`, `untyped int`, `0`}, |
| {`package a2; const _ = 'A'`, `'A'`, `untyped rune`, `65`}, |
| {`package a3; const _ = 0.`, `0.`, `untyped float`, `0`}, |
| {`package a4; const _ = 0i`, `0i`, `untyped complex`, `0`}, |
| {`package a5; const _ = "foo"`, `"foo"`, `untyped string`, `"foo"`}, |
| |
| {`package b0; var _ = false`, `false`, `bool`, `false`}, |
| {`package b1; var _ = 0`, `0`, `int`, `0`}, |
| {`package b2; var _ = 'A'`, `'A'`, `rune`, `65`}, |
| {`package b3; var _ = 0.`, `0.`, `float64`, `0`}, |
| {`package b4; var _ = 0i`, `0i`, `complex128`, `0`}, |
| {`package b5; var _ = "foo"`, `"foo"`, `string`, `"foo"`}, |
| |
| {`package c0a; var _ = bool(false)`, `false`, `bool`, `false`}, |
| {`package c0b; var _ = bool(false)`, `bool(false)`, `bool`, `false`}, |
| {`package c0c; type T bool; var _ = T(false)`, `T(false)`, `c0c.T`, `false`}, |
| |
| {`package c1a; var _ = int(0)`, `0`, `int`, `0`}, |
| {`package c1b; var _ = int(0)`, `int(0)`, `int`, `0`}, |
| {`package c1c; type T int; var _ = T(0)`, `T(0)`, `c1c.T`, `0`}, |
| |
| {`package c2a; var _ = rune('A')`, `'A'`, `rune`, `65`}, |
| {`package c2b; var _ = rune('A')`, `rune('A')`, `rune`, `65`}, |
| {`package c2c; type T rune; var _ = T('A')`, `T('A')`, `c2c.T`, `65`}, |
| |
| {`package c3a; var _ = float32(0.)`, `0.`, `float32`, `0`}, |
| {`package c3b; var _ = float32(0.)`, `float32(0.)`, `float32`, `0`}, |
| {`package c3c; type T float32; var _ = T(0.)`, `T(0.)`, `c3c.T`, `0`}, |
| |
| {`package c4a; var _ = complex64(0i)`, `0i`, `complex64`, `0`}, |
| {`package c4b; var _ = complex64(0i)`, `complex64(0i)`, `complex64`, `0`}, |
| {`package c4c; type T complex64; var _ = T(0i)`, `T(0i)`, `c4c.T`, `0`}, |
| |
| {`package c5a; var _ = string("foo")`, `"foo"`, `string`, `"foo"`}, |
| {`package c5b; var _ = string("foo")`, `string("foo")`, `string`, `"foo"`}, |
| {`package c5c; type T string; var _ = T("foo")`, `T("foo")`, `c5c.T`, `"foo"`}, |
| |
| {`package d0; var _ = []byte("foo")`, `"foo"`, `string`, `"foo"`}, |
| {`package d1; var _ = []byte(string("foo"))`, `"foo"`, `string`, `"foo"`}, |
| {`package d2; var _ = []byte(string("foo"))`, `string("foo")`, `string`, `"foo"`}, |
| {`package d3; type T []byte; var _ = T("foo")`, `"foo"`, `string`, `"foo"`}, |
| |
| {`package e0; const _ = float32( 1e-200)`, `float32(1e-200)`, `float32`, `0`}, |
| {`package e1; const _ = float32(-1e-200)`, `float32(-1e-200)`, `float32`, `0`}, |
| {`package e2; const _ = float64( 1e-2000)`, `float64(1e-2000)`, `float64`, `0`}, |
| {`package e3; const _ = float64(-1e-2000)`, `float64(-1e-2000)`, `float64`, `0`}, |
| {`package e4; const _ = complex64( 1e-200)`, `complex64(1e-200)`, `complex64`, `0`}, |
| {`package e5; const _ = complex64(-1e-200)`, `complex64(-1e-200)`, `complex64`, `0`}, |
| {`package e6; const _ = complex128( 1e-2000)`, `complex128(1e-2000)`, `complex128`, `0`}, |
| {`package e7; const _ = complex128(-1e-2000)`, `complex128(-1e-2000)`, `complex128`, `0`}, |
| |
| {`package f0 ; var _ float32 = 1e-200`, `1e-200`, `float32`, `0`}, |
| {`package f1 ; var _ float32 = -1e-200`, `-1e-200`, `float32`, `0`}, |
| {`package f2a; var _ float64 = 1e-2000`, `1e-2000`, `float64`, `0`}, |
| {`package f3a; var _ float64 = -1e-2000`, `-1e-2000`, `float64`, `0`}, |
| {`package f2b; var _ = 1e-2000`, `1e-2000`, `float64`, `0`}, |
| {`package f3b; var _ = -1e-2000`, `-1e-2000`, `float64`, `0`}, |
| {`package f4 ; var _ complex64 = 1e-200 `, `1e-200`, `complex64`, `0`}, |
| {`package f5 ; var _ complex64 = -1e-200 `, `-1e-200`, `complex64`, `0`}, |
| {`package f6a; var _ complex128 = 1e-2000i`, `1e-2000i`, `complex128`, `0`}, |
| {`package f7a; var _ complex128 = -1e-2000i`, `-1e-2000i`, `complex128`, `0`}, |
| {`package f6b; var _ = 1e-2000i`, `1e-2000i`, `complex128`, `0`}, |
| {`package f7b; var _ = -1e-2000i`, `-1e-2000i`, `complex128`, `0`}, |
| } |
| |
| for _, test := range tests { |
| info := Info{ |
| Types: make(map[ast.Expr]TypeAndValue), |
| } |
| name := mustTypecheck(t, "ValuesInfo", test.src, &info) |
| |
| // look for constant expression |
| var expr ast.Expr |
| for e := range info.Types { |
| if ExprString(e) == test.expr { |
| expr = e |
| break |
| } |
| } |
| if expr == nil { |
| t.Errorf("package %s: no expression found for %s", name, test.expr) |
| continue |
| } |
| tv := info.Types[expr] |
| |
| // check that type is correct |
| if got := tv.Type.String(); got != test.typ { |
| t.Errorf("package %s: got type %s; want %s", name, got, test.typ) |
| continue |
| } |
| |
| // check that value is correct |
| if got := tv.Value.String(); got != test.val { |
| t.Errorf("package %s: got value %s; want %s", name, got, test.val) |
| } |
| } |
| } |
| |
| func TestTypesInfo(t *testing.T) { |
| var tests = []struct { |
| src string |
| expr string // expression |
| typ string // value type |
| }{ |
| // single-valued expressions of untyped constants |
| {`package b0; var x interface{} = false`, `false`, `bool`}, |
| {`package b1; var x interface{} = 0`, `0`, `int`}, |
| {`package b2; var x interface{} = 0.`, `0.`, `float64`}, |
| {`package b3; var x interface{} = 0i`, `0i`, `complex128`}, |
| {`package b4; var x interface{} = "foo"`, `"foo"`, `string`}, |
| |
| // comma-ok expressions |
| {`package p0; var x interface{}; var _, _ = x.(int)`, |
| `x.(int)`, |
| `(int, bool)`, |
| }, |
| {`package p1; var x interface{}; func _() { _, _ = x.(int) }`, |
| `x.(int)`, |
| `(int, bool)`, |
| }, |
| {`package p2; type mybool bool; var m map[string]complex128; var b mybool; func _() { _, b = m["foo"] }`, |
| `m["foo"]`, |
| `(complex128, p2.mybool)`, |
| }, |
| {`package p3; var c chan string; var _, _ = <-c`, |
| `<-c`, |
| `(string, bool)`, |
| }, |
| |
| // issue 6796 |
| {`package issue6796_a; var x interface{}; var _, _ = (x.(int))`, |
| `x.(int)`, |
| `(int, bool)`, |
| }, |
| {`package issue6796_b; var c chan string; var _, _ = (<-c)`, |
| `(<-c)`, |
| `(string, bool)`, |
| }, |
| {`package issue6796_c; var c chan string; var _, _ = (<-c)`, |
| `<-c`, |
| `(string, bool)`, |
| }, |
| {`package issue6796_d; var c chan string; var _, _ = ((<-c))`, |
| `(<-c)`, |
| `(string, bool)`, |
| }, |
| {`package issue6796_e; func f(c chan string) { _, _ = ((<-c)) }`, |
| `(<-c)`, |
| `(string, bool)`, |
| }, |
| |
| // issue 7060 |
| {`package issue7060_a; var ( m map[int]string; x, ok = m[0] )`, |
| `m[0]`, |
| `(string, bool)`, |
| }, |
| {`package issue7060_b; var ( m map[int]string; x, ok interface{} = m[0] )`, |
| `m[0]`, |
| `(string, bool)`, |
| }, |
| {`package issue7060_c; func f(x interface{}, ok bool, m map[int]string) { x, ok = m[0] }`, |
| `m[0]`, |
| `(string, bool)`, |
| }, |
| {`package issue7060_d; var ( ch chan string; x, ok = <-ch )`, |
| `<-ch`, |
| `(string, bool)`, |
| }, |
| {`package issue7060_e; var ( ch chan string; x, ok interface{} = <-ch )`, |
| `<-ch`, |
| `(string, bool)`, |
| }, |
| {`package issue7060_f; func f(x interface{}, ok bool, ch chan string) { x, ok = <-ch }`, |
| `<-ch`, |
| `(string, bool)`, |
| }, |
| } |
| |
| for _, test := range tests { |
| info := Info{Types: make(map[ast.Expr]TypeAndValue)} |
| name := mustTypecheck(t, "TypesInfo", test.src, &info) |
| |
| // look for expression type |
| var typ Type |
| for e, tv := range info.Types { |
| if ExprString(e) == test.expr { |
| typ = tv.Type |
| break |
| } |
| } |
| if typ == nil { |
| t.Errorf("package %s: no type found for %s", name, test.expr) |
| continue |
| } |
| |
| // check that type is correct |
| if got := typ.String(); got != test.typ { |
| t.Errorf("package %s: got %s; want %s", name, got, test.typ) |
| } |
| } |
| } |
| |
| func TestScopesInfo(t *testing.T) { |
| var tests = []struct { |
| src string |
| scopes []string // list of scope descriptors of the form kind:varlist |
| }{ |
| {`package p0`, []string{ |
| "file:", |
| }}, |
| {`package p1; import ( "fmt"; m "math"; _ "os" ); var ( _ = fmt.Println; _ = m.Pi )`, []string{ |
| "file:fmt m", |
| }}, |
| {`package p2; func _() {}`, []string{ |
| "file:", "func:", |
| }}, |
| {`package p3; func _(x, y int) {}`, []string{ |
| "file:", "func:x y", |
| }}, |
| {`package p4; func _(x, y int) { x, z := 1, 2; _ = z }`, []string{ |
| "file:", "func:x y z", // redeclaration of x |
| }}, |
| {`package p5; func _(x, y int) (u, _ int) { return }`, []string{ |
| "file:", "func:u x y", |
| }}, |
| {`package p6; func _() { { var x int; _ = x } }`, []string{ |
| "file:", "func:", "block:x", |
| }}, |
| {`package p7; func _() { if true {} }`, []string{ |
| "file:", "func:", "if:", "block:", |
| }}, |
| {`package p8; func _() { if x := 0; x < 0 { y := x; _ = y } }`, []string{ |
| "file:", "func:", "if:x", "block:y", |
| }}, |
| {`package p9; func _() { switch x := 0; x {} }`, []string{ |
| "file:", "func:", "switch:x", |
| }}, |
| {`package p10; func _() { switch x := 0; x { case 1: y := x; _ = y; default: }}`, []string{ |
| "file:", "func:", "switch:x", "case:y", "case:", |
| }}, |
| {`package p11; func _(t interface{}) { switch t.(type) {} }`, []string{ |
| "file:", "func:t", "type switch:", |
| }}, |
| {`package p12; func _(t interface{}) { switch t := t; t.(type) {} }`, []string{ |
| "file:", "func:t", "type switch:t", |
| }}, |
| {`package p13; func _(t interface{}) { switch x := t.(type) { case int: _ = x } }`, []string{ |
| "file:", "func:t", "type switch:", "case:x", // x implicitly declared |
| }}, |
| {`package p14; func _() { select{} }`, []string{ |
| "file:", "func:", |
| }}, |
| {`package p15; func _(c chan int) { select{ case <-c: } }`, []string{ |
| "file:", "func:c", "comm:", |
| }}, |
| {`package p16; func _(c chan int) { select{ case i := <-c: x := i; _ = x} }`, []string{ |
| "file:", "func:c", "comm:i x", |
| }}, |
| {`package p17; func _() { for{} }`, []string{ |
| "file:", "func:", "for:", "block:", |
| }}, |
| {`package p18; func _(n int) { for i := 0; i < n; i++ { _ = i } }`, []string{ |
| "file:", "func:n", "for:i", "block:", |
| }}, |
| {`package p19; func _(a []int) { for i := range a { _ = i} }`, []string{ |
| "file:", "func:a", "range:i", "block:", |
| }}, |
| {`package p20; var s int; func _(a []int) { for i, x := range a { s += x; _ = i } }`, []string{ |
| "file:", "func:a", "range:i x", "block:", |
| }}, |
| } |
| |
| for _, test := range tests { |
| info := Info{Scopes: make(map[ast.Node]*Scope)} |
| name := mustTypecheck(t, "ScopesInfo", test.src, &info) |
| |
| // number of scopes must match |
| if len(info.Scopes) != len(test.scopes) { |
| t.Errorf("package %s: got %d scopes; want %d", name, len(info.Scopes), len(test.scopes)) |
| } |
| |
| // scope descriptions must match |
| for node, scope := range info.Scopes { |
| kind := "<unknown node kind>" |
| switch node.(type) { |
| case *ast.File: |
| kind = "file" |
| case *ast.FuncType: |
| kind = "func" |
| case *ast.BlockStmt: |
| kind = "block" |
| case *ast.IfStmt: |
| kind = "if" |
| case *ast.SwitchStmt: |
| kind = "switch" |
| case *ast.TypeSwitchStmt: |
| kind = "type switch" |
| case *ast.CaseClause: |
| kind = "case" |
| case *ast.CommClause: |
| kind = "comm" |
| case *ast.ForStmt: |
| kind = "for" |
| case *ast.RangeStmt: |
| kind = "range" |
| } |
| |
| // look for matching scope description |
| desc := kind + ":" + strings.Join(scope.Names(), " ") |
| found := false |
| for _, d := range test.scopes { |
| if desc == d { |
| found = true |
| break |
| } |
| } |
| if !found { |
| t.Errorf("package %s: no matching scope found for %s", name, desc) |
| } |
| } |
| } |
| } |
| |
| func TestInitOrderInfo(t *testing.T) { |
| var tests = []struct { |
| src string |
| inits []string |
| }{ |
| {`package p0; var (x = 1; y = x)`, []string{ |
| "x = 1", "y = x", |
| }}, |
| {`package p1; var (a = 1; b = 2; c = 3)`, []string{ |
| "a = 1", "b = 2", "c = 3", |
| }}, |
| {`package p2; var (a, b, c = 1, 2, 3)`, []string{ |
| "a = 1", "b = 2", "c = 3", |
| }}, |
| {`package p3; var _ = f(); func f() int { return 1 }`, []string{ |
| "_ = f()", // blank var |
| }}, |
| {`package p4; var (a = 0; x = y; y = z; z = 0)`, []string{ |
| "a = 0", "z = 0", "y = z", "x = y", |
| }}, |
| {`package p5; var (a, _ = m[0]; m map[int]string)`, []string{ |
| "a, _ = m[0]", // blank var |
| }}, |
| {`package p6; var a, b = f(); func f() (_, _ int) { return z, z }; var z = 0`, []string{ |
| "z = 0", "a, b = f()", |
| }}, |
| {`package p7; var (a = func() int { return b }(); b = 1)`, []string{ |
| "b = 1", "a = (func() int literal)()", |
| }}, |
| {`package p8; var (a, b = func() (_, _ int) { return c, c }(); c = 1)`, []string{ |
| "c = 1", "a, b = (func() (_, _ int) literal)()", |
| }}, |
| {`package p9; type T struct{}; func (T) m() int { _ = y; return 0 }; var x, y = T.m, 1`, []string{ |
| "y = 1", "x = T.m", |
| }}, |
| {`package p10; var (d = c + b; a = 0; b = 0; c = 0)`, []string{ |
| "b = 0", "c = 0", "d = c + b", "a = 0", |
| }}, |
| // test case for issue 7131 |
| {`package main |
| |
| var counter int |
| func next() int { counter++; return counter } |
| |
| var _ = makeOrder() |
| func makeOrder() []int { return []int{f, b, d, e, c, a} } |
| |
| var a = next() |
| var b, c = next(), next() |
| var d, e, f = next(), next(), next() |
| `, []string{ |
| "a = next()", "b = next()", "c = next()", "d = next()", "e = next()", "f = next()", "_ = makeOrder()", |
| }}, |
| } |
| |
| for _, test := range tests { |
| info := Info{} |
| name := mustTypecheck(t, "InitOrderInfo", test.src, &info) |
| |
| // number of initializers must match |
| if len(info.InitOrder) != len(test.inits) { |
| t.Errorf("package %s: got %d initializers; want %d", name, len(info.InitOrder), len(test.inits)) |
| continue |
| } |
| |
| // initializers must match |
| for i, want := range test.inits { |
| got := info.InitOrder[i].String() |
| if got != want { |
| t.Errorf("package %s, init %d: got %s; want %s", name, i, got, want) |
| continue |
| } |
| } |
| } |
| } |
| |
| func TestFiles(t *testing.T) { |
| var sources = []string{ |
| "package p; type T struct{}; func (T) m1() {}", |
| "package p; func (T) m2() {}; var _ interface{ m1(); m2() } = T{}", |
| "package p; func (T) m3() {}; var _ interface{ m1(); m2(); m3() } = T{}", |
| } |
| |
| var conf Config |
| fset := token.NewFileSet() |
| pkg := NewPackage("p", "p") |
| check := NewChecker(&conf, fset, pkg, nil) |
| |
| for i, src := range sources { |
| filename := fmt.Sprintf("sources%d", i) |
| f, err := parser.ParseFile(fset, filename, src, 0) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if err := check.Files([]*ast.File{f}); err != nil { |
| t.Error(err) |
| } |
| } |
| } |