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 <heschi@google.com>
Run-TryBot: Heschi Kreinick <heschi@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
Heschi Kreinick 2021-04-08 17:58:25 -04:00
parent 1a0c6081c5
commit 07295caad0
1 changed files with 32 additions and 1 deletions

View File

@ -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
})
}
}