1// Copyright 2019 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 cache 6 7import ( 8 "context" 9 "fmt" 10 11 "golang.org/x/tools/go/packages" 12 "golang.org/x/tools/internal/lsp/source" 13 "golang.org/x/tools/internal/span" 14) 15 16func (v *view) loadParseTypecheck(ctx context.Context, f *goFile) ([]packages.Error, error) { 17 v.mcache.mu.Lock() 18 defer v.mcache.mu.Unlock() 19 20 // If the AST for this file is trimmed, and we are explicitly type-checking it, 21 // don't ignore function bodies. 22 if f.astIsTrimmed() { 23 v.pcache.mu.Lock() 24 f.invalidateAST(ctx) 25 v.pcache.mu.Unlock() 26 } 27 28 // Get the metadata for the file. 29 meta, errs, err := v.checkMetadata(ctx, f) 30 if err != nil { 31 return errs, err 32 } 33 if meta == nil { 34 return nil, nil 35 } 36 for id, m := range meta { 37 imp := &importer{ 38 view: v, 39 seen: make(map[packageID]struct{}), 40 ctx: ctx, 41 fset: v.session.cache.FileSet(), 42 topLevelPkgID: id, 43 } 44 // Start prefetching direct imports. 45 for importID := range m.children { 46 go imp.getPkg(ctx, importID) 47 } 48 // Type-check package. 49 pkg, err := imp.getPkg(ctx, imp.topLevelPkgID) 50 if err != nil { 51 return nil, err 52 } 53 if pkg == nil || pkg.IsIllTyped() { 54 return nil, fmt.Errorf("loadParseTypecheck: %s is ill typed", m.pkgPath) 55 } 56 } 57 if len(f.pkgs) == 0 { 58 return nil, fmt.Errorf("no packages found for %s", f.URI()) 59 } 60 return nil, nil 61} 62 63func sameSet(x, y map[packagePath]struct{}) bool { 64 if len(x) != len(y) { 65 return false 66 } 67 for k := range x { 68 if _, ok := y[k]; !ok { 69 return false 70 } 71 } 72 return true 73} 74 75// checkMetadata determines if we should run go/packages.Load for this file. 76// If yes, update the metadata for the file and its package. 77func (v *view) checkMetadata(ctx context.Context, f *goFile) (map[packageID]*metadata, []packages.Error, error) { 78 if !v.runGopackages(ctx, f) { 79 return f.meta, nil, nil 80 } 81 82 // Check if the context has been canceled before calling packages.Load. 83 if ctx.Err() != nil { 84 return nil, nil, ctx.Err() 85 } 86 87 pkgs, err := packages.Load(v.Config(), fmt.Sprintf("file=%s", f.filename())) 88 if len(pkgs) == 0 { 89 if err == nil { 90 err = fmt.Errorf("go/packages.Load: no packages found for %s", f.filename()) 91 } 92 // Return this error as a diagnostic to the user. 93 return nil, []packages.Error{ 94 { 95 Msg: err.Error(), 96 Kind: packages.UnknownError, 97 }, 98 }, err 99 } 100 // Track missing imports as we look at the package's errors. 101 missingImports := make(map[packagePath]struct{}) 102 for _, pkg := range pkgs { 103 // If the package comes back with errors from `go list`, 104 // don't bother type-checking it. 105 if len(pkg.Errors) > 0 { 106 return nil, pkg.Errors, fmt.Errorf("package %s has errors, skipping type-checking", pkg.PkgPath) 107 } 108 // Build the import graph for this package. 109 if err := v.link(ctx, packagePath(pkg.PkgPath), pkg, nil, missingImports); err != nil { 110 return nil, nil, err 111 } 112 } 113 m, err := validateMetadata(ctx, missingImports, f) 114 if err != nil { 115 return nil, nil, err 116 } 117 return m, nil, nil 118} 119 120func validateMetadata(ctx context.Context, missingImports map[packagePath]struct{}, f *goFile) (map[packageID]*metadata, error) { 121 f.mu.Lock() 122 defer f.mu.Unlock() 123 124 // If `go list` failed to get data for the file in question (this should never happen). 125 if len(f.meta) == 0 { 126 return nil, fmt.Errorf("loadParseTypecheck: no metadata found for %v", f.filename()) 127 } 128 129 // If we have already seen these missing imports before, and we have type information, 130 // there is no need to continue. 131 if sameSet(missingImports, f.missingImports) && len(f.pkgs) != 0 { 132 return nil, nil 133 } 134 // Otherwise, update the missing imports map. 135 f.missingImports = missingImports 136 return f.meta, nil 137} 138 139// reparseImports reparses a file's package and import declarations to 140// determine if they have changed. 141func (v *view) runGopackages(ctx context.Context, f *goFile) (result bool) { 142 f.mu.Lock() 143 defer func() { 144 // Clear metadata if we are intending to re-run go/packages. 145 if result { 146 // Reset the file's metadata and type information if we are re-running `go list`. 147 for k := range f.meta { 148 delete(f.meta, k) 149 } 150 for k := range f.pkgs { 151 delete(f.pkgs, k) 152 } 153 } 154 155 f.mu.Unlock() 156 }() 157 158 if len(f.meta) == 0 || len(f.missingImports) > 0 { 159 return true 160 } 161 162 // Get file content in case we don't already have it. 163 parsed, err := v.session.cache.ParseGoHandle(f.Handle(ctx), source.ParseHeader).Parse(ctx) 164 if err == context.Canceled { 165 return false 166 } 167 if parsed == nil { 168 return true 169 } 170 171 // Check if the package's name has changed, by checking if this is a filename 172 // we already know about, and if so, check if its package name has changed. 173 for _, m := range f.meta { 174 for _, filename := range m.files { 175 if filename == f.URI().Filename() { 176 if m.name != parsed.Name.Name { 177 return true 178 } 179 } 180 } 181 } 182 183 // If the package's imports have changed, re-run `go list`. 184 if len(f.imports) != len(parsed.Imports) { 185 return true 186 } 187 188 for i, importSpec := range f.imports { 189 if importSpec.Path.Value != parsed.Imports[i].Path.Value { 190 return true 191 } 192 } 193 194 return false 195} 196 197func (v *view) link(ctx context.Context, pkgPath packagePath, pkg *packages.Package, parent *metadata, missingImports map[packagePath]struct{}) error { 198 id := packageID(pkg.ID) 199 m, ok := v.mcache.packages[id] 200 201 // If a file was added or deleted we need to invalidate the package cache 202 // so relevant packages get parsed and type-checked again. 203 if ok && !filenamesIdentical(m.files, pkg.CompiledGoFiles) { 204 v.pcache.mu.Lock() 205 v.remove(ctx, id, make(map[packageID]struct{})) 206 v.pcache.mu.Unlock() 207 } 208 209 // If we haven't seen this package before. 210 if !ok { 211 m = &metadata{ 212 pkgPath: pkgPath, 213 id: id, 214 typesSizes: pkg.TypesSizes, 215 parents: make(map[packageID]bool), 216 children: make(map[packageID]bool), 217 } 218 v.mcache.packages[id] = m 219 v.mcache.ids[pkgPath] = id 220 } 221 // Reset any field that could have changed across calls to packages.Load. 222 m.name = pkg.Name 223 m.files = pkg.CompiledGoFiles 224 for _, filename := range m.files { 225 f, err := v.getFile(ctx, span.FileURI(filename)) 226 if err != nil { 227 v.session.log.Errorf(ctx, "no file %s: %v", filename, err) 228 } 229 gof, ok := f.(*goFile) 230 if !ok { 231 v.session.log.Errorf(ctx, "not a Go file: %s", f.URI()) 232 } 233 if gof.meta == nil { 234 gof.meta = make(map[packageID]*metadata) 235 } 236 gof.meta[m.id] = m 237 } 238 // Connect the import graph. 239 if parent != nil { 240 m.parents[parent.id] = true 241 parent.children[id] = true 242 } 243 for importPath, importPkg := range pkg.Imports { 244 importPkgPath := packagePath(importPath) 245 if importPkgPath == pkgPath { 246 return fmt.Errorf("cycle detected in %s", importPath) 247 } 248 // Don't remember any imports with significant errors. 249 if importPkgPath != "unsafe" && len(pkg.CompiledGoFiles) == 0 { 250 missingImports[importPkgPath] = struct{}{} 251 continue 252 } 253 if _, ok := m.children[packageID(importPkg.ID)]; !ok { 254 if err := v.link(ctx, importPkgPath, importPkg, m, missingImports); err != nil { 255 v.session.log.Errorf(ctx, "error in dependency %s: %v", importPkgPath, err) 256 } 257 } 258 } 259 // Clear out any imports that have been removed. 260 for importID := range m.children { 261 child, ok := v.mcache.packages[importID] 262 if !ok { 263 continue 264 } 265 importPath := string(child.pkgPath) 266 if _, ok := pkg.Imports[importPath]; ok { 267 continue 268 } 269 delete(m.children, importID) 270 delete(child.parents, id) 271 } 272 return nil 273} 274 275// filenamesIdentical reports whether two sets of file names are identical. 276func filenamesIdentical(oldFiles, newFiles []string) bool { 277 if len(oldFiles) != len(newFiles) { 278 return false 279 } 280 oldByName := make(map[string]struct{}, len(oldFiles)) 281 for _, filename := range oldFiles { 282 oldByName[filename] = struct{}{} 283 } 284 for _, newFilename := range newFiles { 285 if _, found := oldByName[newFilename]; !found { 286 return false 287 } 288 delete(oldByName, newFilename) 289 } 290 return len(oldByName) == 0 291} 292