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