From 07295caad09cddc39fc3b5631934f4780c2014de Mon Sep 17 00:00:00 2001 From: Heschi Kreinick Date: Thu, 8 Apr 2021 17:58:25 -0400 Subject: [PATCH] internal/lsp/cache: prune types.Info entries in slice literals The type checker emits a TypeAndValue entry for (among other things) every constant in a Go file. Normally, that cost is moderate. However, in the case of a large slice literal, it can get out of control very quickly. Imagine a code generator that creates a 2KB byte slice literal; that's 2K TypeAndValue entries, each of which is considerably larger than the 1-3 bytes for the source text. Unfortunately, there are a number of such code generators. Notably, there are such slice literals in proto code, e.g. https://github.com/grpc/grpc-go/blob/master/examples/route_guide/routeguide/route_guide.pb.go#L360 This CL changes the type checking code to remove the TypeAndValue entries for slice literals of basic types after the checker returns. In the extreme case of https://github.com/googleapis/go-genproto, which is nothing but protos, I see a ~40% drop in heap usage. I believe this change is generally safe, but there's no way to guarantee it. I don't think any editor features need to know the type or value of an arbitrary slice element, but it is just barely possible that an analyzer does. Change-Id: Iee1af2369f994597a42fd1dcbf8af20faa43410e Reviewed-on: https://go-review.googlesource.com/c/tools/+/308730 Trust: Heschi Kreinick Run-TryBot: Heschi Kreinick gopls-CI: kokoro TryBot-Result: Go Bot Reviewed-by: Robert Findley --- internal/lsp/cache/check.go | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/internal/lsp/cache/check.go b/internal/lsp/cache/check.go index 65c33717ee..0d37440f53 100644 --- a/internal/lsp/cache/check.go +++ b/internal/lsp/cache/check.go @@ -446,7 +446,6 @@ func typeCheck(ctx context.Context, snapshot *snapshot, m *metadata, mode source typesinternal.SetUsesCgo(cfg) check := types.NewChecker(cfg, fset, pkg.types, pkg.typesInfo) - // Type checking errors are handled via the config, so ignore them here. _ = check.Files(files) // If the context was cancelled, we may have returned a ton of transient @@ -455,6 +454,8 @@ func typeCheck(ctx context.Context, snapshot *snapshot, m *metadata, mode source return nil, ctx.Err() } + trimTypesInfo(files, pkg.typesInfo) + // We don't care about a package's errors unless we have parsed it in full. if mode != source.ParseFull { return pkg, nil @@ -795,3 +796,33 @@ func isValidImport(pkgPath, importPkgPath packagePath) bool { type importerFunc func(path string) (*types.Package, error) func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) } + +func trimTypesInfo(files []*ast.File, info *types.Info) { + for _, file := range files { + ast.Inspect(file, func(n ast.Node) bool { + if n == nil { + return false + } + cl, ok := n.(*ast.CompositeLit) + if !ok { + return true + } + + // types.Info.Types for long slice/array literals are particularly + // expensive. Try to clear them out. + if _, ok := cl.Type.(*ast.ArrayType); !ok { + return true + } + + for _, elt := range cl.Elts { + // Only delete information for basic literals; other elements, + // like constants and function calls, could still be relevant. + if _, ok := elt.(*ast.BasicLit); ok { + delete(info.Types, elt) + } + } + + return true + }) + } +}