@@ -30,10 +30,14 @@ import (
30
30
// AppendDebug writes a multi-line Go-like representation of a syntax tree node,
31
31
// including node position information and any relevant Go types.
32
32
func AppendDebug (dst []byte , node ast.Node , config DebugConfig ) []byte {
33
- d := & debugPrinter {cfg : config }
34
- dst = d .value (dst , reflect .ValueOf (node ), nil )
35
- dst = d .newline (dst )
36
- return dst
33
+ d := & debugPrinter {
34
+ cfg : config ,
35
+ buf : dst ,
36
+ }
37
+ if d .value (reflect .ValueOf (node ), nil ) {
38
+ d .newline ()
39
+ }
40
+ return d .buf
37
41
}
38
42
39
43
// DebugConfig configures the behavior of [AppendDebug].
@@ -49,161 +53,167 @@ type DebugConfig struct {
49
53
50
54
type debugPrinter struct {
51
55
w io.Writer
56
+ buf []byte
52
57
cfg DebugConfig
53
58
level int
54
59
}
55
60
56
- func (d * debugPrinter ) printf (dst []byte , format string , args ... any ) []byte {
57
- return fmt .Appendf (dst , format , args ... )
58
- }
59
-
60
- func (d * debugPrinter ) newline (dst []byte ) []byte {
61
- return fmt .Appendf (dst , "\n %s" , strings .Repeat ("\t " , d .level ))
62
- }
63
-
64
61
var (
65
62
typeTokenPos = reflect .TypeFor [token.Pos ]()
66
63
typeTokenToken = reflect .TypeFor [token.Token ]()
67
64
)
68
65
69
- func (d * debugPrinter ) value (dst []byte , v reflect.Value , impliedType reflect.Type ) []byte {
66
+ // value produces the given value, omitting type information if
67
+ // its type is the same as implied type. It reports whether
68
+ // anything was produced.
69
+ func (d * debugPrinter ) value (v reflect.Value , impliedType reflect.Type ) bool {
70
+ start := d .pos ()
71
+ d .value0 (v , impliedType )
72
+ return d .pos () > start
73
+ }
74
+
75
+ func (d * debugPrinter ) value0 (v reflect.Value , impliedType reflect.Type ) {
70
76
if d .cfg .Filter != nil && ! d .cfg .Filter (v ) {
71
- return dst
77
+ return
72
78
}
73
79
// Skip over interface types.
74
80
if v .Kind () == reflect .Interface {
75
81
v = v .Elem ()
76
82
}
77
- // Indirecting a nil interface gives a zero value.
78
- if ! v .IsValid () {
83
+ // Indirecting a nil interface gives a zero value. We
84
+ // can also have a nil pointer inside a non-nil interface.
85
+ if ! v .IsValid () || (v .Kind () == reflect .Pointer && v .IsNil ()) {
79
86
if ! d .cfg .OmitEmpty {
80
- dst = d .printf (dst , "nil" )
87
+ d .printf ("nil" )
81
88
}
82
- return dst
89
+ return
83
90
}
84
91
85
92
// We print the original pointer type if there was one.
86
93
origType := v .Type ()
87
94
88
95
v = reflect .Indirect (v )
89
- // Indirecting a nil pointer gives a zero value.
90
- if ! v .IsValid () {
91
- if ! d .cfg .OmitEmpty {
92
- dst = d .printf (dst , "nil" )
93
- }
94
- return dst
95
- }
96
96
97
97
if d .cfg .OmitEmpty && v .IsZero () {
98
- return dst
98
+ return
99
99
}
100
100
101
101
t := v .Type ()
102
102
switch t {
103
103
// Simple types which can stringify themselves.
104
104
case typeTokenPos , typeTokenToken :
105
- dst = d .printf (dst , "%s(%q)" , t , v )
105
+ d .printf ("%s(%q)" , t , v )
106
106
// Show relative positions too, if there are any, as they affect formatting.
107
107
if t == typeTokenPos {
108
108
pos := v .Interface ().(token.Pos )
109
109
if pos .HasRelPos () {
110
- dst = d .printf (dst , ".WithRel(%q)" , pos .RelPos ())
110
+ d .printf (".WithRel(%q)" , pos .RelPos ())
111
111
}
112
112
}
113
- return dst
113
+ return
114
114
}
115
115
116
- undoValue := len (dst )
117
116
switch t .Kind () {
118
117
default :
119
118
// We assume all other kinds are basic in practice, like string or bool.
120
119
if t .PkgPath () != "" {
121
120
// Mention defined and non-predeclared types, for clarity.
122
- dst = d .printf (dst , "%s(%#v)" , t , v )
121
+ d .printf ("%s(%#v)" , t , v )
123
122
} else {
124
- dst = d .printf (dst , "%#v" , v )
123
+ d .printf ("%#v" , v )
125
124
}
126
125
127
- case reflect .Slice :
126
+ case reflect .Slice , reflect .Struct :
127
+ valueStart := d .pos ()
128
128
if origType != impliedType {
129
- dst = d .printf (dst , "%s" , origType )
129
+ d .printf ("%s" , origType )
130
130
}
131
- dst = d .printf (dst , "{" )
131
+ d .printf ("{" )
132
132
d .level ++
133
- anyElems := false
134
- for i := 0 ; i < v .Len (); i ++ {
135
- ev := v .Index (i )
136
- undoElem := len (dst )
137
- dst = d .newline (dst )
138
- // Note: a slice literal implies the type of its elements
139
- // so we can avoid mentioning the type
140
- // of each element if it matches.
141
- if dst2 := d .value (dst , ev , t .Elem ()); len (dst2 ) == len (dst ) {
142
- dst = dst [:undoElem ]
143
- } else {
144
- dst = dst2
145
- anyElems = true
146
- }
133
+ var anyElems bool
134
+ if t .Kind () == reflect .Slice {
135
+ anyElems = d .sliceElems (v , t .Elem ())
136
+ } else {
137
+ anyElems = d .structFields (v )
147
138
}
148
139
d .level --
149
140
if ! anyElems && d .cfg .OmitEmpty {
150
- dst = dst [: undoValue ]
141
+ d . truncate ( valueStart )
151
142
} else {
152
143
if anyElems {
153
- dst = d .newline (dst )
144
+ d .newline ()
154
145
}
155
- dst = d .printf (dst , "}" )
146
+ d .printf ("}" )
156
147
}
148
+ }
149
+ }
157
150
158
- case reflect .Struct :
159
- if origType != impliedType {
160
- dst = d .printf (dst , "%s" , origType )
161
- }
162
- dst = d .printf (dst , "{" )
163
- anyElems := false
164
- d .level ++
165
- for i := 0 ; i < v .NumField (); i ++ {
166
- f := t .Field (i )
167
- if ! gotoken .IsExported (f .Name ) {
168
- continue
169
- }
170
- switch f .Name {
171
- // These fields are cyclic, and they don't represent the syntax anyway.
172
- case "Scope" , "Node" , "Unresolved" :
173
- continue
174
- }
175
- undoElem := len (dst )
176
- dst = d .newline (dst )
177
- dst = d .printf (dst , "%s: " , f .Name )
178
- if dst2 := d .value (dst , v .Field (i ), nil ); len (dst2 ) == len (dst ) {
179
- dst = dst [:undoElem ]
180
- } else {
181
- dst = dst2
182
- anyElems = true
183
- }
184
- }
185
- val := v .Addr ().Interface ()
186
- if val , ok := val .(ast.Node ); ok {
187
- // Comments attached to a node aren't a regular field, but are still useful.
188
- // The majority of nodes won't have comments, so skip them when empty.
189
- if comments := ast .Comments (val ); len (comments ) > 0 {
190
- anyElems = true
191
- dst = d .newline (dst )
192
- dst = d .printf (dst , "Comments: " )
193
- dst = d .value (dst , reflect .ValueOf (comments ), nil )
194
- }
151
+ func (d * debugPrinter ) sliceElems (v reflect.Value , elemType reflect.Type ) (anyElems bool ) {
152
+ for i := 0 ; i < v .Len (); i ++ {
153
+ ev := v .Index (i )
154
+ elemStart := d .pos ()
155
+ d .newline ()
156
+ // Note: a slice literal implies the type of its elements
157
+ // so we can avoid mentioning the type
158
+ // of each element if it matches.
159
+ if d .value (ev , elemType ) {
160
+ anyElems = true
161
+ } else {
162
+ d .truncate (elemStart )
195
163
}
196
- d .level --
197
- if ! anyElems && d .cfg .OmitEmpty {
198
- dst = dst [:undoValue ]
164
+ }
165
+ return anyElems
166
+ }
167
+
168
+ func (d * debugPrinter ) structFields (v reflect.Value ) (anyElems bool ) {
169
+ t := v .Type ()
170
+ for i := 0 ; i < v .NumField (); i ++ {
171
+ f := t .Field (i )
172
+ if ! gotoken .IsExported (f .Name ) {
173
+ continue
174
+ }
175
+ switch f .Name {
176
+ // These fields are cyclic, and they don't represent the syntax anyway.
177
+ case "Scope" , "Node" , "Unresolved" :
178
+ continue
179
+ }
180
+ elemStart := d .pos ()
181
+ d .newline ()
182
+ d .printf ("%s: " , f .Name )
183
+ if d .value (v .Field (i ), nil ) {
184
+ anyElems = true
199
185
} else {
200
- if anyElems {
201
- dst = d .newline (dst )
202
- }
203
- dst = d .printf (dst , "}" )
186
+ d .truncate (elemStart )
204
187
}
205
188
}
206
- return dst
189
+ val := v .Addr ().Interface ()
190
+ if val , ok := val .(ast.Node ); ok {
191
+ // Comments attached to a node aren't a regular field, but are still useful.
192
+ // The majority of nodes won't have comments, so skip them when empty.
193
+ if comments := ast .Comments (val ); len (comments ) > 0 {
194
+ anyElems = true
195
+ d .newline ()
196
+ d .printf ("Comments: " )
197
+ d .value (reflect .ValueOf (comments ), nil )
198
+ }
199
+ }
200
+ return anyElems
201
+ }
202
+
203
+ func (d * debugPrinter ) printf (format string , args ... any ) {
204
+ d .buf = fmt .Appendf (d .buf , format , args ... )
205
+ }
206
+
207
+ func (d * debugPrinter ) newline () {
208
+ d .buf = fmt .Appendf (d .buf , "\n %s" , strings .Repeat ("\t " , d .level ))
209
+ }
210
+
211
+ func (d * debugPrinter ) pos () int {
212
+ return len (d .buf )
213
+ }
214
+
215
+ func (d * debugPrinter ) truncate (pos int ) {
216
+ d .buf = d .buf [:pos ]
207
217
}
208
218
209
219
func DebugStr (x interface {}) (out string ) {
0 commit comments