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/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  
    24  func 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  
    61  func 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  
    68  func 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  
    75  func 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  
    92  func 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.
   212  var gotypesalias = godebug.New("#gotypesalias")
   213  
   214  // split splits string s at the first occurrence of s, trimming spaces.
   215  func split(s, sep string) (string, string) {
   216  	before, after, _ := strings.Cut(s, sep)
   217  	return strings.TrimSpace(before), strings.TrimSpace(after)
   218  }
   219  
   220  func 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 = `
   228  package p
   229  
   230  import "fmt"
   231  
   232  const c = 3.0
   233  type T []int
   234  type S struct{ X int }
   235  
   236  func 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  
   315  func TestIssue65898(t *testing.T) {
   316  	const src = `
   317  package p
   318  func _[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  

View as plain text