internal/lsp/completion: fix untyped ints to match floats
In cases like:
foo<> == 100
We weren't preferring floats at <>. Fix the basic type comparison
logic to know that an untyped int is always compatible with a float.
Fixes golang/go#44066.
Change-Id: I9cf9bac1632178db100c0a5447351be208b4a2af
Reviewed-on: https://go-review.googlesource.com/c/tools/+/289129
Run-TryBot: Muir Manders <muir@mnd.rs>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Trust: Rebecca Stambler <rstambler@golang.org>
Trust: Hyang-Ah Hana Kim <hyangah@gmail.com>
diff --git a/internal/lsp/source/completion/completion.go b/internal/lsp/source/completion/completion.go
index 51f85d1..cde7b38 100644
--- a/internal/lsp/source/completion/completion.go
+++ b/internal/lsp/source/completion/completion.go
@@ -2509,7 +2509,7 @@
continue
}
- matches, untyped := ci.typeMatches(expType, candType)
+ matches := ci.typeMatches(expType, candType)
if !matches {
// If candType doesn't otherwise match, consider if we can
// convert candType directly to expType.
@@ -2531,7 +2531,7 @@
// Lower candidate score for untyped conversions. This avoids
// ranking untyped constants above candidates with an exact type
// match. Don't lower score of builtin constants, e.g. "true".
- if untyped && !types.Identical(candType, expType) && cand.obj.Parent() != types.Universe {
+ if isUntyped(candType) && !types.Identical(candType, expType) && cand.obj.Parent() != types.Universe {
// Bigger penalty for deep completions into other packages to
// avoid random constants from other packages popping up all
// the time.
@@ -2598,21 +2598,34 @@
}
// typeMatches reports whether an object of candType makes a good
-// completion candidate given the expected type expType. It also
-// returns a second bool which is true if both types are basic types
-// of the same kind, and at least one is untyped.
-func (ci *candidateInference) typeMatches(expType, candType types.Type) (bool, bool) {
+// completion candidate given the expected type expType.
+func (ci *candidateInference) typeMatches(expType, candType types.Type) bool {
// Handle untyped values specially since AssignableTo gives false negatives
// for them (see https://golang.org/issue/32146).
if candBasic, ok := candType.Underlying().(*types.Basic); ok {
- if wantBasic, ok := expType.Underlying().(*types.Basic); ok {
- // Make sure at least one of them is untyped.
- if isUntyped(candType) || isUntyped(expType) {
- // Check that their constant kind (bool|int|float|complex|string) matches.
+ if expBasic, ok := expType.Underlying().(*types.Basic); ok {
+ // Note that the candidate and/or the expected can be untyped.
+ // In "fo<> == 100" the expected type is untyped, and the
+ // candidate could also be an untyped constant.
+
+ // Sort by is_untyped and then by is_int to simplify below logic.
+ a, b := candBasic.Info(), expBasic.Info()
+ if a&types.IsUntyped == 0 || (b&types.IsInteger > 0 && b&types.IsUntyped > 0) {
+ a, b = b, a
+ }
+
+ // If at least one is untyped...
+ if a&types.IsUntyped > 0 {
+ switch {
+ // Untyped integers are compatible with floats.
+ case a&types.IsInteger > 0 && b&types.IsFloat > 0:
+ return true
+
+ // Check if their constant kind (bool|int|float|complex|string) matches.
// This doesn't take into account the constant value, so there will be some
// false positives due to integer sign and overflow.
- if candBasic.Info()&types.IsConstType == wantBasic.Info()&types.IsConstType {
- return true, true
+ case a&types.IsConstType == b&types.IsConstType:
+ return true
}
}
}
@@ -2620,7 +2633,7 @@
// AssignableTo covers the case where the types are equal, but also handles
// cases like assigning a concrete type to an interface type.
- return types.AssignableTo(candType, expType), false
+ return types.AssignableTo(candType, expType)
}
// kindMatches reports whether candType's kind matches our expected
@@ -2681,7 +2694,7 @@
continue
}
- allMatch, _ = ci.typeMatches(assignee, sig.Results().At(i).Type())
+ allMatch = ci.typeMatches(assignee, sig.Results().At(i).Type())
if !allMatch {
break
}
diff --git a/internal/lsp/testdata/rank/convert_rank.go.in b/internal/lsp/testdata/rank/convert_rank.go.in
index 475bebd..372d9c3 100644
--- a/internal/lsp/testdata/rank/convert_rank.go.in
+++ b/internal/lsp/testdata/rank/convert_rank.go.in
@@ -49,4 +49,7 @@
var convP myInt
&convP //@item(convertP, "&convP", "myInt", "var")
var _ *int = conv //@snippet(" //", convertP, "(*int)(&convP)", "(*int)(&convP)")
+
+ var ff float64 //@item(convertFloat, "ff", "float64", "var")
+ f == convD //@snippet(" =", convertFloat, "ff", "ff")
}
diff --git a/internal/lsp/testdata/summary.txt.golden b/internal/lsp/testdata/summary.txt.golden
index 37bb48a..18f2b1b 100644
--- a/internal/lsp/testdata/summary.txt.golden
+++ b/internal/lsp/testdata/summary.txt.golden
@@ -2,7 +2,7 @@
CallHierarchyCount = 2
CodeLensCount = 5
CompletionsCount = 258
-CompletionSnippetCount = 88
+CompletionSnippetCount = 89
UnimportedCompletionsCount = 5
DeepCompletionsCount = 5
FuzzyCompletionsCount = 8