| // 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/rangefunc" |
| "cmd/compile/internal/syntax" |
| "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 |
| } |
| |
| // 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), |
| EnableAlias: true, |
| } |
| 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() |
| |
| // 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) |
| |
| return pkg, info, rangeInfo |
| } |
| |
| // 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 |
| } |
| } |
| } |