internal/lsp/cache: trim ellipsis array literals
While looking at Kubernetes I noticed that golang.org/x/text packages
were some of the largest. The problem is the large code-generated
tables, which use ellipsis array literals. Teach gopls to trim the cases
that matter there.
While silly, this trims ~60MB off the live heap, so I think it might be
worth it.
Change-Id: I0cfd80bd5fbc8703ac628312982af9c6ed871758
Reviewed-on: https://go-review.googlesource.com/c/tools/+/248180
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/internal/lsp/cache/parse.go b/internal/lsp/cache/parse.go
index 1c4c64a..95e9de5 100644
--- a/internal/lsp/cache/parse.go
+++ b/internal/lsp/cache/parse.go
@@ -13,6 +13,7 @@
"go/scanner"
"go/token"
"reflect"
+ "strconv"
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/lsp/debug/tag"
@@ -343,24 +344,78 @@
case *ast.CommClause:
n.Body = nil
case *ast.CompositeLit:
- // Leave elts in place for [...]T
- // array literals, because they can
- // affect the expression's type.
- if !isEllipsisArray(n.Type) {
- n.Elts = nil
+ // types.Info.Types for long slice/array literals are particularly
+ // expensive. Try to clear them out.
+ at, ok := n.Type.(*ast.ArrayType)
+ if !ok {
+ break
}
+ // Removing the elements from an ellipsis array changes its type.
+ // Try to set the length explicitly so we can continue.
+ if _, ok := at.Len.(*ast.Ellipsis); ok {
+ length, ok := arrayLength(n)
+ if !ok {
+ break
+ }
+ at.Len = &ast.BasicLit{
+ Kind: token.INT,
+ Value: fmt.Sprint(length),
+ ValuePos: at.Len.Pos(),
+ }
+ }
+ n.Elts = nil
}
return true
})
}
-func isEllipsisArray(n ast.Expr) bool {
- at, ok := n.(*ast.ArrayType)
- if !ok {
- return false
+// arrayLength returns the length of some simple forms of ellipsis array literal.
+// Notably, it handles the tables in golang.org/x/text.
+func arrayLength(array *ast.CompositeLit) (int, bool) {
+ litVal := func(expr ast.Expr) (int, bool) {
+ lit, ok := expr.(*ast.BasicLit)
+ if !ok {
+ return 0, false
+ }
+ val, err := strconv.ParseInt(lit.Value, 10, 64)
+ if err != nil {
+ return 0, false
+ }
+ return int(val), true
}
- _, ok = at.Len.(*ast.Ellipsis)
- return ok
+ largestKey := -1
+ for _, elt := range array.Elts {
+ kve, ok := elt.(*ast.KeyValueExpr)
+ if !ok {
+ continue
+ }
+ switch key := kve.Key.(type) {
+ case *ast.BasicLit:
+ if val, ok := litVal(key); ok && largestKey < val {
+ largestKey = val
+ }
+ case *ast.BinaryExpr:
+ // golang.org/x/text uses subtraction (and only subtraction) in its indices.
+ if key.Op != token.SUB {
+ break
+ }
+ x, ok := litVal(key.X)
+ if !ok {
+ break
+ }
+ y, ok := litVal(key.Y)
+ if !ok {
+ break
+ }
+ if val := x - y; largestKey < val {
+ largestKey = val
+ }
+ }
+ }
+ if largestKey != -1 {
+ return largestKey + 1, true
+ }
+ return len(array.Elts), true
}
// fixAST inspects the AST and potentially modifies any *ast.BadStmts so that it can be
diff --git a/internal/lsp/cache/parse_test.go b/internal/lsp/cache/parse_test.go
new file mode 100644
index 0000000..ff1b83f
--- /dev/null
+++ b/internal/lsp/cache/parse_test.go
@@ -0,0 +1,37 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package cache
+
+import (
+ "go/ast"
+ "go/parser"
+ "testing"
+)
+
+func TestArrayLength(t *testing.T) {
+ tests := []struct {
+ expr string
+ length int
+ }{
+ {`[...]int{0,1,2,3,4,5,6,7,8,9}`, 10},
+ {`[...]int{9:0}`, 10},
+ {`[...]int{19-10:0}`, 10},
+ {`[...]int{19-10:0, 17-10:0, 18-10:0}`, 10},
+ }
+
+ for _, tt := range tests {
+ expr, err := parser.ParseExpr(tt.expr)
+ if err != nil {
+ t.Fatal(err)
+ }
+ l, ok := arrayLength(expr.(*ast.CompositeLit))
+ if !ok {
+ t.Errorf("arrayLength did not recognize expression %#v", expr)
+ }
+ if l != tt.length {
+ t.Errorf("arrayLength(%#v) = %v, want %v", expr, l, tt.length)
+ }
+ }
+}