aboutsummaryrefslogtreecommitdiff
path: root/gopls/internal/lsp/source/completion/printf.go
blob: 43201175542380d93668de7fce8c72f4b660c5a8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
// 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 completion

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 multiple 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/precision value.
			continue
		default:
			// Assume unrecognized rune is a custom fmt.Formatter verb.
			addVerb(kindAny)
		}

		if len(verbs) > 0 {
			break
		}
	}

	return f, verbs
}