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
5package typesinternal
6
7import (
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.
19func 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}
90