| // 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. |
| |
| // This file contains tests for Eval. |
| |
| package types_test |
| |
| import ( |
| "fmt" |
| "go/ast" |
| "go/importer" |
| "go/parser" |
| "go/token" |
| "internal/testenv" |
| "strings" |
| "testing" |
| |
| . "go/types" |
| ) |
| |
| func testEval(t *testing.T, fset *token.FileSet, pkg *Package, pos token.Pos, expr string, typ Type, typStr, valStr string) { |
| gotTv, err := Eval(fset, pkg, pos, expr) |
| if err != nil { |
| t.Errorf("Eval(%q) failed: %s", expr, err) |
| return |
| } |
| if gotTv.Type == nil { |
| t.Errorf("Eval(%q) got nil type but no error", expr) |
| return |
| } |
| |
| // compare types |
| if typ != nil { |
| // we have a type, check identity |
| if !Identical(gotTv.Type, typ) { |
| t.Errorf("Eval(%q) got type %s, want %s", expr, gotTv.Type, typ) |
| return |
| } |
| } else { |
| // we have a string, compare type string |
| gotStr := gotTv.Type.String() |
| if gotStr != typStr { |
| t.Errorf("Eval(%q) got type %s, want %s", expr, gotStr, typStr) |
| return |
| } |
| } |
| |
| // compare values |
| gotStr := "" |
| if gotTv.Value != nil { |
| gotStr = gotTv.Value.ExactString() |
| } |
| if gotStr != valStr { |
| t.Errorf("Eval(%q) got value %s, want %s", expr, gotStr, valStr) |
| } |
| } |
| |
| func TestEvalBasic(t *testing.T) { |
| fset := token.NewFileSet() |
| for _, typ := range Typ[Bool : String+1] { |
| testEval(t, fset, nil, token.NoPos, typ.Name(), typ, "", "") |
| } |
| } |
| |
| func TestEvalComposite(t *testing.T) { |
| fset := token.NewFileSet() |
| for _, test := range independentTestTypes { |
| testEval(t, fset, nil, token.NoPos, test.src, nil, test.str, "") |
| } |
| } |
| |
| func TestEvalArith(t *testing.T) { |
| var tests = []string{ |
| `true`, |
| `false == false`, |
| `12345678 + 87654321 == 99999999`, |
| `10 * 20 == 200`, |
| `(1<<1000)*2 >> 100 == 2<<900`, |
| `"foo" + "bar" == "foobar"`, |
| `"abc" <= "bcd"`, |
| `len([10]struct{}{}) == 2*5`, |
| } |
| fset := token.NewFileSet() |
| for _, test := range tests { |
| testEval(t, fset, nil, token.NoPos, test, Typ[UntypedBool], "", "true") |
| } |
| } |
| |
| func TestEvalPos(t *testing.T) { |
| testenv.MustHaveGoBuild(t) |
| |
| // The contents of /*-style comments are of the form |
| // expr => value, type |
| // where value may be the empty string. |
| // Each expr is evaluated at the position of the comment |
| // and the result is compared with the expected value |
| // and type. |
| var sources = []string{ |
| ` |
| package p |
| import "fmt" |
| import m "math" |
| const c = 3.0 |
| type T []int |
| func f(a int, s string) float64 { |
| fmt.Println("calling f") |
| _ = m.Pi // use package math |
| const d int = c + 1 |
| var x int |
| x = a + len(s) |
| return float64(x) |
| /* true => true, untyped bool */ |
| /* fmt.Println => , func(a ...interface{}) (n int, err error) */ |
| /* c => 3, untyped float */ |
| /* T => , p.T */ |
| /* a => , int */ |
| /* s => , string */ |
| /* d => 4, int */ |
| /* x => , int */ |
| /* d/c => 1, int */ |
| /* c/2 => 3/2, untyped float */ |
| /* m.Pi < m.E => false, untyped bool */ |
| } |
| `, |
| ` |
| package p |
| /* c => 3, untyped float */ |
| type T1 /* T1 => , p.T1 */ struct {} |
| var v1 /* v1 => , int */ = 42 |
| func /* f1 => , func(v1 float64) */ f1(v1 float64) { |
| /* f1 => , func(v1 float64) */ |
| /* v1 => , float64 */ |
| var c /* c => 3, untyped float */ = "foo" /* c => , string */ |
| { |
| var c struct { |
| c /* c => , string */ int |
| } |
| /* c => , struct{c int} */ |
| _ = c |
| } |
| _ = func(a, b, c int) /* c => , string */ { |
| /* c => , int */ |
| } |
| _ = c |
| type FT /* FT => , p.FT */ interface{} |
| } |
| `, |
| ` |
| package p |
| /* T => , p.T */ |
| `, |
| ` |
| package p |
| import "io" |
| type R = io.Reader |
| func _() { |
| /* interface{R}.Read => , func(interface{io.Reader}, p []byte) (n int, err error) */ |
| _ = func() { |
| /* interface{io.Writer}.Write => , func(interface{io.Writer}, p []byte) (n int, err error) */ |
| type io interface {} // must not shadow io in line above |
| } |
| type R interface {} // must not shadow R in first line of this function body |
| } |
| `, |
| } |
| |
| fset := token.NewFileSet() |
| var files []*ast.File |
| for i, src := range sources { |
| file, err := parser.ParseFile(fset, "p", src, parser.ParseComments) |
| if err != nil { |
| t.Fatalf("could not parse file %d: %s", i, err) |
| } |
| files = append(files, file) |
| } |
| |
| conf := Config{Importer: importer.Default()} |
| pkg, err := conf.Check("p", fset, files, nil) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| for _, file := range files { |
| for _, group := range file.Comments { |
| for _, comment := range group.List { |
| s := comment.Text |
| if len(s) >= 4 && s[:2] == "/*" && s[len(s)-2:] == "*/" { |
| str, typ := split(s[2:len(s)-2], ", ") |
| str, val := split(str, "=>") |
| testEval(t, fset, pkg, comment.Pos(), str, nil, typ, val) |
| } |
| } |
| } |
| } |
| } |
| |
| // split splits string s at the first occurrence of s. |
| func split(s, sep string) (string, string) { |
| i := strings.Index(s, sep) |
| return strings.TrimSpace(s[:i]), strings.TrimSpace(s[i+len(sep):]) |
| } |
| |
| func TestCheckExpr(t *testing.T) { |
| testenv.MustHaveGoBuild(t) |
| |
| // Each comment has the form /* expr => object */: |
| // expr is an identifier or selector expression that is passed |
| // to CheckExpr at the position of the comment, and object is |
| // the string form of the object it denotes. |
| const src = ` |
| package p |
| |
| import "fmt" |
| |
| const c = 3.0 |
| type T []int |
| type S struct{ X int } |
| |
| func f(a int, s string) S { |
| /* fmt.Println => func fmt.Println(a ...interface{}) (n int, err error) */ |
| /* fmt.Stringer.String => func (fmt.Stringer).String() string */ |
| fmt.Println("calling f") |
| |
| var fmt struct{ Println int } |
| /* fmt => var fmt struct{Println int} */ |
| /* fmt.Println => field Println int */ |
| /* f(1, "").X => field X int */ |
| fmt.Println = 1 |
| |
| /* append => builtin append */ |
| |
| /* new(S).X => field X int */ |
| |
| return S{} |
| }` |
| |
| fset := token.NewFileSet() |
| f, err := parser.ParseFile(fset, "p", src, parser.ParseComments) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| conf := Config{Importer: importer.Default()} |
| pkg, err := conf.Check("p", fset, []*ast.File{f}, nil) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| checkExpr := func(pos token.Pos, str string) (Object, error) { |
| expr, err := parser.ParseExprFrom(fset, "eval", str, 0) |
| if err != nil { |
| return nil, err |
| } |
| |
| info := &Info{ |
| Uses: make(map[*ast.Ident]Object), |
| Selections: make(map[*ast.SelectorExpr]*Selection), |
| } |
| if err := CheckExpr(fset, pkg, pos, expr, info); err != nil { |
| return nil, fmt.Errorf("CheckExpr(%q) failed: %s", str, err) |
| } |
| switch expr := expr.(type) { |
| case *ast.Ident: |
| if obj, ok := info.Uses[expr]; ok { |
| return obj, nil |
| } |
| case *ast.SelectorExpr: |
| if sel, ok := info.Selections[expr]; ok { |
| return sel.Obj(), nil |
| } |
| if obj, ok := info.Uses[expr.Sel]; ok { |
| return obj, nil // qualified identifier |
| } |
| } |
| return nil, fmt.Errorf("no object for %s", str) |
| } |
| |
| for _, group := range f.Comments { |
| for _, comment := range group.List { |
| s := comment.Text |
| if len(s) >= 4 && strings.HasPrefix(s, "/*") && strings.HasSuffix(s, "*/") { |
| pos := comment.Pos() |
| expr, wantObj := split(s[2:len(s)-2], "=>") |
| obj, err := checkExpr(pos, expr) |
| if err != nil { |
| t.Errorf("%s: %s", fset.Position(pos), err) |
| continue |
| } |
| if obj.String() != wantObj { |
| t.Errorf("%s: checkExpr(%s) = %s, want %v", |
| fset.Position(pos), expr, obj, wantObj) |
| } |
| } |
| } |
| } |
| } |