golang.org/x/tools/gopls@v0.15.3/internal/cache/metadata/metadata.go (about) 1 // Copyright 2023 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 // The metadata package defines types and functions for working with package 6 // metadata, which describes Go packages and their relationships. 7 // 8 // Package metadata is loaded by gopls using go/packages, and the [Package] 9 // type is itself a projection and translation of data from 10 // go/packages.Package. 11 // 12 // Packages are assembled into an immutable [Graph] 13 package metadata 14 15 import ( 16 "go/ast" 17 "go/types" 18 "sort" 19 "strconv" 20 "strings" 21 22 "golang.org/x/tools/go/packages" 23 "golang.org/x/tools/gopls/internal/protocol" 24 "golang.org/x/tools/internal/packagesinternal" 25 ) 26 27 // Declare explicit types for package paths, names, and IDs to ensure that we 28 // never use an ID where a path belongs, and vice versa. If we confused these, 29 // it would result in confusing errors because package IDs often look like 30 // package paths. 31 type ( 32 PackageID string // go list's unique identifier for a package (e.g. "vendor/example.com/foo [vendor/example.com/bar.test]") 33 PackagePath string // name used to prefix linker symbols (e.g. "vendor/example.com/foo") 34 PackageName string // identifier in 'package' declaration (e.g. "foo") 35 ImportPath string // path that appears in an import declaration (e.g. "example.com/foo") 36 ) 37 38 // Package represents package metadata retrieved from go/packages. 39 // The DepsBy{Imp,Pkg}Path maps do not contain self-import edges. 40 // 41 // An ad-hoc package (without go.mod or GOPATH) has its ID, PkgPath, 42 // and LoadDir equal to the absolute path of its directory. 43 type Package struct { 44 ID PackageID 45 PkgPath PackagePath 46 Name PackageName 47 48 // these three fields are as defined by go/packages.Package 49 GoFiles []protocol.DocumentURI 50 CompiledGoFiles []protocol.DocumentURI 51 IgnoredFiles []protocol.DocumentURI 52 53 ForTest PackagePath // q in a "p [q.test]" package, else "" 54 TypesSizes types.Sizes 55 Errors []packages.Error // must be set for packages in import cycles 56 DepsByImpPath map[ImportPath]PackageID // may contain dups; empty ID => missing 57 DepsByPkgPath map[PackagePath]PackageID // values are unique and non-empty 58 Module *packages.Module 59 DepsErrors []*packagesinternal.PackageError 60 LoadDir string // directory from which go/packages was run 61 Standalone bool // package synthesized for a standalone file (e.g. ignore-tagged) 62 } 63 64 func (mp *Package) String() string { return string(mp.ID) } 65 66 // IsIntermediateTestVariant reports whether the given package is an 67 // intermediate test variant (ITV), e.g. "net/http [net/url.test]". 68 // 69 // An ITV has identical syntax to the regular variant, but different 70 // import metadata (DepsBy{Imp,Pkg}Path). 71 // 72 // Such test variants arise when an x_test package (in this case net/url_test) 73 // imports a package (in this case net/http) that itself imports the 74 // non-x_test package (in this case net/url). 75 // 76 // This is done so that the forward transitive closure of net/url_test has 77 // only one package for the "net/url" import. 78 // The ITV exists to hold the test variant import: 79 // 80 // net/url_test [net/url.test] 81 // 82 // | "net/http" -> net/http [net/url.test] 83 // | "net/url" -> net/url [net/url.test] 84 // | ... 85 // 86 // net/http [net/url.test] 87 // 88 // | "net/url" -> net/url [net/url.test] 89 // | ... 90 // 91 // This restriction propagates throughout the import graph of net/http: for 92 // every package imported by net/http that imports net/url, there must be an 93 // intermediate test variant that instead imports "net/url [net/url.test]". 94 // 95 // As one can see from the example of net/url and net/http, intermediate test 96 // variants can result in many additional packages that are essentially (but 97 // not quite) identical. For this reason, we filter these variants wherever 98 // possible. 99 // 100 // # Why we mostly ignore intermediate test variants 101 // 102 // In projects with complicated tests, there may be a very large 103 // number of ITVs--asymptotically more than the number of ordinary 104 // variants. Since they have identical syntax, it is fine in most 105 // cases to ignore them since the results of analyzing the ordinary 106 // variant suffice. However, this is not entirely sound. 107 // 108 // Consider this package: 109 // 110 // // p/p.go -- in all variants of p 111 // package p 112 // type T struct { io.Closer } 113 // 114 // // p/p_test.go -- in test variant of p 115 // package p 116 // func (T) Close() error { ... } 117 // 118 // The ordinary variant "p" defines T with a Close method promoted 119 // from io.Closer. But its test variant "p [p.test]" defines a type T 120 // with a Close method from p_test.go. 121 // 122 // Now consider a package q that imports p, perhaps indirectly. Within 123 // it, T.Close will resolve to the first Close method: 124 // 125 // // q/q.go -- in all variants of q 126 // package q 127 // import "p" 128 // var _ = new(p.T).Close 129 // 130 // Let's assume p also contains this file defining an external test (xtest): 131 // 132 // // p/p_x_test.go -- external test of p 133 // package p_test 134 // import ( "q"; "testing" ) 135 // func Test(t *testing.T) { ... } 136 // 137 // Note that q imports p, but p's xtest imports q. Now, in "q 138 // [p.test]", the intermediate test variant of q built for p's 139 // external test, T.Close resolves not to the io.Closer.Close 140 // interface method, but to the concrete method of T.Close 141 // declared in p_test.go. 142 // 143 // If we now request all references to the T.Close declaration in 144 // p_test.go, the result should include the reference from q's ITV. 145 // (It's not just methods that can be affected; fields can too, though 146 // it requires bizarre code to achieve.) 147 // 148 // As a matter of policy, gopls mostly ignores this subtlety, 149 // because to account for it would require that we type-check every 150 // intermediate test variant of p, of which there could be many. 151 // Good code doesn't rely on such trickery. 152 // 153 // Most callers of MetadataForFile call RemoveIntermediateTestVariants 154 // to discard them before requesting type checking, or the products of 155 // type-checking such as the cross-reference index or method set index. 156 // 157 // MetadataForFile doesn't do this filtering itself becaused in some 158 // cases we need to make a reverse dependency query on the metadata 159 // graph, and it's important to include the rdeps of ITVs in that 160 // query. But the filtering of ITVs should be applied after that step, 161 // before type checking. 162 // 163 // In general, we should never type check an ITV. 164 func (mp *Package) IsIntermediateTestVariant() bool { 165 return mp.ForTest != "" && mp.ForTest != mp.PkgPath && mp.ForTest+"_test" != mp.PkgPath 166 } 167 168 // A Source maps package IDs to metadata for the packages. 169 // 170 // TODO(rfindley): replace this with a concrete metadata graph, once it is 171 // exposed from the snapshot. 172 type Source interface { 173 // Metadata returns the [Package] for the given package ID, or nil if it does 174 // not exist. 175 // TODO(rfindley): consider returning (*Metadata, bool) 176 // TODO(rfindley): consider renaming this method. 177 Metadata(PackageID) *Package 178 } 179 180 // TODO(rfindley): move the utility functions below to a util.go file. 181 182 // IsCommandLineArguments reports whether a given value denotes 183 // "command-line-arguments" package, which is a package with an unknown ID 184 // created by the go command. It can have a test variant, which is why callers 185 // should not check that a value equals "command-line-arguments" directly. 186 func IsCommandLineArguments(id PackageID) bool { 187 return strings.Contains(string(id), "command-line-arguments") 188 } 189 190 // SortPostOrder sorts the IDs so that if x depends on y, then y appears before x. 191 func SortPostOrder(meta Source, ids []PackageID) { 192 postorder := make(map[PackageID]int) 193 order := 0 194 var visit func(PackageID) 195 visit = func(id PackageID) { 196 if _, ok := postorder[id]; !ok { 197 postorder[id] = -1 // break recursion 198 if mp := meta.Metadata(id); mp != nil { 199 for _, depID := range mp.DepsByPkgPath { 200 visit(depID) 201 } 202 } 203 order++ 204 postorder[id] = order 205 } 206 } 207 for _, id := range ids { 208 visit(id) 209 } 210 sort.Slice(ids, func(i, j int) bool { 211 return postorder[ids[i]] < postorder[ids[j]] 212 }) 213 } 214 215 // UnquoteImportPath returns the unquoted import path of s, 216 // or "" if the path is not properly quoted. 217 func UnquoteImportPath(spec *ast.ImportSpec) ImportPath { 218 path, err := strconv.Unquote(spec.Path.Value) 219 if err != nil { 220 return "" 221 } 222 return ImportPath(path) 223 } 224 225 // RemoveIntermediateTestVariants removes intermediate test variants, modifying 226 // the array. We use a pointer to a slice make it impossible to forget to use 227 // the result. 228 func RemoveIntermediateTestVariants(pmetas *[]*Package) { 229 metas := *pmetas 230 res := metas[:0] 231 for _, mp := range metas { 232 if !mp.IsIntermediateTestVariant() { 233 res = append(res, mp) 234 } 235 } 236 *pmetas = res 237 } 238 239 // IsValidImport returns whether importPkgPath is importable 240 // by pkgPath. 241 func IsValidImport(pkgPath, importPkgPath PackagePath) bool { 242 i := strings.LastIndex(string(importPkgPath), "/internal/") 243 if i == -1 { 244 return true 245 } 246 // TODO(rfindley): this looks wrong: IsCommandLineArguments is meant to 247 // operate on package IDs, not package paths. 248 if IsCommandLineArguments(PackageID(pkgPath)) { 249 return true 250 } 251 // TODO(rfindley): this is wrong. mod.testx/p should not be able to 252 // import mod.test/internal: https://go.dev/play/p/-Ca6P-E4V4q 253 return strings.HasPrefix(string(pkgPath), string(importPkgPath[:i])) 254 }