1
0
mirror of https://github.com/golang/go synced 2024-11-18 16:14:46 -07:00
go/internal/lsp/source/completion_printf.go
Muir Manders 9ac8e33b36 internal/lsp/source: improve completion of printf operands
We now rank printf operand candidates according to the corresponding
formatting verb. We follow what fmt allows for the most part, but I
omitted some things because they are uncommon and would result in many
false positives, or didn't seem worth it to support:

- We don't prefer fmt.Stringer or error types for "%x" or "%X".
- We don't prefer pointers for any verbs except "%p".
- We don't prefer recursive application of verbs (e.g. we won't prefer
  []string for "%s").

I decided against sharing code with the printf analyzer. It was
tangled somewhat with go/analysis, and I needed only a very small
subset of the format parsing.

I tweaked candidate type evaluation to accommodate the printf hints.
We now skip expected type of "interface{}" when matching candidate
types because it matches everything and would always supersede the
coarser object kind checks.

Fixes golang/go#40485.

Change-Id: I6440702e33d5ec85d701f8be65453044b5dab746
Reviewed-on: https://go-review.googlesource.com/c/tools/+/246699
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-08-21 13:58:45 +00:00

173 lines
4.3 KiB
Go

// Copyright 2020 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 source
import (
"go/ast"
"go/constant"
"go/types"
"strconv"
"strings"
"unicode/utf8"
)
// printfArgKind returns the expected objKind when completing a
// printf-like operand. call is the printf-like function call, and
// argIdx is the index of call.Args being completed.
func printfArgKind(info *types.Info, call *ast.CallExpr, argIdx int) objKind {
// Printf-like function name must end in "f".
fn := exprObj(info, call.Fun)
if fn == nil || !strings.HasSuffix(fn.Name(), "f") {
return kindAny
}
sig, _ := fn.Type().(*types.Signature)
if sig == nil {
return kindAny
}
// Must be variadic and take at least two params.
numParams := sig.Params().Len()
if !sig.Variadic() || numParams < 2 || argIdx < numParams-1 {
return kindAny
}
// Param preceding variadic args must be a (format) string.
if !types.Identical(sig.Params().At(numParams-2).Type(), types.Typ[types.String]) {
return kindAny
}
// Format string must be a constant.
strArg := info.Types[call.Args[numParams-2]].Value
if strArg == nil || strArg.Kind() != constant.String {
return kindAny
}
return formatOperandKind(constant.StringVal(strArg), argIdx-(numParams-1)+1)
}
// formatOperandKind returns the objKind corresponding to format's
// operandIdx'th operand.
func formatOperandKind(format string, operandIdx int) objKind {
var (
prevOperandIdx int
kind = kindAny
)
for {
i := strings.Index(format, "%")
if i == -1 {
break
}
var operands []formatOperand
format, operands = parsePrintfVerb(format[i+1:], prevOperandIdx)
// Check if any this verb's operands correspond to our target
// operandIdx.
for _, v := range operands {
if v.idx == operandIdx {
if kind == kindAny {
kind = v.kind
} else if v.kind != kindAny {
// If mulitple verbs refer to the same operand, take the
// intersection of their kinds.
kind &= v.kind
}
}
prevOperandIdx = v.idx
}
}
return kind
}
type formatOperand struct {
// idx is the one-based printf operand index.
idx int
// kind is a mask of expected kinds of objects for this operand.
kind objKind
}
// parsePrintfVerb parses the leading printf verb in f. The opening
// "%" must already be trimmed from f. prevIdx is the previous
// operand's index, or zero if this is the first verb. The format
// string is returned with the leading verb removed. Multiple operands
// can be returned in the case of dynamic widths such as "%*.*f".
func parsePrintfVerb(f string, prevIdx int) (string, []formatOperand) {
var verbs []formatOperand
addVerb := func(k objKind) {
verbs = append(verbs, formatOperand{
idx: prevIdx + 1,
kind: k,
})
prevIdx++
}
for len(f) > 0 {
// Trim first rune off of f so we are guaranteed to make progress.
r, l := utf8.DecodeRuneInString(f)
f = f[l:]
// We care about three things:
// 1. The verb, which maps directly to object kind.
// 2. Explicit operand indices like "%[2]s".
// 3. Dynamic widths using "*".
switch r {
case '%':
return f, nil
case '*':
addVerb(kindInt)
continue
case '[':
// Parse operand index as in "%[2]s".
i := strings.Index(f, "]")
if i == -1 {
return f, nil
}
idx, err := strconv.Atoi(f[:i])
f = f[i+1:]
if err != nil {
return f, nil
}
prevIdx = idx - 1
continue
case 'v', 'T':
addVerb(kindAny)
case 't':
addVerb(kindBool)
case 'c', 'd', 'o', 'O', 'U':
addVerb(kindInt)
case 'e', 'E', 'f', 'F', 'g', 'G':
addVerb(kindFloat | kindComplex)
case 'b':
addVerb(kindInt | kindFloat | kindComplex | kindBytes)
case 'q', 's':
addVerb(kindString | kindBytes | kindStringer | kindError)
case 'x', 'X':
// Omit kindStringer and kindError though technically allowed.
addVerb(kindString | kindBytes | kindInt | kindFloat | kindComplex)
case 'p':
addVerb(kindPtr | kindSlice)
case 'w':
addVerb(kindError)
case '+', '-', '#', ' ', '.', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
// Flag or numeric width/precicision value.
continue
default:
// Assume unrecognized rune is a custom fmt.Formatter verb.
addVerb(kindAny)
}
if len(verbs) > 0 {
break
}
}
return f, verbs
}