internal/lsp: sort by label after score

I want to stop sorting unimported completions. We still want to show
users something reasonable, so use label as a tiebreaker for score in
the higher level completion function.

To maintain the current sorting, we need to adjust scores by search
depth (height?) for lexical completions. A few tests are really ties,
and need sorting in the test case.

Change-Id: Ie2d09fdcbebf6fda4ab33a2f16c579d12b0f26ad
Reviewed-on: https://go-review.googlesource.com/c/tools/+/212633
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/completion.go b/internal/lsp/completion.go
index cdad7e4..90bcba9 100644
--- a/internal/lsp/completion.go
+++ b/internal/lsp/completion.go
@@ -52,9 +52,12 @@
 	if err != nil {
 		return nil, err
 	}
-	// Sort the candidates by score, since that is not supported by LSP yet.
+	// Sort the candidates by score, then label, since that is not supported by LSP yet.
 	sort.SliceStable(candidates, func(i, j int) bool {
-		return candidates[i].Score > candidates[j].Score
+		if candidates[i].Score != candidates[j].Score {
+			return candidates[i].Score > candidates[j].Score
+		}
+		return candidates[i].Label < candidates[j].Label
 	})
 
 	// When using deep completions/fuzzy matching, report results as incomplete so
diff --git a/internal/lsp/source/completion.go b/internal/lsp/source/completion.go
index 930eb09..767181d 100644
--- a/internal/lsp/source/completion.go
+++ b/internal/lsp/source/completion.go
@@ -11,6 +11,7 @@
 	"go/constant"
 	"go/token"
 	"go/types"
+	"math"
 	"strconv"
 	"strings"
 	"time"
@@ -696,7 +697,7 @@
 	for _, f := range fieldSelections(typ) {
 		c.found(candidate{
 			obj:         f,
-			score:       stdScore,
+			score:       stdScore - 0.01,
 			imp:         imp,
 			addressable: addressable || isPointer(typ),
 		})
@@ -778,7 +779,8 @@
 				continue
 			}
 
-			score := stdScore
+			// Rank outer scopes lower than inner.
+			score := stdScore * math.Pow(.99, float64(i))
 
 			// Dowrank "nil" a bit so it is ranked below more interesting candidates.
 			if obj == builtinNil {
diff --git a/internal/lsp/source/completion_keywords.go b/internal/lsp/source/completion_keywords.go
index 526de65..dcb5c95 100644
--- a/internal/lsp/source/completion_keywords.go
+++ b/internal/lsp/source/completion_keywords.go
@@ -38,6 +38,7 @@
 
 // keyword looks at the current scope of an *ast.Ident and recommends keywords
 func (c *completer) keyword() error {
+	keywordScore := float64(0.9)
 	if _, ok := c.path[0].(*ast.Ident); !ok {
 		// TODO(golang/go#34009): Support keyword completion in any context
 		return errors.Errorf("keywords are currently only recommended for identifiers")
@@ -61,7 +62,7 @@
 		case *ast.CaseClause:
 			// only recommend "fallthrough" and "break" within the bodies of a case clause
 			if c.pos > node.Colon {
-				valid[BREAK] = stdScore
+				valid[BREAK] = keywordScore
 				// "fallthrough" is only valid in switch statements.
 				// A case clause is always nested within a block statement in a switch statement,
 				// that block statement is nested within either a TypeSwitchStmt or a SwitchStmt.
@@ -69,32 +70,32 @@
 					continue
 				}
 				if _, ok := path[i+2].(*ast.SwitchStmt); ok {
-					valid[FALLTHROUGH] = stdScore
+					valid[FALLTHROUGH] = keywordScore
 				}
 			}
 		case *ast.CommClause:
 			if c.pos > node.Colon {
-				valid[BREAK] = stdScore
+				valid[BREAK] = keywordScore
 			}
 		case *ast.TypeSwitchStmt, *ast.SelectStmt, *ast.SwitchStmt:
-			valid[CASE] = stdScore + lowScore
-			valid[DEFAULT] = stdScore + lowScore
+			valid[CASE] = keywordScore + lowScore
+			valid[DEFAULT] = keywordScore + lowScore
 		case *ast.ForStmt:
-			valid[BREAK] = stdScore
-			valid[CONTINUE] = stdScore
+			valid[BREAK] = keywordScore
+			valid[CONTINUE] = keywordScore
 		// This is a bit weak, functions allow for many keywords
 		case *ast.FuncDecl:
 			if node.Body != nil && c.pos > node.Body.Lbrace {
-				valid[DEFER] = stdScore - lowScore
-				valid[RETURN] = stdScore - lowScore
-				valid[FOR] = stdScore - lowScore
-				valid[GO] = stdScore - lowScore
-				valid[SWITCH] = stdScore - lowScore
-				valid[SELECT] = stdScore - lowScore
-				valid[IF] = stdScore - lowScore
-				valid[ELSE] = stdScore - lowScore
-				valid[VAR] = stdScore - lowScore
-				valid[CONST] = stdScore - lowScore
+				valid[DEFER] = keywordScore - lowScore
+				valid[RETURN] = keywordScore - lowScore
+				valid[FOR] = keywordScore - lowScore
+				valid[GO] = keywordScore - lowScore
+				valid[SWITCH] = keywordScore - lowScore
+				valid[SELECT] = keywordScore - lowScore
+				valid[IF] = keywordScore - lowScore
+				valid[ELSE] = keywordScore - lowScore
+				valid[VAR] = keywordScore - lowScore
+				valid[CONST] = keywordScore - lowScore
 			}
 		}
 	}
diff --git a/internal/lsp/source/completion_labels.go b/internal/lsp/source/completion_labels.go
index ebef101..0a23aa8 100644
--- a/internal/lsp/source/completion_labels.go
+++ b/internal/lsp/source/completion_labels.go
@@ -7,6 +7,7 @@
 import (
 	"go/ast"
 	"go/token"
+	"math"
 )
 
 type labelType int
@@ -53,10 +54,10 @@
 		return
 	}
 
-	addLabel := func(l *ast.LabeledStmt) {
+	addLabel := func(score float64, l *ast.LabeledStmt) {
 		labelObj := c.pkg.GetTypesInfo().ObjectOf(l.Label)
 		if labelObj != nil {
-			c.found(candidate{obj: labelObj, score: highScore})
+			c.found(candidate{obj: labelObj, score: score})
 		}
 	}
 
@@ -64,7 +65,7 @@
 	case labelBreak, labelContinue:
 		// "break" and "continue" only accept labels from enclosing statements.
 
-		for _, p := range c.path {
+		for i, p := range c.path {
 			switch p := p.(type) {
 			case *ast.FuncLit:
 				// Labels are function scoped, so don't continue out of functions.
@@ -73,11 +74,11 @@
 				switch p.Stmt.(type) {
 				case *ast.ForStmt, *ast.RangeStmt:
 					// Loop labels can be used for "break" or "continue".
-					addLabel(p)
+					addLabel(highScore*math.Pow(.99, float64(i)), p)
 				case *ast.SwitchStmt, *ast.SelectStmt, *ast.TypeSwitchStmt:
 					// Switch and select labels can be used only for "break".
 					if lt == labelBreak {
-						addLabel(p)
+						addLabel(highScore*math.Pow(.99, float64(i)), p)
 					}
 				}
 			}
@@ -102,7 +103,7 @@
 				}
 				return false
 			case *ast.LabeledStmt:
-				addLabel(n)
+				addLabel(highScore, n)
 			}
 
 			return true
diff --git a/internal/lsp/source/source_test.go b/internal/lsp/source/source_test.go
index c53a787..c77cab4 100644
--- a/internal/lsp/source/source_test.go
+++ b/internal/lsp/source/source_test.go
@@ -251,7 +251,10 @@
 	// TODO(rstambler): In testing this out, I noticed that scores are equal,
 	// even when they shouldn't be. This needs more investigation.
 	sort.SliceStable(list, func(i, j int) bool {
-		return list[i].Score > list[j].Score
+		if list[i].Score != list[j].Score {
+			return list[i].Score > list[j].Score
+		}
+		return list[i].Label < list[j].Label
 	})
 	var numDeepCompletionsSeen int
 	var items []source.CompletionItem
diff --git a/internal/lsp/testdata/deep/deep.go b/internal/lsp/testdata/deep/deep.go
index a7c659a..c49f001 100644
--- a/internal/lsp/testdata/deep/deep.go
+++ b/internal/lsp/testdata/deep/deep.go
@@ -113,9 +113,9 @@
 		f foo
 	)
 
-	f.bar().valueReceiver    //@item(deepBarValue, "f.bar().valueReceiver", "func() int", "method")
-	f.barPtr().valueReceiver //@item(deepBarPtrValue, "f.barPtr().valueReceiver", "func() int", "method")
-	f.barPtr().ptrReceiver   //@item(deepBarPtrPtr, "f.barPtr().ptrReceiver", "func() int", "method")
+	f.b.ptrReceiver()      //@item(deepBPtr, "f.b.ptrReceiver", "func() int", "method")
+	f.bar().valueReceiver  //@item(deepBarValue, "f.bar().valueReceiver", "func() int", "method")
+	f.barPtr().ptrReceiver //@item(deepBarPtrPtr, "f.barPtr().ptrReceiver", "func() int", "method")
 
-	i = fb //@fuzzy(" //", deepBarValue, deepBarPtrPtr, deepBarPtrValue)
+	i = fb //@fuzzy(" //", deepBPtr, deepBarValue, deepBarPtrPtr)
 }
diff --git a/internal/lsp/testdata/selector/selector.go.in b/internal/lsp/testdata/selector/selector.go.in
index 77df003..6c6e1fd 100644
--- a/internal/lsp/testdata/selector/selector.go.in
+++ b/internal/lsp/testdata/selector/selector.go.in
@@ -11,7 +11,7 @@
 }
 
 func _() {
-	_ = S{}.; //@complete(";", Bf, Af, Cf)
+	_ = S{}.; //@complete(";", Af, Bf, Cf)
 }
 
 type bob struct { a int } //@item(a, "a", "int", "field")