@@ -7,65 +7,143 @@ package xerrors
7
7
import (
8
8
"fmt"
9
9
"strings"
10
+ "unicode"
11
+ "unicode/utf8"
10
12
11
13
"golang.org/x/xerrors/internal"
12
14
)
13
15
16
+ const percentBangString = "%!"
17
+
14
18
// Errorf formats according to a format specifier and returns the string as a
15
19
// value that satisfies error.
16
20
//
17
21
// The returned error includes the file and line number of the caller when
18
22
// formatted with additional detail enabled. If the last argument is an error
19
23
// the returned error's Format method will return it if the format string ends
20
24
// with ": %s", ": %v", or ": %w". If the last argument is an error and the
21
- // format string ends with ": %w", the returned error implements Wrapper
22
- // with an Unwrap method returning it.
25
+ // format string ends with ": %w", the returned error implements an Unwrap
26
+ // method returning it.
27
+ //
28
+ // If the format specifier includes a %w verb with an error operand in a
29
+ // position other than at the end, the returned error will still implement an
30
+ // Unwrap method returning the operand, but the error's Format method will not
31
+ // return the wrapped error.
32
+ //
33
+ // It is invalid to include more than one %w verb or to supply it with an
34
+ // operand that does not implement the error interface. The %w verb is otherwise
35
+ // a synonym for %v.
23
36
func Errorf (format string , a ... interface {}) error {
24
- err , wrap := lastError (format , a )
25
37
format = formatPlusW (format )
26
- if err == nil {
27
- return & noWrapError {fmt .Sprintf (format , a ... ), nil , Caller (1 )}
38
+ // Support a ": %[wsv]" suffix, which works well with xerrors.Formatter.
39
+ wrap := strings .HasSuffix (format , ": %w" )
40
+ idx , format2 , ok := parsePercentW (format )
41
+ percentWElsewhere := ! wrap && idx >= 0
42
+ if ! percentWElsewhere && (wrap || strings .HasSuffix (format , ": %s" ) || strings .HasSuffix (format , ": %v" )) {
43
+ err := errorAt (a , len (a )- 1 )
44
+ if err == nil {
45
+ return & noWrapError {fmt .Sprintf (format , a ... ), nil , Caller (1 )}
46
+ }
47
+ // TODO: this is not entirely correct. The error value could be
48
+ // printed elsewhere in format if it mixes numbered with unnumbered
49
+ // substitutions. With relatively small changes to doPrintf we can
50
+ // have it optionally ignore extra arguments and pass the argument
51
+ // list in its entirety.
52
+ msg := fmt .Sprintf (format [:len (format )- len (": %s" )], a [:len (a )- 1 ]... )
53
+ frame := Frame {}
54
+ if internal .EnableTrace {
55
+ frame = Caller (1 )
56
+ }
57
+ if wrap {
58
+ return & wrapError {msg , err , frame }
59
+ }
60
+ return & noWrapError {msg , err , frame }
61
+ }
62
+ // Support %w anywhere.
63
+ // TODO: don't repeat the wrapped error's message when %w occurs in the middle.
64
+ msg := fmt .Sprintf (format2 , a ... )
65
+ if idx < 0 {
66
+ return & noWrapError {msg , nil , Caller (1 )}
67
+ }
68
+ err := errorAt (a , idx )
69
+ if ! ok || err == nil {
70
+ // Too many %ws or argument of %w is not an error. Approximate the Go
71
+ // 1.13 fmt.Errorf message.
72
+ return & noWrapError {fmt .Sprintf ("%sw(%s)" , percentBangString , msg ), nil , Caller (1 )}
28
73
}
29
-
30
- // TODO: this is not entirely correct. The error value could be
31
- // printed elsewhere in format if it mixes numbered with unnumbered
32
- // substitutions. With relatively small changes to doPrintf we can
33
- // have it optionally ignore extra arguments and pass the argument
34
- // list in its entirety.
35
- msg := fmt .Sprintf (format [:len (format )- len (": %s" )], a [:len (a )- 1 ]... )
36
74
frame := Frame {}
37
75
if internal .EnableTrace {
38
76
frame = Caller (1 )
39
77
}
40
- if wrap {
41
- return & wrapError {msg , err , frame }
78
+ return & wrapError {msg , err , frame }
79
+ }
80
+
81
+ func errorAt (args []interface {}, i int ) error {
82
+ if i < 0 || i >= len (args ) {
83
+ return nil
42
84
}
43
- return & noWrapError {msg , err , frame }
85
+ err , ok := args [i ].(error )
86
+ if ! ok {
87
+ return nil
88
+ }
89
+ return err
44
90
}
45
91
46
92
// formatPlusW is used to avoid the vet check that will barf at %w.
47
93
func formatPlusW (s string ) string {
48
94
return s
49
95
}
50
96
51
- func lastError (format string , a []interface {}) (err error , wrap bool ) {
52
- wrap = strings .HasSuffix (format , ": %w" )
53
- if ! wrap &&
54
- ! strings .HasSuffix (format , ": %s" ) &&
55
- ! strings .HasSuffix (format , ": %v" ) {
56
- return nil , false
57
- }
58
-
59
- if len (a ) == 0 {
60
- return nil , false
97
+ // Return the index of the only %w in format, or -1 if none.
98
+ // Also return a rewritten format string with %w replaced by %v, and
99
+ // false if there is more than one %w.
100
+ // TODO: handle "%[N]w".
101
+ func parsePercentW (format string ) (idx int , newFormat string , ok bool ) {
102
+ // Loosely copied from golang.org/x/tools/go/analysis/passes/printf/printf.go.
103
+ idx = - 1
104
+ ok = true
105
+ n := 0
106
+ sz := 0
107
+ var isW bool
108
+ for i := 0 ; i < len (format ); i += sz {
109
+ if format [i ] != '%' {
110
+ sz = 1
111
+ continue
112
+ }
113
+ // "%%" is not a format directive.
114
+ if i + 1 < len (format ) && format [i + 1 ] == '%' {
115
+ sz = 2
116
+ continue
117
+ }
118
+ sz , isW = parsePrintfVerb (format [i :])
119
+ if isW {
120
+ if idx >= 0 {
121
+ ok = false
122
+ } else {
123
+ idx = n
124
+ }
125
+ // "Replace" the last character, the 'w', with a 'v'.
126
+ p := i + sz - 1
127
+ format = format [:p ] + "v" + format [p + 1 :]
128
+ }
129
+ n ++
61
130
}
131
+ return idx , format , ok
132
+ }
62
133
63
- err , ok := a [len (a )- 1 ].(error )
64
- if ! ok {
65
- return nil , false
134
+ // Parse the printf verb starting with a % at s[0].
135
+ // Return how many bytes it occupies and whether the verb is 'w'.
136
+ func parsePrintfVerb (s string ) (int , bool ) {
137
+ // Assume only that the directive is a sequence of non-letters followed by a single letter.
138
+ sz := 0
139
+ var r rune
140
+ for i := 1 ; i < len (s ); i += sz {
141
+ r , sz = utf8 .DecodeRuneInString (s [i :])
142
+ if unicode .IsLetter (r ) {
143
+ return i + sz , r == 'w'
144
+ }
66
145
}
67
-
68
- return err , wrap
146
+ return len (s ), false
69
147
}
70
148
71
149
type noWrapError struct {
0 commit comments