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

View as plain text