| // Copyright 2021 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package noder | |
| import ( | |
| "fmt" | |
| "internal/buildcfg" | |
| "internal/types/errors" | |
| "regexp" | |
| "sort" | |
| "cmd/compile/internal/base" | |
| "cmd/compile/internal/midway" | |
| "cmd/compile/internal/rangefunc" | |
| "cmd/compile/internal/syntax" | |
| "cmd/compile/internal/typecheck" | |
| "cmd/compile/internal/types" | |
| "cmd/compile/internal/types2" | |
| "cmd/internal/src" | |
| ) | |
| var versionErrorRx = regexp.MustCompile(`requires go[0-9]+\.[0-9]+ or later`) | |
| // checkFiles configures and runs the types2 checker on the given | |
| // parsed source files and then returns the result. | |
| // The map result value indicates which closures are generated from the bodies of range function loops. | |
| func checkFiles(m posMap, noders []*noder) (*types2.Package, *types2.Info, map[*syntax.FuncLit]bool) { | |
| if base.SyntaxErrors() != 0 { | |
| base.ErrorExit() | |
| } | |
| // setup and syntax error reporting | |
| files := make([]*syntax.File, len(noders)) | |
| // fileBaseMap maps all file pos bases back to *syntax.File | |
| // for checking Go version mismatched. | |
| fileBaseMap := make(map[*syntax.PosBase]*syntax.File) | |
| for i, p := range noders { | |
| files[i] = p.file | |
| // The file.Pos() is the position of the package clause. | |
| // If there's a //line directive before that, file.Pos().Base() | |
| // refers to that directive, not the file itself. | |
| // Make sure to consistently map back to file base, here and | |
| // when we look for a file in the conf.Error handler below, | |
| // otherwise the file may not be found (was go.dev/issue/67141). | |
| fileBaseMap[p.file.Pos().FileBase()] = p.file | |
| } | |
| didMidway := false | |
| recheck: | |
| // typechecking | |
| ctxt := types2.NewContext() | |
| importer := gcimports{ | |
| ctxt: ctxt, | |
| packages: make(map[string]*types2.Package), | |
| } | |
| conf := types2.Config{ | |
| Context: ctxt, | |
| GoVersion: base.Flag.Lang, | |
| IgnoreBranchErrors: true, // parser already checked via syntax.CheckBranches mode | |
| Importer: &importer, | |
| Sizes: types2.SizesFor("gc", buildcfg.GOARCH), | |
| } | |
| if base.Flag.ErrorURL { | |
| conf.ErrorURL = " [go.dev/e/%s]" | |
| } | |
| info := &types2.Info{ | |
| StoreTypesInSyntax: true, | |
| Defs: make(map[*syntax.Name]types2.Object), | |
| Uses: make(map[*syntax.Name]types2.Object), | |
| Selections: make(map[*syntax.SelectorExpr]*types2.Selection), | |
| Implicits: make(map[syntax.Node]types2.Object), | |
| Scopes: make(map[syntax.Node]*types2.Scope), | |
| Instances: make(map[*syntax.Name]types2.Instance), | |
| FileVersions: make(map[*syntax.PosBase]string), | |
| // expand as needed | |
| } | |
| conf.Error = func(err error) { | |
| terr := err.(types2.Error) | |
| msg := terr.Msg | |
| if versionErrorRx.MatchString(msg) { | |
| fileBase := terr.Pos.FileBase() | |
| fileVersion := info.FileVersions[fileBase] | |
| file := fileBaseMap[fileBase] | |
| if file == nil { | |
| // This should never happen, but be careful and don't crash. | |
| } else if file.GoVersion == fileVersion { | |
| // If we have a version error caused by //go:build, report it. | |
| msg = fmt.Sprintf("%s (file declares //go:build %s)", msg, fileVersion) | |
| } else { | |
| // Otherwise, hint at the -lang setting. | |
| msg = fmt.Sprintf("%s (-lang was set to %s; check go.mod)", msg, base.Flag.Lang) | |
| } | |
| } | |
| base.ErrorfAt(m.makeXPos(terr.Pos), terr.Code, "%s", msg) | |
| } | |
| pkg, err := conf.Check(base.Ctxt.Pkgpath, files, info) | |
| base.ExitIfErrors() | |
| if err != nil { | |
| base.FatalfAt(src.NoXPos, "conf.Check error: %v", err) | |
| } | |
| // Check for anonymous interface cycles (#56103). | |
| // TODO(gri) move this code into the type checkers (types2 and go/types) | |
| var f cycleFinder | |
| for _, file := range files { | |
| syntax.Inspect(file, func(n syntax.Node) bool { | |
| if n, ok := n.(*syntax.InterfaceType); ok { | |
| if f.hasCycle(types2.Unalias(n.GetTypeInfo().Type).(*types2.Interface)) { | |
| base.ErrorfAt(m.makeXPos(n.Pos()), errors.InvalidTypeCycle, "invalid recursive type: anonymous interface refers to itself (see https://go.dev/issue/56103)") | |
| for typ := range f.cyclic { | |
| f.cyclic[typ] = false // suppress duplicate errors | |
| } | |
| } | |
| return false | |
| } | |
| return true | |
| }) | |
| } | |
| base.ExitIfErrors() | |
| // Implementation restriction: we don't allow not-in-heap types to | |
| // be used as type arguments (#54765). | |
| { | |
| type nihTarg struct { | |
| pos src.XPos | |
| typ types2.Type | |
| } | |
| var nihTargs []nihTarg | |
| for name, inst := range info.Instances { | |
| for i := 0; i < inst.TypeArgs.Len(); i++ { | |
| if targ := inst.TypeArgs.At(i); isNotInHeap(targ) { | |
| nihTargs = append(nihTargs, nihTarg{m.makeXPos(name.Pos()), targ}) | |
| } | |
| } | |
| } | |
| sort.Slice(nihTargs, func(i, j int) bool { | |
| ti, tj := nihTargs[i], nihTargs[j] | |
| return ti.pos.Before(tj.pos) | |
| }) | |
| for _, targ := range nihTargs { | |
| base.ErrorfAt(targ.pos, 0, "cannot use incomplete (or unallocatable) type as a type argument: %v", targ.typ) | |
| } | |
| } | |
| base.ExitIfErrors() | |
| // Implementation restriction: we don't allow not-in-heap types to | |
| // be used as map keys/values, or channel. | |
| { | |
| for _, file := range files { | |
| syntax.Inspect(file, func(n syntax.Node) bool { | |
| if n, ok := n.(*syntax.TypeDecl); ok { | |
| switch n := n.Type.(type) { | |
| case *syntax.MapType: | |
| typ := n.GetTypeInfo().Type.Underlying().(*types2.Map) | |
| if isNotInHeap(typ.Key()) { | |
| base.ErrorfAt(m.makeXPos(n.Pos()), 0, "incomplete (or unallocatable) map key not allowed") | |
| } | |
| if isNotInHeap(typ.Elem()) { | |
| base.ErrorfAt(m.makeXPos(n.Pos()), 0, "incomplete (or unallocatable) map value not allowed") | |
| } | |
| case *syntax.ChanType: | |
| typ := n.GetTypeInfo().Type.Underlying().(*types2.Chan) | |
| if isNotInHeap(typ.Elem()) { | |
| base.ErrorfAt(m.makeXPos(n.Pos()), 0, "chan of incomplete (or unallocatable) type not allowed") | |
| } | |
| } | |
| } | |
| return true | |
| }) | |
| } | |
| } | |
| base.ExitIfErrors() | |
| if len(base.Debug.AstDump) > 0 { | |
| dumpSyntax(pkg, info, files, "checked") | |
| } | |
| if buildcfg.Experiment.SIMD && !didMidway { | |
| didMidway = true | |
| // Perform midway transformation on AST directly | |
| if midway.RewriteWrapper(pkg, info, files) { | |
| // midway made changes; type checking must be repeated. | |
| if len(base.Debug.AstDump) > 0 { | |
| // TODO how should this interact with -W and textual dumps | |
| dumpSyntax(pkg, info, files, "midway before recheck") | |
| } | |
| // necessary to reset type checking | |
| for _, p := range types.PkgMap() { | |
| p.Direct = false | |
| } | |
| // necessary to reset type checking | |
| typecheck.Target.Imports = nil | |
| goto recheck | |
| } | |
| } | |
| if len(base.Debug.AstDump) > 0 { | |
| dumpSyntax(pkg, info, files, "midway after recheck") | |
| } | |
| // Rewrite range over function to explicit function calls | |
| // with the loop bodies converted into new implicit closures. | |
| // We do this now, before serialization to unified IR, so that if the | |
| // implicit closures are inlined, we will have the unified IR form. | |
| // If we do the rewrite in the back end, like between typecheck and walk, | |
| // then the new implicit closure will not have a unified IR inline body, | |
| // and bodyReaderFor will fail. | |
| rangeInfo := rangefunc.Rewrite(pkg, info, files) | |
| if len(base.Debug.AstDump) > 0 { | |
| dumpSyntax(pkg, info, files, "rangefunc") | |
| } | |
| return pkg, info, rangeInfo | |
| } | |
| func dumpSyntax(pkg *types2.Package, info *types2.Info, files []*syntax.File, phase string) { | |
| for _, file := range files { | |
| for _, decl := range file.DeclList { | |
| if fn, ok := decl.(*syntax.FuncDecl); ok { | |
| if MatchASTDump(fn) { | |
| DumpNodeHTML(pkg, file, info, fn, phase, fn) | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // A cycleFinder detects anonymous interface cycles (go.dev/issue/56103). | |
| type cycleFinder struct { | |
| cyclic map[*types2.Interface]bool | |
| } | |
| // hasCycle reports whether typ is part of an anonymous interface cycle. | |
| func (f *cycleFinder) hasCycle(typ *types2.Interface) bool { | |
| // We use Method instead of ExplicitMethod to implicitly expand any | |
| // embedded interfaces. Then we just need to walk any anonymous | |
| // types, keeping track of *types2.Interface types we visit along | |
| // the way. | |
| for i := 0; i < typ.NumMethods(); i++ { | |
| if f.visit(typ.Method(i).Type()) { | |
| return true | |
| } | |
| } | |
| return false | |
| } | |
| // visit recursively walks typ0 to check any referenced interface types. | |
| func (f *cycleFinder) visit(typ0 types2.Type) bool { | |
| for { // loop for tail recursion | |
| switch typ := types2.Unalias(typ0).(type) { | |
| default: | |
| base.Fatalf("unexpected type: %T", typ) | |
| case *types2.Basic, *types2.Named, *types2.TypeParam: | |
| return false // named types cannot be part of an anonymous cycle | |
| case *types2.Pointer: | |
| typ0 = typ.Elem() | |
| case *types2.Array: | |
| typ0 = typ.Elem() | |
| case *types2.Chan: | |
| typ0 = typ.Elem() | |
| case *types2.Map: | |
| if f.visit(typ.Key()) { | |
| return true | |
| } | |
| typ0 = typ.Elem() | |
| case *types2.Slice: | |
| typ0 = typ.Elem() | |
| case *types2.Struct: | |
| for i := 0; i < typ.NumFields(); i++ { | |
| if f.visit(typ.Field(i).Type()) { | |
| return true | |
| } | |
| } | |
| return false | |
| case *types2.Interface: | |
| // The empty interface (e.g., "any") cannot be part of a cycle. | |
| if typ.NumExplicitMethods() == 0 && typ.NumEmbeddeds() == 0 { | |
| return false | |
| } | |
| // As an optimization, we wait to allocate cyclic here, after | |
| // we've found at least one other (non-empty) anonymous | |
| // interface. This means when a cycle is present, we need to | |
| // make an extra recursive call to actually detect it. But for | |
| // most packages, it allows skipping the map allocation | |
| // entirely. | |
| if x, ok := f.cyclic[typ]; ok { | |
| return x | |
| } | |
| if f.cyclic == nil { | |
| f.cyclic = make(map[*types2.Interface]bool) | |
| } | |
| f.cyclic[typ] = true | |
| if f.hasCycle(typ) { | |
| return true | |
| } | |
| f.cyclic[typ] = false | |
| return false | |
| case *types2.Signature: | |
| return f.visit(typ.Params()) || f.visit(typ.Results()) | |
| case *types2.Tuple: | |
| for i := 0; i < typ.Len(); i++ { | |
| if f.visit(typ.At(i).Type()) { | |
| return true | |
| } | |
| } | |
| return false | |
| } | |
| } | |
| } |