eval_test.go raw

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