1// Copyright 2013 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5// This file contains tests for Eval. 6 7package types_test 8 9import ( 10 "fmt" 11 "go/ast" 12 "go/importer" 13 "go/parser" 14 "go/token" 15 "go/types" 16 "internal/godebug" 17 "internal/testenv" 18 "strings" 19 "testing" 20 21 . "go/types" 22) 23 24func testEval(t *testing.T, fset *token.FileSet, pkg *Package, pos token.Pos, expr string, typ Type, typStr, valStr string) { 25 gotTv, err := Eval(fset, pkg, pos, expr) 26 if err != nil { 27 t.Errorf("Eval(%q) failed: %s", expr, err) 28 return 29 } 30 if gotTv.Type == nil { 31 t.Errorf("Eval(%q) got nil type but no error", expr) 32 return 33 } 34 35 // compare types 36 if typ != nil { 37 // we have a type, check identity 38 if !Identical(gotTv.Type, typ) { 39 t.Errorf("Eval(%q) got type %s, want %s", expr, gotTv.Type, typ) 40 return 41 } 42 } else { 43 // we have a string, compare type string 44 gotStr := gotTv.Type.String() 45 if gotStr != typStr { 46 t.Errorf("Eval(%q) got type %s, want %s", expr, gotStr, typStr) 47 return 48 } 49 } 50 51 // compare values 52 gotStr := "" 53 if gotTv.Value != nil { 54 gotStr = gotTv.Value.ExactString() 55 } 56 if gotStr != valStr { 57 t.Errorf("Eval(%q) got value %s, want %s", expr, gotStr, valStr) 58 } 59} 60 61func TestEvalBasic(t *testing.T) { 62 fset := token.NewFileSet() 63 for _, typ := range Typ[Bool : String+1] { 64 testEval(t, fset, nil, nopos, typ.Name(), typ, "", "") 65 } 66} 67 68func TestEvalComposite(t *testing.T) { 69 fset := token.NewFileSet() 70 for _, test := range independentTestTypes { 71 testEval(t, fset, nil, nopos, test.src, nil, test.str, "") 72 } 73} 74 75func TestEvalArith(t *testing.T) { 76 var tests = []string{ 77 `true`, 78 `false == false`, 79 `12345678 + 87654321 == 99999999`, 80 `10 * 20 == 200`, 81 `(1<<500)*2 >> 100 == 2<<400`, 82 `"foo" + "bar" == "foobar"`, 83 `"abc" <= "bcd"`, 84 `len([10]struct{}{}) == 2*5`, 85 } 86 fset := token.NewFileSet() 87 for _, test := range tests { 88 testEval(t, fset, nil, nopos, test, Typ[UntypedBool], "", "true") 89 } 90} 91 92func TestEvalPos(t *testing.T) { 93 testenv.MustHaveGoBuild(t) 94 95 // The contents of /*-style comments are of the form 96 // expr => value, type 97 // where value may be the empty string. 98 // Each expr is evaluated at the position of the comment 99 // and the result is compared with the expected value 100 // and type. 101 var sources = []string{ 102 ` 103 package p 104 import "fmt" 105 import m "math" 106 const c = 3.0 107 type T []int 108 func f(a int, s string) float64 { 109 fmt.Println("calling f") 110 _ = m.Pi // use package math 111 const d int = c + 1 112 var x int 113 x = a + len(s) 114 return float64(x) 115 /* true => true, untyped bool */ 116 /* fmt.Println => , func(a ...any) (n int, err error) */ 117 /* c => 3, untyped float */ 118 /* T => , p.T */ 119 /* a => , int */ 120 /* s => , string */ 121 /* d => 4, int */ 122 /* x => , int */ 123 /* d/c => 1, int */ 124 /* c/2 => 3/2, untyped float */ 125 /* m.Pi < m.E => false, untyped bool */ 126 } 127 `, 128 ` 129 package p 130 /* c => 3, untyped float */ 131 type T1 /* T1 => , p.T1 */ struct {} 132 var v1 /* v1 => , int */ = 42 133 func /* f1 => , func(v1 float64) */ f1(v1 float64) { 134 /* f1 => , func(v1 float64) */ 135 /* v1 => , float64 */ 136 var c /* c => 3, untyped float */ = "foo" /* c => , string */ 137 { 138 var c struct { 139 c /* c => , string */ int 140 } 141 /* c => , struct{c int} */ 142 _ = c 143 } 144 _ = func(a, b, c int /* c => , string */) /* c => , int */ { 145 /* c => , int */ 146 } 147 _ = c 148 type FT /* FT => , p.FT */ interface{} 149 } 150 `, 151 ` 152 package p 153 /* T => , p.T */ 154 `, 155 ` 156 package p 157 import "io" 158 type R = io.Reader 159 func _() { 160 /* interface{R}.Read => , func(_ interface{io.Reader}, p []byte) (n int, err error) */ 161 _ = func() { 162 /* interface{io.Writer}.Write => , func(_ interface{io.Writer}, p []byte) (n int, err error) */ 163 type io interface {} // must not shadow io in line above 164 } 165 type R interface {} // must not shadow R in first line of this function body 166 } 167 `, 168 } 169 170 fset := token.NewFileSet() 171 var files []*ast.File 172 for i, src := range sources { 173 file, err := parser.ParseFile(fset, "p", src, parser.ParseComments) 174 if err != nil { 175 t.Fatalf("could not parse file %d: %s", i, err) 176 } 177 178 // Materialized aliases give a different (better) 179 // result for the final test, so skip it for now. 180 // TODO(adonovan): reenable when gotypesalias=1 is the default. 181 switch gotypesalias.Value() { 182 case "", "1": 183 if strings.Contains(src, "interface{R}.Read") { 184 continue 185 } 186 } 187 188 files = append(files, file) 189 } 190 191 conf := Config{Importer: importer.Default()} 192 pkg, err := conf.Check("p", fset, files, nil) 193 if err != nil { 194 t.Fatal(err) 195 } 196 197 for _, file := range files { 198 for _, group := range file.Comments { 199 for _, comment := range group.List { 200 s := comment.Text 201 if len(s) >= 4 && s[:2] == "/*" && s[len(s)-2:] == "*/" { 202 str, typ := split(s[2:len(s)-2], ", ") 203 str, val := split(str, "=>") 204 testEval(t, fset, pkg, comment.Pos(), str, nil, typ, val) 205 } 206 } 207 } 208 } 209} 210 211// gotypesalias controls the use of Alias types. 212var gotypesalias = godebug.New("#gotypesalias") 213 214// split splits string s at the first occurrence of s, trimming spaces. 215func split(s, sep string) (string, string) { 216 before, after, _ := strings.Cut(s, sep) 217 return strings.TrimSpace(before), strings.TrimSpace(after) 218} 219 220func TestCheckExpr(t *testing.T) { 221 testenv.MustHaveGoBuild(t) 222 223 // Each comment has the form /* expr => object */: 224 // expr is an identifier or selector expression that is passed 225 // to CheckExpr at the position of the comment, and object is 226 // the string form of the object it denotes. 227 const src = ` 228package p 229 230import "fmt" 231 232const c = 3.0 233type T []int 234type S struct{ X int } 235 236func f(a int, s string) S { 237 /* fmt.Println => func fmt.Println(a ...any) (n int, err error) */ 238 /* fmt.Stringer.String => func (fmt.Stringer).String() string */ 239 fmt.Println("calling f") 240 241 var fmt struct{ Println int } 242 /* fmt => var fmt struct{Println int} */ 243 /* fmt.Println => field Println int */ 244 /* f(1, "").X => field X int */ 245 fmt.Println = 1 246 247 /* append => builtin append */ 248 249 /* new(S).X => field X int */ 250 251 return S{} 252}` 253 254 fset := token.NewFileSet() 255 f, err := parser.ParseFile(fset, "p", src, parser.ParseComments) 256 if err != nil { 257 t.Fatal(err) 258 } 259 260 conf := Config{Importer: importer.Default()} 261 pkg, err := conf.Check("p", fset, []*ast.File{f}, nil) 262 if err != nil { 263 t.Fatal(err) 264 } 265 266 checkExpr := func(pos token.Pos, str string) (Object, error) { 267 expr, err := parser.ParseExprFrom(fset, "eval", str, 0) 268 if err != nil { 269 return nil, err 270 } 271 272 info := &Info{ 273 Uses: make(map[*ast.Ident]Object), 274 Selections: make(map[*ast.SelectorExpr]*Selection), 275 } 276 if err := CheckExpr(fset, pkg, pos, expr, info); err != nil { 277 return nil, fmt.Errorf("CheckExpr(%q) failed: %s", str, err) 278 } 279 switch expr := expr.(type) { 280 case *ast.Ident: 281 if obj, ok := info.Uses[expr]; ok { 282 return obj, nil 283 } 284 case *ast.SelectorExpr: 285 if sel, ok := info.Selections[expr]; ok { 286 return sel.Obj(), nil 287 } 288 if obj, ok := info.Uses[expr.Sel]; ok { 289 return obj, nil // qualified identifier 290 } 291 } 292 return nil, fmt.Errorf("no object for %s", str) 293 } 294 295 for _, group := range f.Comments { 296 for _, comment := range group.List { 297 s := comment.Text 298 if len(s) >= 4 && strings.HasPrefix(s, "/*") && strings.HasSuffix(s, "*/") { 299 pos := comment.Pos() 300 expr, wantObj := split(s[2:len(s)-2], "=>") 301 obj, err := checkExpr(pos, expr) 302 if err != nil { 303 t.Errorf("%s: %s", fset.Position(pos), err) 304 continue 305 } 306 if obj.String() != wantObj { 307 t.Errorf("%s: checkExpr(%s) = %s, want %v", 308 fset.Position(pos), expr, obj, wantObj) 309 } 310 } 311 } 312 } 313} 314 315func TestIssue65898(t *testing.T) { 316 const src = ` 317package p 318func _[A any](A) {} 319` 320 321 fset := token.NewFileSet() 322 f := mustParse(fset, src) 323 324 var conf types.Config 325 pkg, err := conf.Check(pkgName(src), fset, []*ast.File{f}, nil) 326 if err != nil { 327 t.Fatal(err) 328 } 329 330 for _, d := range f.Decls { 331 if fun, _ := d.(*ast.FuncDecl); fun != nil { 332 // type parameter A is not found at the start of the function type 333 if err := types.CheckExpr(fset, pkg, fun.Type.Pos(), fun.Type, nil); err == nil || !strings.Contains(err.Error(), "undefined") { 334 t.Fatalf("got %s, want undefined error", err) 335 } 336 // type parameter A must be found at the end of the function type 337 if err := types.CheckExpr(fset, pkg, fun.Type.End(), fun.Type, nil); err != nil { 338 t.Fatal(err) 339 } 340 } 341 } 342} 343