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

View as plain text