...

Source file src/golang.org/x/tools/go/ast/inspector/inspector.go

Documentation: golang.org/x/tools/go/ast/inspector

     1  // Copyright 2018 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  
     5  // Package inspector provides helper functions for traversal over the
     6  // syntax trees of a package, including node filtering by type, and
     7  // materialization of the traversal stack.
     8  //
     9  // During construction, the inspector does a complete traversal and
    10  // builds a list of push/pop events and their node type. Subsequent
    11  // method calls that request a traversal scan this list, rather than walk
    12  // the AST, and perform type filtering using efficient bit sets.
    13  //
    14  // Experiments suggest the inspector's traversals are about 2.5x faster
    15  // than ast.Inspect, but it may take around 5 traversals for this
    16  // benefit to amortize the inspector's construction cost.
    17  // If efficiency is the primary concern, do not use Inspector for
    18  // one-off traversals.
    19  package inspector
    20  
    21  // There are four orthogonal features in a traversal:
    22  //  1 type filtering
    23  //  2 pruning
    24  //  3 postorder calls to f
    25  //  4 stack
    26  // Rather than offer all of them in the API,
    27  // only a few combinations are exposed:
    28  // - Preorder is the fastest and has fewest features,
    29  //   but is the most commonly needed traversal.
    30  // - Nodes and WithStack both provide pruning and postorder calls,
    31  //   even though few clients need it, because supporting two versions
    32  //   is not justified.
    33  // More combinations could be supported by expressing them as
    34  // wrappers around a more generic traversal, but this was measured
    35  // and found to degrade performance significantly (30%).
    36  
    37  import (
    38  	"go/ast"
    39  )
    40  
    41  // An Inspector provides methods for inspecting
    42  // (traversing) the syntax trees of a package.
    43  type Inspector struct {
    44  	events []event
    45  }
    46  
    47  // New returns an Inspector for the specified syntax trees.
    48  func New(files []*ast.File) *Inspector {
    49  	return &Inspector{traverse(files)}
    50  }
    51  
    52  // An event represents a push or a pop
    53  // of an ast.Node during a traversal.
    54  type event struct {
    55  	node  ast.Node
    56  	typ   uint64 // typeOf(node)
    57  	index int    // 1 + index of corresponding pop event, or 0 if this is a pop
    58  }
    59  
    60  // Preorder visits all the nodes of the files supplied to New in
    61  // depth-first order. It calls f(n) for each node n before it visits
    62  // n's children.
    63  //
    64  // The types argument, if non-empty, enables type-based filtering of
    65  // events. The function f if is called only for nodes whose type
    66  // matches an element of the types slice.
    67  func (in *Inspector) Preorder(types []ast.Node, f func(ast.Node)) {
    68  	// Because it avoids postorder calls to f, and the pruning
    69  	// check, Preorder is almost twice as fast as Nodes. The two
    70  	// features seem to contribute similar slowdowns (~1.4x each).
    71  
    72  	mask := maskOf(types)
    73  	for i := 0; i < len(in.events); {
    74  		ev := in.events[i]
    75  		if ev.typ&mask != 0 {
    76  			if ev.index > 0 {
    77  				f(ev.node)
    78  			}
    79  		}
    80  		i++
    81  	}
    82  }
    83  
    84  // Nodes visits the nodes of the files supplied to New in depth-first
    85  // order. It calls f(n, true) for each node n before it visits n's
    86  // children. If f returns true, Nodes invokes f recursively for each
    87  // of the non-nil children of the node, followed by a call of
    88  // f(n, false).
    89  //
    90  // The types argument, if non-empty, enables type-based filtering of
    91  // events. The function f if is called only for nodes whose type
    92  // matches an element of the types slice.
    93  func (in *Inspector) Nodes(types []ast.Node, f func(n ast.Node, push bool) (proceed bool)) {
    94  	mask := maskOf(types)
    95  	for i := 0; i < len(in.events); {
    96  		ev := in.events[i]
    97  		if ev.typ&mask != 0 {
    98  			if ev.index > 0 {
    99  				// push
   100  				if !f(ev.node, true) {
   101  					i = ev.index // jump to corresponding pop + 1
   102  					continue
   103  				}
   104  			} else {
   105  				// pop
   106  				f(ev.node, false)
   107  			}
   108  		}
   109  		i++
   110  	}
   111  }
   112  
   113  // WithStack visits nodes in a similar manner to Nodes, but it
   114  // supplies each call to f an additional argument, the current
   115  // traversal stack. The stack's first element is the outermost node,
   116  // an *ast.File; its last is the innermost, n.
   117  func (in *Inspector) WithStack(types []ast.Node, f func(n ast.Node, push bool, stack []ast.Node) (proceed bool)) {
   118  	mask := maskOf(types)
   119  	var stack []ast.Node
   120  	for i := 0; i < len(in.events); {
   121  		ev := in.events[i]
   122  		if ev.index > 0 {
   123  			// push
   124  			stack = append(stack, ev.node)
   125  			if ev.typ&mask != 0 {
   126  				if !f(ev.node, true, stack) {
   127  					i = ev.index
   128  					stack = stack[:len(stack)-1]
   129  					continue
   130  				}
   131  			}
   132  		} else {
   133  			// pop
   134  			if ev.typ&mask != 0 {
   135  				f(ev.node, false, stack)
   136  			}
   137  			stack = stack[:len(stack)-1]
   138  		}
   139  		i++
   140  	}
   141  }
   142  
   143  // traverse builds the table of events representing a traversal.
   144  func traverse(files []*ast.File) []event {
   145  	// Preallocate approximate number of events
   146  	// based on source file extent.
   147  	// This makes traverse faster by 4x (!).
   148  	var extent int
   149  	for _, f := range files {
   150  		extent += int(f.End() - f.Pos())
   151  	}
   152  	// This estimate is based on the net/http package.
   153  	capacity := extent * 33 / 100
   154  	if capacity > 1e6 {
   155  		capacity = 1e6 // impose some reasonable maximum
   156  	}
   157  	events := make([]event, 0, capacity)
   158  
   159  	var stack []event
   160  	for _, f := range files {
   161  		ast.Inspect(f, func(n ast.Node) bool {
   162  			if n != nil {
   163  				// push
   164  				ev := event{
   165  					node:  n,
   166  					typ:   typeOf(n),
   167  					index: len(events), // push event temporarily holds own index
   168  				}
   169  				stack = append(stack, ev)
   170  				events = append(events, ev)
   171  			} else {
   172  				// pop
   173  				ev := stack[len(stack)-1]
   174  				stack = stack[:len(stack)-1]
   175  
   176  				events[ev.index].index = len(events) + 1 // make push refer to pop
   177  
   178  				ev.index = 0 // turn ev into a pop event
   179  				events = append(events, ev)
   180  			}
   181  			return true
   182  		})
   183  	}
   184  
   185  	return events
   186  }
   187  

View as plain text