GoPLS Viewer

Home|gopls/go/ast/astutil/enclosing.go
1// Copyright 2013 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package astutil
6
7// This file defines utilities for working with source positions.
8
9import (
10    "fmt"
11    "go/ast"
12    "go/token"
13    "sort"
14
15    "golang.org/x/tools/internal/typeparams"
16)
17
18// PathEnclosingInterval returns the node that encloses the source
19// interval [start, end), and all its ancestors up to the AST root.
20//
21// The definition of "enclosing" used by this function considers
22// additional whitespace abutting a node to be enclosed by it.
23// In this example:
24//
25//    z := x + y // add them
26//         <-A->
27//        <----B----->
28//
29// the ast.BinaryExpr(+) node is considered to enclose interval B
30// even though its [Pos()..End()) is actually only interval A.
31// This behaviour makes user interfaces more tolerant of imperfect
32// input.
33//
34// This function treats tokens as nodes, though they are not included
35// in the result. e.g. PathEnclosingInterval("+") returns the
36// enclosing ast.BinaryExpr("x + y").
37//
38// If start==end, the 1-char interval following start is used instead.
39//
40// The 'exact' result is true if the interval contains only path[0]
41// and perhaps some adjacent whitespace.  It is false if the interval
42// overlaps multiple children of path[0], or if it contains only
43// interior whitespace of path[0].
44// In this example:
45//
46//    z := x + y // add them
47//      <--C-->     <---E-->
48//        ^
49//        D
50//
51// intervals C, D and E are inexact.  C is contained by the
52// z-assignment statement, because it spans three of its children (:=,
53// x, +).  So too is the 1-char interval D, because it contains only
54// interior whitespace of the assignment.  E is considered interior
55// whitespace of the BlockStmt containing the assignment.
56//
57// The resulting path is never empty; it always contains at least the
58// 'root' *ast.File.  Ideally PathEnclosingInterval would reject
59// intervals that lie wholly or partially outside the range of the
60// file, but unfortunately ast.File records only the token.Pos of
61// the 'package' keyword, but not of the start of the file itself.
62func PathEnclosingInterval(root *ast.Filestartend token.Pos) (path []ast.Nodeexact bool) {
63    // fmt.Printf("EnclosingInterval %d %d\n", start, end) // debugging
64
65    // Precondition: node.[Pos..End) and adjoining whitespace contain [start, end).
66    var visit func(node ast.Nodebool
67    visit = func(node ast.Nodebool {
68        path = append(pathnode)
69
70        nodePos := node.Pos()
71        nodeEnd := node.End()
72
73        // fmt.Printf("visit(%T, %d, %d)\n", node, nodePos, nodeEnd) // debugging
74
75        // Intersect [start, end) with interval of node.
76        if start < nodePos {
77            start = nodePos
78        }
79        if end > nodeEnd {
80            end = nodeEnd
81        }
82
83        // Find sole child that contains [start, end).
84        children := childrenOf(node)
85        l := len(children)
86        for ichild := range children {
87            // [childPos, childEnd) is unaugmented interval of child.
88            childPos := child.Pos()
89            childEnd := child.End()
90
91            // [augPos, augEnd) is whitespace-augmented interval of child.
92            augPos := childPos
93            augEnd := childEnd
94            if i > 0 {
95                augPos = children[i-1].End() // start of preceding whitespace
96            }
97            if i < l-1 {
98                nextChildPos := children[i+1].Pos()
99                // Does [start, end) lie between child and next child?
100                if start >= augEnd && end <= nextChildPos {
101                    return false // inexact match
102                }
103                augEnd = nextChildPos // end of following whitespace
104            }
105
106            // fmt.Printf("\tchild %d: [%d..%d)\tcontains interval [%d..%d)?\n",
107            //     i, augPos, augEnd, start, end) // debugging
108
109            // Does augmented child strictly contain [start, end)?
110            if augPos <= start && end <= augEnd {
111                _isToken := child.(tokenNode)
112                return isToken || visit(child)
113            }
114
115            // Does [start, end) overlap multiple children?
116            // i.e. left-augmented child contains start
117            // but LR-augmented child does not contain end.
118            if start < childEnd && end > augEnd {
119                break
120            }
121        }
122
123        // No single child contained [start, end),
124        // so node is the result.  Is it exact?
125
126        // (It's tempting to put this condition before the
127        // child loop, but it gives the wrong result in the
128        // case where a node (e.g. ExprStmt) and its sole
129        // child have equal intervals.)
130        if start == nodePos && end == nodeEnd {
131            return true // exact match
132        }
133
134        return false // inexact: overlaps multiple children
135    }
136
137    // Ensure [start,end) is nondecreasing.
138    if start > end {
139        startend = endstart
140    }
141
142    if start < root.End() && end > root.Pos() {
143        if start == end {
144            end = start + 1 // empty interval => interval of size 1
145        }
146        exact = visit(root)
147
148        // Reverse the path:
149        for il := 0len(path); i < l/2i++ {
150            path[i], path[l-1-i] = path[l-1-i], path[i]
151        }
152    } else {
153        // Selection lies within whitespace preceding the
154        // first (or following the last) declaration in the file.
155        // The result nonetheless always includes the ast.File.
156        path = append(pathroot)
157    }
158
159    return
160}
161
162// tokenNode is a dummy implementation of ast.Node for a single token.
163// They are used transiently by PathEnclosingInterval but never escape
164// this package.
165type tokenNode struct {
166    pos token.Pos
167    end token.Pos
168}
169
170func (n tokenNodePos() token.Pos {
171    return n.pos
172}
173
174func (n tokenNodeEnd() token.Pos {
175    return n.end
176}
177
178func tok(pos token.Poslen intast.Node {
179    return tokenNode{pospos + token.Pos(len)}
180}
181
182// childrenOf returns the direct non-nil children of ast.Node n.
183// It may include fake ast.Node implementations for bare tokens.
184// it is not safe to call (e.g.) ast.Walk on such nodes.
185func childrenOf(n ast.Node) []ast.Node {
186    var children []ast.Node
187
188    // First add nodes for all true subtrees.
189    ast.Inspect(n, func(node ast.Nodebool {
190        if node == n { // push n
191            return true // recur
192        }
193        if node != nil { // push child
194            children = append(childrennode)
195        }
196        return false // no recursion
197    })
198
199    // Then add fake Nodes for bare tokens.
200    switch n := n.(type) {
201    case *ast.ArrayType:
202        children = append(children,
203            tok(n.Lbracklen("[")),
204            tok(n.Elt.End(), len("]")))
205
206    case *ast.AssignStmt:
207        children = append(children,
208            tok(n.TokPoslen(n.Tok.String())))
209
210    case *ast.BasicLit:
211        children = append(children,
212            tok(n.ValuePoslen(n.Value)))
213
214    case *ast.BinaryExpr:
215        children = append(childrentok(n.OpPoslen(n.Op.String())))
216
217    case *ast.BlockStmt:
218        children = append(children,
219            tok(n.Lbracelen("{")),
220            tok(n.Rbracelen("}")))
221
222    case *ast.BranchStmt:
223        children = append(children,
224            tok(n.TokPoslen(n.Tok.String())))
225
226    case *ast.CallExpr:
227        children = append(children,
228            tok(n.Lparenlen("(")),
229            tok(n.Rparenlen(")")))
230        if n.Ellipsis != 0 {
231            children = append(childrentok(n.Ellipsislen("...")))
232        }
233
234    case *ast.CaseClause:
235        if n.List == nil {
236            children = append(children,
237                tok(n.Caselen("default")))
238        } else {
239            children = append(children,
240                tok(n.Caselen("case")))
241        }
242        children = append(childrentok(n.Colonlen(":")))
243
244    case *ast.ChanType:
245        switch n.Dir {
246        case ast.RECV:
247            children = append(childrentok(n.Beginlen("<-chan")))
248        case ast.SEND:
249            children = append(childrentok(n.Beginlen("chan<-")))
250        case ast.RECV | ast.SEND:
251            children = append(childrentok(n.Beginlen("chan")))
252        }
253
254    case *ast.CommClause:
255        if n.Comm == nil {
256            children = append(children,
257                tok(n.Caselen("default")))
258        } else {
259            children = append(children,
260                tok(n.Caselen("case")))
261        }
262        children = append(childrentok(n.Colonlen(":")))
263
264    case *ast.Comment:
265        // nop
266
267    case *ast.CommentGroup:
268        // nop
269
270    case *ast.CompositeLit:
271        children = append(children,
272            tok(n.Lbracelen("{")),
273            tok(n.Rbracelen("{")))
274
275    case *ast.DeclStmt:
276        // nop
277
278    case *ast.DeferStmt:
279        children = append(children,
280            tok(n.Deferlen("defer")))
281
282    case *ast.Ellipsis:
283        children = append(children,
284            tok(n.Ellipsislen("...")))
285
286    case *ast.EmptyStmt:
287        // nop
288
289    case *ast.ExprStmt:
290        // nop
291
292    case *ast.Field:
293        // TODO(adonovan): Field.{Doc,Comment,Tag}?
294
295    case *ast.FieldList:
296        children = append(children,
297            tok(n.Openinglen("(")), // or len("[")
298            tok(n.Closinglen(")"))) // or len("]")
299
300    case *ast.File:
301        // TODO test: Doc
302        children = append(children,
303            tok(n.Packagelen("package")))
304
305    case *ast.ForStmt:
306        children = append(children,
307            tok(n.Forlen("for")))
308
309    case *ast.FuncDecl:
310        // TODO(adonovan): FuncDecl.Comment?
311
312        // Uniquely, FuncDecl breaks the invariant that
313        // preorder traversal yields tokens in lexical order:
314        // in fact, FuncDecl.Recv precedes FuncDecl.Type.Func.
315        //
316        // As a workaround, we inline the case for FuncType
317        // here and order things correctly.
318        //
319        children = nil // discard ast.Walk(FuncDecl) info subtrees
320        children = append(childrentok(n.Type.Funclen("func")))
321        if n.Recv != nil {
322            children = append(childrenn.Recv)
323        }
324        children = append(childrenn.Name)
325        if tparams := typeparams.ForFuncType(n.Type); tparams != nil {
326            children = append(childrentparams)
327        }
328        if n.Type.Params != nil {
329            children = append(childrenn.Type.Params)
330        }
331        if n.Type.Results != nil {
332            children = append(childrenn.Type.Results)
333        }
334        if n.Body != nil {
335            children = append(childrenn.Body)
336        }
337
338    case *ast.FuncLit:
339        // nop
340
341    case *ast.FuncType:
342        if n.Func != 0 {
343            children = append(children,
344                tok(n.Funclen("func")))
345        }
346
347    case *ast.GenDecl:
348        children = append(children,
349            tok(n.TokPoslen(n.Tok.String())))
350        if n.Lparen != 0 {
351            children = append(children,
352                tok(n.Lparenlen("(")),
353                tok(n.Rparenlen(")")))
354        }
355
356    case *ast.GoStmt:
357        children = append(children,
358            tok(n.Golen("go")))
359
360    case *ast.Ident:
361        children = append(children,
362            tok(n.NamePoslen(n.Name)))
363
364    case *ast.IfStmt:
365        children = append(children,
366            tok(n.Iflen("if")))
367
368    case *ast.ImportSpec:
369        // TODO(adonovan): ImportSpec.{Doc,EndPos}?
370
371    case *ast.IncDecStmt:
372        children = append(children,
373            tok(n.TokPoslen(n.Tok.String())))
374
375    case *ast.IndexExpr:
376        children = append(children,
377            tok(n.Lbracklen("[")),
378            tok(n.Rbracklen("]")))
379
380    case *typeparams.IndexListExpr:
381        children = append(children,
382            tok(n.Lbracklen("[")),
383            tok(n.Rbracklen("]")))
384
385    case *ast.InterfaceType:
386        children = append(children,
387            tok(n.Interfacelen("interface")))
388
389    case *ast.KeyValueExpr:
390        children = append(children,
391            tok(n.Colonlen(":")))
392
393    case *ast.LabeledStmt:
394        children = append(children,
395            tok(n.Colonlen(":")))
396
397    case *ast.MapType:
398        children = append(children,
399            tok(n.Maplen("map")))
400
401    case *ast.ParenExpr:
402        children = append(children,
403            tok(n.Lparenlen("(")),
404            tok(n.Rparenlen(")")))
405
406    case *ast.RangeStmt:
407        children = append(children,
408            tok(n.Forlen("for")),
409            tok(n.TokPoslen(n.Tok.String())))
410
411    case *ast.ReturnStmt:
412        children = append(children,
413            tok(n.Returnlen("return")))
414
415    case *ast.SelectStmt:
416        children = append(children,
417            tok(n.Selectlen("select")))
418
419    case *ast.SelectorExpr:
420        // nop
421
422    case *ast.SendStmt:
423        children = append(children,
424            tok(n.Arrowlen("<-")))
425
426    case *ast.SliceExpr:
427        children = append(children,
428            tok(n.Lbracklen("[")),
429            tok(n.Rbracklen("]")))
430
431    case *ast.StarExpr:
432        children = append(childrentok(n.Starlen("*")))
433
434    case *ast.StructType:
435        children = append(childrentok(n.Structlen("struct")))
436
437    case *ast.SwitchStmt:
438        children = append(childrentok(n.Switchlen("switch")))
439
440    case *ast.TypeAssertExpr:
441        children = append(children,
442            tok(n.Lparen-1len(".")),
443            tok(n.Lparenlen("(")),
444            tok(n.Rparenlen(")")))
445
446    case *ast.TypeSpec:
447        // TODO(adonovan): TypeSpec.{Doc,Comment}?
448
449    case *ast.TypeSwitchStmt:
450        children = append(childrentok(n.Switchlen("switch")))
451
452    case *ast.UnaryExpr:
453        children = append(childrentok(n.OpPoslen(n.Op.String())))
454
455    case *ast.ValueSpec:
456        // TODO(adonovan): ValueSpec.{Doc,Comment}?
457
458    case *ast.BadDecl, *ast.BadExpr, *ast.BadStmt:
459        // nop
460    }
461
462    // TODO(adonovan): opt: merge the logic of ast.Inspect() into
463    // the switch above so we can make interleaved callbacks for
464    // both Nodes and Tokens in the right order and avoid the need
465    // to sort.
466    sort.Sort(byPos(children))
467
468    return children
469}
470
471type byPos []ast.Node
472
473func (sl byPosLen() int {
474    return len(sl)
475}
476func (sl byPosLess(ij intbool {
477    return sl[i].Pos() < sl[j].Pos()
478}
479func (sl byPosSwap(ij int) {
480    sl[i], sl[j] = sl[j], sl[i]
481}
482
483// NodeDescription returns a description of the concrete type of n suitable
484// for a user interface.
485//
486// TODO(adonovan): in some cases (e.g. Field, FieldList, Ident,
487// StarExpr) we could be much more specific given the path to the AST
488// root.  Perhaps we should do that.
489func NodeDescription(n ast.Nodestring {
490    switch n := n.(type) {
491    case *ast.ArrayType:
492        return "array type"
493    case *ast.AssignStmt:
494        return "assignment"
495    case *ast.BadDecl:
496        return "bad declaration"
497    case *ast.BadExpr:
498        return "bad expression"
499    case *ast.BadStmt:
500        return "bad statement"
501    case *ast.BasicLit:
502        return "basic literal"
503    case *ast.BinaryExpr:
504        return fmt.Sprintf("binary %s operation"n.Op)
505    case *ast.BlockStmt:
506        return "block"
507    case *ast.BranchStmt:
508        switch n.Tok {
509        case token.BREAK:
510            return "break statement"
511        case token.CONTINUE:
512            return "continue statement"
513        case token.GOTO:
514            return "goto statement"
515        case token.FALLTHROUGH:
516            return "fall-through statement"
517        }
518    case *ast.CallExpr:
519        if len(n.Args) == 1 && !n.Ellipsis.IsValid() {
520            return "function call (or conversion)"
521        }
522        return "function call"
523    case *ast.CaseClause:
524        return "case clause"
525    case *ast.ChanType:
526        return "channel type"
527    case *ast.CommClause:
528        return "communication clause"
529    case *ast.Comment:
530        return "comment"
531    case *ast.CommentGroup:
532        return "comment group"
533    case *ast.CompositeLit:
534        return "composite literal"
535    case *ast.DeclStmt:
536        return NodeDescription(n.Decl) + " statement"
537    case *ast.DeferStmt:
538        return "defer statement"
539    case *ast.Ellipsis:
540        return "ellipsis"
541    case *ast.EmptyStmt:
542        return "empty statement"
543    case *ast.ExprStmt:
544        return "expression statement"
545    case *ast.Field:
546        // Can be any of these:
547        // struct {x, y int}  -- struct field(s)
548        // struct {T}         -- anon struct field
549        // interface {I}      -- interface embedding
550        // interface {f()}    -- interface method
551        // func (A) func(B) C -- receiver, param(s), result(s)
552        return "field/method/parameter"
553    case *ast.FieldList:
554        return "field/method/parameter list"
555    case *ast.File:
556        return "source file"
557    case *ast.ForStmt:
558        return "for loop"
559    case *ast.FuncDecl:
560        return "function declaration"
561    case *ast.FuncLit:
562        return "function literal"
563    case *ast.FuncType:
564        return "function type"
565    case *ast.GenDecl:
566        switch n.Tok {
567        case token.IMPORT:
568            return "import declaration"
569        case token.CONST:
570            return "constant declaration"
571        case token.TYPE:
572            return "type declaration"
573        case token.VAR:
574            return "variable declaration"
575        }
576    case *ast.GoStmt:
577        return "go statement"
578    case *ast.Ident:
579        return "identifier"
580    case *ast.IfStmt:
581        return "if statement"
582    case *ast.ImportSpec:
583        return "import specification"
584    case *ast.IncDecStmt:
585        if n.Tok == token.INC {
586            return "increment statement"
587        }
588        return "decrement statement"
589    case *ast.IndexExpr:
590        return "index expression"
591    case *typeparams.IndexListExpr:
592        return "index list expression"
593    case *ast.InterfaceType:
594        return "interface type"
595    case *ast.KeyValueExpr:
596        return "key/value association"
597    case *ast.LabeledStmt:
598        return "statement label"
599    case *ast.MapType:
600        return "map type"
601    case *ast.Package:
602        return "package"
603    case *ast.ParenExpr:
604        return "parenthesized " + NodeDescription(n.X)
605    case *ast.RangeStmt:
606        return "range loop"
607    case *ast.ReturnStmt:
608        return "return statement"
609    case *ast.SelectStmt:
610        return "select statement"
611    case *ast.SelectorExpr:
612        return "selector"
613    case *ast.SendStmt:
614        return "channel send"
615    case *ast.SliceExpr:
616        return "slice expression"
617    case *ast.StarExpr:
618        return "*-operation" // load/store expr or pointer type
619    case *ast.StructType:
620        return "struct type"
621    case *ast.SwitchStmt:
622        return "switch statement"
623    case *ast.TypeAssertExpr:
624        return "type assertion"
625    case *ast.TypeSpec:
626        return "type specification"
627    case *ast.TypeSwitchStmt:
628        return "type switch"
629    case *ast.UnaryExpr:
630        return fmt.Sprintf("unary %s operation"n.Op)
631    case *ast.ValueSpec:
632        return "value specification"
633
634    }
635    panic(fmt.Sprintf("unexpected node type: %T"n))
636}
637
MembersX
PathEnclosingInterval
childrenOf
byPos.Swap
byPos.Swap.i
fmt
token
typeparams
tokenNode.pos
tok
childrenOf.children
PathEnclosingInterval.start
PathEnclosingInterval.BlockStmt.i
tokenNode.Pos
byPos.Less.i
ast
PathEnclosingInterval.exact
PathEnclosingInterval.BlockStmt.RangeStmt_2861.BlockStmt.childPos
PathEnclosingInterval.BlockStmt.RangeStmt_2861.BlockStmt.augEnd
tokenNode.End
byPos
byPos.Less.j
PathEnclosingInterval.path
PathEnclosingInterval.BlockStmt.nodeEnd
PathEnclosingInterval.BlockStmt.RangeStmt_2861.BlockStmt.augPos
tokenNode.Pos.n
tok.pos
byPos.Len
byPos.Swap.sl
byPos.Swap.j
PathEnclosingInterval.root
PathEnclosingInterval.end
PathEnclosingInterval.BlockStmt.RangeStmt_2861.i
PathEnclosingInterval.BlockStmt.RangeStmt_2861.BlockStmt.BlockStmt.nextChildPos
tokenNode.end
childrenOf.n
childrenOf.BlockStmt.tparams
byPos.Less.sl
sort
PathEnclosingInterval.BlockStmt.nodePos
PathEnclosingInterval.BlockStmt.l
PathEnclosingInterval.BlockStmt.RangeStmt_2861.BlockStmt.childEnd
tokenNode.End.n
tok.len
byPos.Len.sl
NodeDescription
PathEnclosingInterval.BlockStmt.children
PathEnclosingInterval.BlockStmt.RangeStmt_2861.child
tokenNode
byPos.Less
NodeDescription.n
Members
X