Skip to content

Commit 0b06bd5

Browse files
committed
internal/astinternal: make it easier to debug references
The `astutil.Sanitize` logic tries to respect `Ident.Node` references, but when debugging generated syntax, it's hard to be sure whether the Node references are pointing to the correct places. This adds functionality to `astinternal.AppendDebug` to cause it to print references and their referred-to nodes in a somewhat friendly manner. Here's a (slightly abbreviated) example of its output from a recent debugging session: Value: *ast.StructLit@ref001{ Elts: []ast.Decl{ *ast.Field{ Label: *ast.Ident{ Name: "next" } Value: *ast.Ident{ Name: "_schema" Node: @ref001 (*ast.StructLit) } } } } Signed-off-by: Roger Peppe <[email protected]> Change-Id: I2f859f3b8d365c6ba3408902039f2aa9cbd8a679 Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1205069 Reviewed-by: Daniel Martí <[email protected]> Unity-Result: CUE porcuepine <[email protected]> TryBot-Result: CUEcueckoo <[email protected]>
1 parent 781f140 commit 0b06bd5

File tree

4 files changed

+104
-8
lines changed

4 files changed

+104
-8
lines changed

Diff for: internal/astinternal/debug.go

+90-4
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ func AppendDebug(dst []byte, node ast.Node, config DebugConfig) []byte {
3333
cfg: config,
3434
buf: dst,
3535
}
36+
if config.IncludeNodeRefs {
37+
d.nodeRefs = make(map[ast.Node]int)
38+
d.addNodeRefs(reflect.ValueOf(node))
39+
}
3640
if d.value(reflect.ValueOf(node), nil) {
3741
d.newline()
3842
}
@@ -48,12 +52,18 @@ type DebugConfig struct {
4852
// OmitEmpty causes empty strings, empty structs, empty lists,
4953
// nil pointers, invalid positions, and missing tokens to be omitted.
5054
OmitEmpty bool
55+
56+
// IncludeNodeRefs causes a Node reference in an identifier
57+
// to indicate which (if any) ast.Node it refers to.
58+
IncludeNodeRefs bool
5159
}
5260

5361
type debugPrinter struct {
54-
buf []byte
55-
cfg DebugConfig
56-
level int
62+
buf []byte
63+
cfg DebugConfig
64+
level int
65+
nodeRefs map[ast.Node]int
66+
refID int
5767
}
5868

5969
// value produces the given value, omitting type information if
@@ -71,6 +81,7 @@ func (d *debugPrinter) value0(v reflect.Value, impliedType reflect.Type) {
7181
}
7282
// Skip over interfaces and pointers, stopping early if nil.
7383
concreteType := v.Type()
84+
refName := ""
7485
for {
7586
k := v.Kind()
7687
if k != reflect.Interface && k != reflect.Pointer {
@@ -82,6 +93,13 @@ func (d *debugPrinter) value0(v reflect.Value, impliedType reflect.Type) {
8293
}
8394
return
8495
}
96+
if k == reflect.Pointer {
97+
if n, ok := v.Interface().(ast.Node); ok {
98+
if id, ok := d.nodeRefs[n]; ok {
99+
refName = refIDToName(id)
100+
}
101+
}
102+
}
85103
v = v.Elem()
86104
if k == reflect.Interface {
87105
// For example, *ast.Ident can be the concrete type behind an ast.Expr.
@@ -125,6 +143,9 @@ func (d *debugPrinter) value0(v reflect.Value, impliedType reflect.Type) {
125143
if concreteType != impliedType {
126144
d.printf("%s", concreteType)
127145
}
146+
if refName != "" {
147+
d.printf("@%s", refName)
148+
}
128149
d.printf("{")
129150
d.level++
130151
var anyElems bool
@@ -169,9 +190,18 @@ func (d *debugPrinter) structFields(v reflect.Value) (anyElems bool) {
169190
if !gotoken.IsExported(f.Name) {
170191
continue
171192
}
193+
if f.Name == "Node" {
194+
nodeVal := v.Field(i)
195+
if !d.cfg.IncludeNodeRefs || nodeVal.IsNil() {
196+
continue
197+
}
198+
d.newline()
199+
d.printf("Node: @%s (%v)", refIDToName(d.nodeRefs[nodeVal.Interface().(ast.Node)]), nodeVal.Elem().Type())
200+
continue
201+
}
172202
switch f.Name {
173203
// These fields are cyclic, and they don't represent the syntax anyway.
174-
case "Scope", "Node", "Unresolved":
204+
case "Scope", "Unresolved":
175205
continue
176206
}
177207
elemStart := d.pos()
@@ -213,6 +243,62 @@ func (d *debugPrinter) truncate(pos int) {
213243
d.buf = d.buf[:pos]
214244
}
215245

246+
// addNodeRefs does a first pass over the value looking for
247+
// [ast.Ident] nodes that refer to other nodes.
248+
// This means when we find such a node, we can include
249+
// an anchor name for it
250+
func (d *debugPrinter) addNodeRefs(v reflect.Value) {
251+
// Skip over interfaces and pointers, stopping early if nil.
252+
for ; v.Kind() == reflect.Interface || v.Kind() == reflect.Pointer; v = v.Elem() {
253+
if v.IsNil() {
254+
return
255+
}
256+
}
257+
258+
t := v.Type()
259+
switch v := v.Interface().(type) {
260+
case token.Pos, token.Token:
261+
// Simple types which can't contain an ast.Node.
262+
return
263+
case ast.Ident:
264+
if v.Node != nil {
265+
if _, ok := d.nodeRefs[v.Node]; !ok {
266+
d.refID++
267+
d.nodeRefs[v.Node] = d.refID
268+
}
269+
}
270+
return
271+
}
272+
273+
switch t.Kind() {
274+
case reflect.Slice:
275+
for i := 0; i < v.Len(); i++ {
276+
d.addNodeRefs(v.Index(i))
277+
}
278+
case reflect.Struct:
279+
t := v.Type()
280+
for i := 0; i < v.NumField(); i++ {
281+
f := t.Field(i)
282+
if !gotoken.IsExported(f.Name) {
283+
continue
284+
}
285+
switch f.Name {
286+
// These fields don't point to any nodes that Node can refer to.
287+
case "Scope", "Node", "Unresolved":
288+
continue
289+
}
290+
d.addNodeRefs(v.Field(i))
291+
}
292+
}
293+
}
294+
295+
func refIDToName(id int) string {
296+
if id == 0 {
297+
return "unknown"
298+
}
299+
return fmt.Sprintf("ref%03d", id)
300+
}
301+
216302
func DebugStr(x interface{}) (out string) {
217303
if n, ok := x.(ast.Node); ok {
218304
comments := ""

Diff for: internal/astinternal/debug_test.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,13 @@ func TestDebugPrint(t *testing.T) {
4343
qt.Assert(t, qt.IsNil(err))
4444

4545
// The full syntax tree, as printed by default.
46-
full := astinternal.AppendDebug(nil, f, astinternal.DebugConfig{})
46+
// We enable IncludeNodeRefs because it only adds information
47+
// that would not otherwise be present.
48+
// The syntax tree does not contain any maps, so
49+
// the generated reference names should be deterministic.
50+
full := astinternal.AppendDebug(nil, f, astinternal.DebugConfig{
51+
IncludeNodeRefs: true,
52+
})
4753
t.Writer(file.Name).Write(full)
4854

4955
// A syntax tree which omits any empty values,

Diff for: internal/astinternal/testdata/debugprint/comprehensions.txtar

+5-2
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,12 @@ for k, v in input if v > 2 {
4646
Clauses: []ast.Clause{
4747
*ast.ForClause{
4848
For: token.Pos("comprehensions.cue:4:1", newline)
49-
Key: *ast.Ident{
49+
Key: *ast.Ident@ref002{
5050
NamePos: token.Pos("comprehensions.cue:4:5", blank)
5151
Name: "k"
5252
}
5353
Colon: token.Pos("comprehensions.cue:4:6", nospace)
54-
Value: *ast.Ident{
54+
Value: *ast.Ident@ref001{
5555
NamePos: token.Pos("comprehensions.cue:4:8", blank)
5656
Name: "v"
5757
}
@@ -67,6 +67,7 @@ for k, v in input if v > 2 {
6767
X: *ast.Ident{
6868
NamePos: token.Pos("comprehensions.cue:4:22", blank)
6969
Name: "v"
70+
Node: @ref001 (*ast.Ident)
7071
}
7172
OpPos: token.Pos("comprehensions.cue:4:24", blank)
7273
Op: token.Token(">")
@@ -87,6 +88,7 @@ for k, v in input if v > 2 {
8788
X: *ast.Ident{
8889
NamePos: token.Pos("comprehensions.cue:5:3", nospace)
8990
Name: "k"
91+
Node: @ref002 (*ast.Ident)
9092
}
9193
Rparen: token.Pos("comprehensions.cue:5:4", nospace)
9294
}
@@ -97,6 +99,7 @@ for k, v in input if v > 2 {
9799
Value: *ast.Ident{
98100
NamePos: token.Pos("comprehensions.cue:5:7", blank)
99101
Name: "v"
102+
Node: @ref001 (*ast.Ident)
100103
}
101104
Attrs: []*ast.Attribute{}
102105
}

Diff for: internal/astinternal/testdata/debugprint/fields.txtar

+2-1
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ embed: {
153153
Constraint: token.Token("ILLEGAL")
154154
TokenPos: token.Pos("fields.cue:5:8", nospace)
155155
Token: token.Token(":")
156-
Value: *ast.StructLit{
156+
Value: *ast.StructLit@ref001{
157157
Lbrace: token.Pos("fields.cue:5:10", blank)
158158
Elts: []ast.Decl{
159159
*ast.Field{
@@ -196,6 +196,7 @@ embed: {
196196
Expr: *ast.Ident{
197197
NamePos: token.Pos("fields.cue:10:2", newline)
198198
Name: "#Schema"
199+
Node: @ref001 (*ast.StructLit)
199200
}
200201
}
201202
}

0 commit comments

Comments
 (0)