golang.org/x/tools@v0.21.1-0.20240520172518-788d39e776b1/internal/typesinternal/toonew.go (about)

     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 typesinternal
     6  
     7  import (
     8  	"go/types"
     9  
    10  	"golang.org/x/tools/internal/stdlib"
    11  	"golang.org/x/tools/internal/versions"
    12  )
    13  
    14  // TooNewStdSymbols computes the set of package-level symbols
    15  // exported by pkg that are not available at the specified version.
    16  // The result maps each symbol to its minimum version.
    17  //
    18  // The pkg is allowed to contain type errors.
    19  func TooNewStdSymbols(pkg *types.Package, version string) map[types.Object]string {
    20  	disallowed := make(map[types.Object]string)
    21  
    22  	// Pass 1: package-level symbols.
    23  	symbols := stdlib.PackageSymbols[pkg.Path()]
    24  	for _, sym := range symbols {
    25  		symver := sym.Version.String()
    26  		if versions.Before(version, symver) {
    27  			switch sym.Kind {
    28  			case stdlib.Func, stdlib.Var, stdlib.Const, stdlib.Type:
    29  				disallowed[pkg.Scope().Lookup(sym.Name)] = symver
    30  			}
    31  		}
    32  	}
    33  
    34  	// Pass 2: fields and methods.
    35  	//
    36  	// We allow fields and methods if their associated type is
    37  	// disallowed, as otherwise we would report false positives
    38  	// for compatibility shims. Consider:
    39  	//
    40  	//   //go:build go1.22
    41  	//   type T struct { F std.Real } // correct new API
    42  	//
    43  	//   //go:build !go1.22
    44  	//   type T struct { F fake } // shim
    45  	//   type fake struct { ... }
    46  	//   func (fake) M () {}
    47  	//
    48  	// These alternative declarations of T use either the std.Real
    49  	// type, introduced in go1.22, or a fake type, for the field
    50  	// F. (The fakery could be arbitrarily deep, involving more
    51  	// nested fields and methods than are shown here.) Clients
    52  	// that use the compatibility shim T will compile with any
    53  	// version of go, whether older or newer than go1.22, but only
    54  	// the newer version will use the std.Real implementation.
    55  	//
    56  	// Now consider a reference to method M in new(T).F.M() in a
    57  	// module that requires a minimum of go1.21. The analysis may
    58  	// occur using a version of Go higher than 1.21, selecting the
    59  	// first version of T, so the method M is Real.M. This would
    60  	// spuriously cause the analyzer to report a reference to a
    61  	// too-new symbol even though this expression compiles just
    62  	// fine (with the fake implementation) using go1.21.
    63  	for _, sym := range symbols {
    64  		symVersion := sym.Version.String()
    65  		if !versions.Before(version, symVersion) {
    66  			continue // allowed
    67  		}
    68  
    69  		var obj types.Object
    70  		switch sym.Kind {
    71  		case stdlib.Field:
    72  			typename, name := sym.SplitField()
    73  			if t := pkg.Scope().Lookup(typename); t != nil && disallowed[t] == "" {
    74  				obj, _, _ = types.LookupFieldOrMethod(t.Type(), false, pkg, name)
    75  			}
    76  
    77  		case stdlib.Method:
    78  			ptr, recvname, name := sym.SplitMethod()
    79  			if t := pkg.Scope().Lookup(recvname); t != nil && disallowed[t] == "" {
    80  				obj, _, _ = types.LookupFieldOrMethod(t.Type(), ptr, pkg, name)
    81  			}
    82  		}
    83  		if obj != nil {
    84  			disallowed[obj] = symVersion
    85  		}
    86  	}
    87  
    88  	return disallowed
    89  }