Source file src/cmd/vendor/golang.org/x/tools/go/analysis/passes/stdversion/stdversion.go

     1  // Copyright 2024 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  // Package stdversion reports uses of standard library symbols that are
     6  // "too new" for the Go version in force in the referring file.
     7  package stdversion
     8  
     9  import (
    10  	"go/ast"
    11  	"go/build"
    12  	"go/types"
    13  	"slices"
    14  
    15  	"golang.org/x/tools/go/analysis"
    16  	"golang.org/x/tools/go/analysis/passes/inspect"
    17  	"golang.org/x/tools/go/ast/inspector"
    18  	"golang.org/x/tools/internal/typesinternal"
    19  	"golang.org/x/tools/internal/versions"
    20  )
    21  
    22  const Doc = `report uses of too-new standard library symbols
    23  
    24  The stdversion analyzer reports references to symbols in the standard
    25  library that were introduced by a Go release higher than the one in
    26  force in the referring file. (Recall that the file's Go version is
    27  defined by the 'go' directive its module's go.mod file, or by a
    28  "//go:build go1.X" build tag at the top of the file.)
    29  
    30  The analyzer does not report a diagnostic for a reference to a "too
    31  new" field or method of a type that is itself "too new", as this may
    32  have false positives, for example if fields or methods are accessed
    33  through a type alias that is guarded by a Go version constraint.
    34  `
    35  
    36  var Analyzer = &analysis.Analyzer{
    37  	Name:             "stdversion",
    38  	Doc:              Doc,
    39  	Requires:         []*analysis.Analyzer{inspect.Analyzer},
    40  	URL:              "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/stdversion",
    41  	RunDespiteErrors: true,
    42  	Run:              run,
    43  }
    44  
    45  func run(pass *analysis.Pass) (any, error) {
    46  	// Prior to go1.22, versions.FileVersion returns only the
    47  	// toolchain version, which is of no use to us, so
    48  	// disable this analyzer on earlier versions.
    49  	if !slices.Contains(build.Default.ReleaseTags, "go1.22") {
    50  		return nil, nil
    51  	}
    52  
    53  	// Don't report diagnostics for modules marked before go1.21,
    54  	// since at that time the go directive wasn't clearly
    55  	// specified as a toolchain requirement.
    56  	pkgVersion := pass.Pkg.GoVersion()
    57  	if !versions.AtLeast(pkgVersion, "go1.21") {
    58  		return nil, nil
    59  	}
    60  
    61  	// disallowedSymbols returns the set of standard library symbols
    62  	// in a given package that are disallowed at the specified Go version.
    63  	type key struct {
    64  		pkg     *types.Package
    65  		version string
    66  	}
    67  	memo := make(map[key]map[types.Object]string) // records symbol's minimum Go version
    68  	disallowedSymbols := func(pkg *types.Package, version string) map[types.Object]string {
    69  		k := key{pkg, version}
    70  		disallowed, ok := memo[k]
    71  		if !ok {
    72  			disallowed = typesinternal.TooNewStdSymbols(pkg, version)
    73  			memo[k] = disallowed
    74  		}
    75  		return disallowed
    76  	}
    77  
    78  	// Scan the syntax looking for references to symbols
    79  	// that are disallowed by the version of the file.
    80  	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
    81  	nodeFilter := []ast.Node{
    82  		(*ast.File)(nil),
    83  		(*ast.Ident)(nil),
    84  	}
    85  	var fileVersion string // "" => no check
    86  	inspect.Preorder(nodeFilter, func(n ast.Node) {
    87  		switch n := n.(type) {
    88  		case *ast.File:
    89  			if ast.IsGenerated(n) {
    90  				// Suppress diagnostics in generated files (such as cgo).
    91  				fileVersion = ""
    92  			} else {
    93  				fileVersion = versions.Lang(versions.FileVersion(pass.TypesInfo, n))
    94  				// (may be "" if unknown)
    95  			}
    96  
    97  		case *ast.Ident:
    98  			if fileVersion != "" {
    99  				if obj, ok := pass.TypesInfo.Uses[n]; ok && obj.Pkg() != nil {
   100  					disallowed := disallowedSymbols(obj.Pkg(), fileVersion)
   101  					if minVersion, ok := disallowed[origin(obj)]; ok {
   102  						noun := "module"
   103  						if fileVersion != pkgVersion {
   104  							noun = "file"
   105  						}
   106  						pass.ReportRangef(n, "%s.%s requires %v or later (%s is %s)",
   107  							obj.Pkg().Name(), obj.Name(), minVersion, noun, fileVersion)
   108  					}
   109  				}
   110  			}
   111  		}
   112  	})
   113  	return nil, nil
   114  }
   115  
   116  // origin returns the original uninstantiated symbol for obj.
   117  func origin(obj types.Object) types.Object {
   118  	switch obj := obj.(type) {
   119  	case *types.Var:
   120  		return obj.Origin()
   121  	case *types.Func:
   122  		return obj.Origin()
   123  	case *types.TypeName:
   124  		if named, ok := obj.Type().(*types.Named); ok { // (don't unalias)
   125  			return named.Origin().Obj()
   126  		}
   127  	}
   128  	return obj
   129  }
   130  

View as plain text