diff --git a/internal/lsp/source/completion.go b/internal/lsp/source/completion.go index e060c2b1..805c441a 100644 --- a/internal/lsp/source/completion.go +++ b/internal/lsp/source/completion.go @@ -1028,14 +1028,28 @@ func (c *completer) matchingType(cand *candidate) bool { // are invoked by default. cand.expandFuncCall = isFunc(cand.obj) - typeMatches := func(actual types.Type) bool { + typeMatches := func(candType types.Type) bool { // Take into account any type modifiers on the expected type. - actual = c.expectedType.applyTypeModifiers(actual) + candType = c.expectedType.applyTypeModifiers(candType) if c.expectedType.objType != nil { + wantType := types.Default(c.expectedType.objType) + + // Handle untyped values specially since AssignableTo gives false negatives + // for them (see https://golang.org/issue/32146). + if candBasic, ok := candType.(*types.Basic); ok && candBasic.Info()&types.IsUntyped > 0 { + if wantBasic, ok := wantType.Underlying().(*types.Basic); ok { + // Check that 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. + return candBasic.Info()&types.IsConstType == wantBasic.Info()&types.IsConstType + } + return false + } + // 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(types.Default(actual), types.Default(c.expectedType.objType)) + return types.AssignableTo(candType, wantType) } return false diff --git a/internal/lsp/testdata/rank/convert_rank.go.in b/internal/lsp/testdata/rank/convert_rank.go.in index dc0a3a5e..fe6b7831 100644 --- a/internal/lsp/testdata/rank/convert_rank.go.in +++ b/internal/lsp/testdata/rank/convert_rank.go.in @@ -10,3 +10,30 @@ func _() { ) wantsStrList(strList(conv)) //@complete("))", convertB, convertA) } + +func _() { + const ( + convC = "hi" //@item(convertC, "convC", "string", "const") + convD = 123 //@item(convertD, "convD", "int", "const") + convE int = 123 //@item(convertE, "convE", "int", "const") + convF string = "there" //@item(convertF, "convF", "string", "const") + ) + + var foo int + foo = conv //@complete(" //", convertD, convertE, convertC, convertF) + + type myInt int + var mi myInt + mi = conv //@complete(" //", convertD, convertC, convertE, convertF) + mi + conv //@complete(" //", convertD, convertC, convertE, convertF) + + 1 + conv //@complete(" //", convertD, convertE, convertC, convertF) + + type myString string + var ms myString + ms = conv //@complete(" //", convertC, convertD, convertE, convertF) + + type myUint uint32 + var mu myUint + mu = conv //@complete(" //", convertD, convertC, convertE, convertF) +} diff --git a/internal/lsp/tests/tests.go b/internal/lsp/tests/tests.go index 6b6c39f2..65a29f39 100644 --- a/internal/lsp/tests/tests.go +++ b/internal/lsp/tests/tests.go @@ -25,7 +25,7 @@ import ( // We hardcode the expected number of test cases to ensure that all tests // are being executed. If a test is added, this number must be changed. const ( - ExpectedCompletionsCount = 138 + ExpectedCompletionsCount = 144 ExpectedCompletionSnippetCount = 15 ExpectedDiagnosticsCount = 17 ExpectedFormatCount = 5