1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 package types2_test
31
32 import (
33 "bytes"
34 "cmd/compile/internal/syntax"
35 "flag"
36 "fmt"
37 "go/build"
38 "go/build/constraint"
39 "internal/buildcfg"
40 "internal/testenv"
41 "os"
42 "path/filepath"
43 "reflect"
44 "regexp"
45 "runtime"
46 "slices"
47 "strconv"
48 "strings"
49 "testing"
50
51 . "cmd/compile/internal/types2"
52 )
53
54 var (
55 haltOnError = flag.Bool("halt", false, "halt on error")
56 verifyErrors = flag.Bool("verify", false, "verify errors (rather than list them) in TestManual")
57 )
58
59 func parseFiles(t *testing.T, filenames []string, srcs [][]byte, mode syntax.Mode) ([]*syntax.File, []error) {
60 var files []*syntax.File
61 var errlist []error
62 errh := func(err error) { errlist = append(errlist, err) }
63 for i, filename := range filenames {
64 base := syntax.NewFileBase(filename)
65 r := bytes.NewReader(srcs[i])
66 file, err := syntax.Parse(base, r, errh, nil, mode)
67 if file == nil {
68 t.Fatalf("%s: %s", filename, err)
69 }
70 files = append(files, file)
71 }
72 return files, errlist
73 }
74
75 func unpackError(err error) (syntax.Pos, string) {
76 switch err := err.(type) {
77 case syntax.Error:
78 return err.Pos, err.Msg
79 case Error:
80 return err.Pos, err.Msg
81 default:
82 return nopos, err.Error()
83 }
84 }
85
86
87 func absDiff(x, y uint) uint {
88 if x < y {
89 return y - x
90 }
91 return x - y
92 }
93
94
95
96
97 func parseFlags(src []byte, flags *flag.FlagSet) error {
98
99 const prefix = "//"
100 if !bytes.HasPrefix(src, []byte(prefix)) {
101 return nil
102 }
103 src = src[len(prefix):]
104 if i := bytes.Index(src, []byte("-")); i < 0 || len(bytes.TrimSpace(src[:i])) != 0 {
105 return nil
106 }
107 end := bytes.Index(src, []byte("\n"))
108 const maxLen = 256
109 if end < 0 || end > maxLen {
110 return fmt.Errorf("flags comment line too long")
111 }
112
113 return flags.Parse(strings.Fields(string(src[:end])))
114 }
115
116
117
118
119
120
121
122
123
124
125 func testFiles(t *testing.T, filenames []string, srcs [][]byte, colDelta uint, manual bool, opts ...func(*Config)) {
126 if len(filenames) == 0 {
127 t.Fatal("no source files")
128 }
129
130
131 files, errlist := parseFiles(t, filenames, srcs, 0)
132 pkgName := "<no package>"
133 if len(files) > 0 {
134 pkgName = files[0].PkgName.Value
135 }
136 listErrors := manual && !*verifyErrors
137 if listErrors && len(errlist) > 0 {
138 t.Errorf("--- %s:", pkgName)
139 for _, err := range errlist {
140 t.Error(err)
141 }
142 }
143
144
145 var conf Config
146 conf.Trace = manual && testing.Verbose()
147 conf.Importer = defaultImporter()
148 conf.Error = func(err error) {
149 if *haltOnError {
150 defer panic(err)
151 }
152 if listErrors {
153 t.Error(err)
154 return
155 }
156 errlist = append(errlist, err)
157 }
158
159
160 for _, opt := range opts {
161 opt(&conf)
162 }
163
164
165 var goexperiment string
166 flags := flag.NewFlagSet("", flag.PanicOnError)
167 flags.StringVar(&conf.GoVersion, "lang", "", "")
168 flags.StringVar(&goexperiment, "goexperiment", "", "")
169 flags.BoolVar(&conf.FakeImportC, "fakeImportC", false, "")
170 if err := parseFlags(srcs[0], flags); err != nil {
171 t.Fatal(err)
172 }
173
174 if goexperiment != "" {
175 revert := setGOEXPERIMENT(goexperiment)
176 defer revert()
177 }
178
179
180 info := Info{
181 Types: make(map[syntax.Expr]TypeAndValue),
182 Instances: make(map[*syntax.Name]Instance),
183 Defs: make(map[*syntax.Name]Object),
184 Uses: make(map[*syntax.Name]Object),
185 Implicits: make(map[syntax.Node]Object),
186 Selections: make(map[*syntax.SelectorExpr]*Selection),
187 Scopes: make(map[syntax.Node]*Scope),
188 FileVersions: make(map[*syntax.PosBase]string),
189 }
190
191
192 pkg, err := conf.Check(pkgName, files, &info)
193
194
195 hasErrors := len(errlist) > 0 || err != nil
196 if errs := checkInvariants(pkg, &info, hasErrors); len(errs) > 0 {
197 for _, err := range errs {
198 t.Errorf("%s: invariant failure: %v", pkgName, err)
199 }
200 }
201 if listErrors {
202 return
203 }
204
205
206 errmap := make(map[string]map[uint][]syntax.Error)
207 for i, filename := range filenames {
208 if m := syntax.CommentMap(bytes.NewReader(srcs[i]), regexp.MustCompile("^ ERRORx? ")); len(m) > 0 {
209 errmap[filename] = m
210 }
211 }
212
213
214 var indices []int
215 for _, err := range errlist {
216 gotPos, gotMsg := unpackError(err)
217
218
219 filename := gotPos.Base().Filename()
220 filemap := errmap[filename]
221 line := gotPos.Line()
222 var errList []syntax.Error
223 if filemap != nil {
224 errList = filemap[line]
225 }
226
227
228 indices = indices[:0]
229 for i, want := range errList {
230 pattern, substr := strings.CutPrefix(want.Msg, " ERROR ")
231 if !substr {
232 var found bool
233 pattern, found = strings.CutPrefix(want.Msg, " ERRORx ")
234 if !found {
235 panic("unreachable")
236 }
237 }
238 unquoted, err := strconv.Unquote(strings.TrimSpace(pattern))
239 if err != nil {
240 t.Errorf("%s:%d:%d: invalid ERROR pattern (cannot unquote %s)", filename, line, want.Pos.Col(), pattern)
241 continue
242 }
243 if substr {
244 if !strings.Contains(gotMsg, unquoted) {
245 continue
246 }
247 } else {
248 rx, err := regexp.Compile(unquoted)
249 if err != nil {
250 t.Errorf("%s:%d:%d: %v", filename, line, want.Pos.Col(), err)
251 continue
252 }
253 if !rx.MatchString(gotMsg) {
254 continue
255 }
256 }
257 indices = append(indices, i)
258 }
259 if len(indices) == 0 {
260 t.Errorf("%s: no error expected: %q", gotPos, gotMsg)
261 continue
262 }
263
264
265
266 index := -1
267 var delta uint
268 for _, i := range indices {
269 if d := absDiff(gotPos.Col(), errList[i].Pos.Col()); index < 0 || d < delta {
270 index, delta = i, d
271 }
272 }
273
274
275 if delta > colDelta {
276 t.Errorf("%s: got col = %d; want %d", gotPos, gotPos.Col(), errList[index].Pos.Col())
277 }
278
279
280 if n := len(errList) - 1; n > 0 {
281
282 copy(errList[index:], errList[index+1:])
283 filemap[line] = errList[:n]
284 } else {
285
286 delete(filemap, line)
287 }
288
289
290 if len(filemap) == 0 {
291 delete(errmap, filename)
292 }
293 }
294
295
296 if len(errmap) > 0 {
297 t.Errorf("--- %s: unreported errors:", pkgName)
298 for filename, filemap := range errmap {
299 for line, errList := range filemap {
300 for _, err := range errList {
301 t.Errorf("%s:%d:%d: %s", filename, line, err.Pos.Col(), err.Msg)
302 }
303 }
304 }
305 }
306 }
307
308
309
310 func boolFieldAddr(conf *Config, name string) *bool {
311 v := reflect.Indirect(reflect.ValueOf(conf))
312 return (*bool)(v.FieldByName(name).Addr().UnsafePointer())
313 }
314
315
316
317
318
319 func setGOEXPERIMENT(goexperiment string) func() {
320 exp, err := buildcfg.ParseGOEXPERIMENT(runtime.GOOS, runtime.GOARCH, goexperiment)
321 if err != nil {
322 panic(err)
323 }
324 old := buildcfg.Experiment
325 buildcfg.Experiment = *exp
326 return func() { buildcfg.Experiment = old }
327 }
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343 func TestManual(t *testing.T) {
344 testenv.MustHaveGoBuild(t)
345
346 filenames := flag.Args()
347 if len(filenames) == 0 {
348 filenames = []string{filepath.FromSlash("testdata/manual.go")}
349 }
350
351 info, err := os.Stat(filenames[0])
352 if err != nil {
353 t.Fatalf("TestManual: %v", err)
354 }
355
356 DefPredeclaredTestFuncs()
357 if info.IsDir() {
358 if len(filenames) > 1 {
359 t.Fatal("TestManual: must have only one directory argument")
360 }
361 testDir(t, filenames[0], 0, true)
362 } else {
363 testPkg(t, filenames, 0, true)
364 }
365 }
366
367 func TestLongConstants(t *testing.T) {
368 t.Parallel()
369 format := `package longconst; const _ = %s /* ERROR "constant overflow" */; const _ = %s // ERROR "excessively long constant"`
370 src := fmt.Sprintf(format, strings.Repeat("1", 9999), strings.Repeat("1", 10001))
371 testFiles(t, []string{"longconst.go"}, [][]byte{[]byte(src)}, 0, false)
372 }
373
374 func withSizes(sizes Sizes) func(*Config) {
375 return func(cfg *Config) {
376 cfg.Sizes = sizes
377 }
378 }
379
380
381
382
383 func TestIndexRepresentability(t *testing.T) {
384 t.Parallel()
385 const src = `package index; var s []byte; var _ = s[int64 /* ERRORx "int64\\(1\\) << 40 \\(.*\\) overflows int" */ (1) << 40]`
386 testFiles(t, []string{"index.go"}, [][]byte{[]byte(src)}, 0, false, withSizes(&StdSizes{4, 4}))
387 }
388
389 func TestIssue47243_TypedRHS(t *testing.T) {
390 t.Parallel()
391
392
393 const src = `package issue47243; var a uint64; var _ = a << uint64(4294967296)`
394 testFiles(t, []string{"p.go"}, [][]byte{[]byte(src)}, 0, false, withSizes(&StdSizes{4, 4}))
395 }
396
397 func TestCheck(t *testing.T) {
398 DefPredeclaredTestFuncs()
399 testDirFiles(t, "../../../../internal/types/testdata/check", 50, false)
400 }
401 func TestSpec(t *testing.T) { testDirFiles(t, "../../../../internal/types/testdata/spec", 20, false) }
402 func TestExamples(t *testing.T) {
403 testDirFiles(t, "../../../../internal/types/testdata/examples", 125, false)
404 }
405 func TestFixedbugs(t *testing.T) {
406 testDirFiles(t, "../../../../internal/types/testdata/fixedbugs", 100, false)
407 }
408 func TestLocal(t *testing.T) { testDirFiles(t, "testdata/local", 0, false) }
409
410 func testDirFiles(t *testing.T, dir string, colDelta uint, manual bool) {
411 testenv.MustHaveGoBuild(t)
412 dir = filepath.FromSlash(dir)
413
414 fis, err := os.ReadDir(dir)
415 if err != nil {
416 t.Error(err)
417 return
418 }
419
420 for _, fi := range fis {
421 path := filepath.Join(dir, fi.Name())
422
423
424 if fi.IsDir() {
425 testDir(t, path, colDelta, manual)
426 } else {
427 t.Run(filepath.Base(path), func(t *testing.T) {
428 testPkg(t, []string{path}, colDelta, manual)
429 })
430 }
431 }
432 }
433
434 func testDir(t *testing.T, dir string, colDelta uint, manual bool) {
435 fis, err := os.ReadDir(dir)
436 if err != nil {
437 t.Error(err)
438 return
439 }
440
441 var filenames []string
442 for _, fi := range fis {
443 filenames = append(filenames, filepath.Join(dir, fi.Name()))
444 }
445
446 t.Run(filepath.Base(dir), func(t *testing.T) {
447 testPkg(t, filenames, colDelta, manual)
448 })
449 }
450
451 func testPkg(t *testing.T, filenames []string, colDelta uint, manual bool) {
452 fs := filenames[:0]
453 srcs := make([][]byte, 0, len(filenames))
454 for _, filename := range filenames {
455 src, err := os.ReadFile(filename)
456 if err != nil {
457 t.Fatalf("could not read %s: %v", filename, err)
458 }
459 if !shouldTest(src) {
460 continue
461 }
462 fs = append(fs, filename)
463 srcs = append(srcs, src)
464 }
465 if len(fs) == 0 {
466 t.Skip("all files skipped by build tags")
467 }
468 testFiles(t, fs, srcs, colDelta, manual)
469 }
470
471
472
473 func shouldTest(src []byte) bool {
474 match := func(tag string) bool {
475
476 if slices.Contains(build.Default.ReleaseTags, tag) {
477 return true
478 }
479 return tag == runtime.GOOS || tag == runtime.GOARCH
480 }
481 for line := range strings.SplitSeq(string(src), "\n") {
482 if strings.HasPrefix(line, "package ") {
483 break
484 }
485 if expr, err := constraint.Parse(line); err == nil {
486 return expr.Eval(match)
487 }
488 }
489 return true
490 }
491
View as plain text