mirror of
https://github.com/golang/go
synced 2024-11-18 22:44:48 -07:00
173 lines
4.3 KiB
Go
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
|
||
|
}
|