Source file src/go/types/eval_test.go

     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  

View as plain text