Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(317)

Side by Side Diff: cmd/sockdrawer/main.go

Issue 186270043: cmd/sockdrawer: a tool for package reorganization.
Patch Set: diff -r a81b057fa6e0681031c81674e00ad6bbb9f2e18f https://code.google.com/p/go.tools Created 10 years, 4 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « cmd/sockdrawer/dot.go ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 /*
2 The sockdrawer command is an analysis and visualization tool to help
3 you reorganize a complex Go package into several simpler ones.
4
5 OVERVIEW
rsc 2014/12/12 19:53:23 Godoc will recognize plain 'Overview' as a headlin
adonovan 2014/12/12 20:13:00 Oh yeah, I discovered that recently. Fixed.
6
7 sockdrawer operates on three kinds of graphs at different levels of
8 abstraction. The lowest level is the NODE GRAPH. A node is a
9 package-level declaration of a named entity (func, var, const or type).
10
11 An entire constant declaration is treated as a single node, even if it
12 contains multiple "specs" each defining multiple names, since constants
13 so grouped are typically closely related; an important special case is
14 an enumerated set data type. Also, we treat each "spec" or a var or
15 type declaration as a single node.
16
17 func f() // a func node
18 const ( a, b = 0, 1; c = 0 ) // a single const node
19 var (
20 a, b = 0, 1 // a single var node
21 c = 0 // another var node
22 )
23 type ( x int; y int ) // a single type node
24
25 Each reference to a package-level entity E forms an edge in the node
26 graph, from the node in which it appears to the node E. For example:
27
28 var x int
29 var y = x // edge y -> x
30 func f() int { return y } // edge f -> y
31
32 Each method declaration depends on its receiver named type; in addition
33 we add an edge from each receiver type to its methods:
34
35 type T int // edge T -> T.f
36 func (T) f() // edge T.f -> T
37
38 to ensure that a type and its methods stay together.
39
40 The node graph is highly cyclic, and obviously all nodes in a cycle must
41 belong to the same package for the package import graph to remain
42 acyclic.
43
44 So, we compute the second graph, the SCNODE GRAPH. In essence, the
45 scnode graph is the graph of strongly connected components (SCCs) of the
46 (ordinary) node graph. By construction, the scnode graph is acyclic.
47
48 We optionally perform an optimization at this point, which is to fuse
49 single-predecessor scnodes with their sole predecessor, as this tends to
50 reduce clutter in big graphs. This means that the scnodes are no longer
51 true SCCs; however, the scnode graph remains acyclic.
52
53 We define a valid PARTITION P of the scnode graph as a mapping from
54 scnodes to CLUSTERS such that the projection of the scnode graph using
55 mapping P is an acyclic graph. This third graph is the CLUSTER GRAPH.
56
57 Every partition represents a valid refactoring of the original package
58 into hypothetical subpackages, each cluster being a subpackage. Two
59 partitions define the extreme ends of a spectrum: the MINIMAL partition
60 maps every scnode to a single cluster; it represents the status quo, a
61 monolithic package. The MAXIMAL partition maps each scnode to a unique
62 cluster; this breaks the package up into an impractically large number
63 of small fragments. The ideal partition lies somewhere in between.
64
65
66 CLUSTER FILE
67
68 The --clusters=<file> argument specifies a "clusters file" that
69 constrains the partition algorithm. The file consists of a number of
70 stanzas, each naming a cluster ("foo") and assigning a set of initial
71 nodes ({x, y, z}) to it:
72
73 = foo
74 x
75 y # this is a comment
76 z
77
78 Order of stanzas is important: clusters must be be declared bottom to
79 top. After each stanza, all nodes transitively reachable (via the node
80 graph) from that cluster are assigned to that cluster, if they have not
81 yet been assigned to some other cluster. Thus we need only mention the
82 root nodes of the cluster, not all its internal nodes. A warning is
83 reported if a node mentioned in a stanza already belongs to a previously
84 defined cluster.
85
86 There is an implicit cluster, "residue", that holds all remaining nodes
87 after the cluster defined by the file have been processed. Initially,
88 when the cluster file is empty, the residue cluster contains the entire
89 package. (It is logically at the top.) The task for the user is to
90 iteratively define new clusters until the residue becomes empty.
91
92
93 VISUALIZATION
94
95 When sockdrawer is run, it analyzes the source package, builds the node
96 graph and the scgraph, loads the cluster file, computes the clusters for
97 every node, and then emits SVG renderings of the three levels of graphs,
98 with nodes color codes as follows:
99
100 green = cluster (candidate subpackage)
101 pink = scnode (strong component of size > 1)
102 blue = node (func/type/var/const)
103
104 The graphs of all clusters, a DAG, has green nodes; clicking one takes
105 you to the graph over scnodes for a that cluster, also a DAG. Each pink
106 node in this graph represents a cyclical bunch of the node graph,
107 collapsed together for ease of viewing. Each blue node represents a
108 singleton SCC, a single named entity; singular SCCs are replaced by
109 their sole element for simplicity.
110
111 Clicking a pink (plural) scnode shows the cyclical portion of the node
112 graph that it represents. (If the fusion optimization was enabled, it
113 may not be fully cyclic.) All of its nodes are blue.
114
115 Clicking a blue node shows the definition of that node in godoc.
116 (The godoc server's base URL is specified by the --godoc flag.)
117
118
119 WORKFLOW
120
121 Initially, all nodes belong to the "residue" cluster. (GraphViz graph
122 rendering can be slow for the first several iterations.) The sockdrawer
123
124 The user's task when decomposing a package into clusters is to identify
125 the lowest-hanging fruit (so to speak) in the residue cluster---a
126 coherent group of related scnodes at the bottom of the graph---and to
127 "snip off" a bunch at the "stem" by appending a new stanza to the
128 cluster file, listing the roots of that bunch in the stanza, and
129 re-running the tool. Nodes may be added to an existing stanza if
130 appropriate, but if they are added to a cluster that is "too low", this
131 may create conflicts; keep an eye out for warnings.
132
133 This process continues iteratively until the residue has become empty
134 and the sets of clusters are satisfactory.
135
136 The tool prints the assignments of nodes to clusters: the "shopping
137 list" for the refactoring work. Clusters should split off into
138 subpackages in dependency order, lowest first.
139
140
141 CAVEATS
142
143 The analysis chooses a single configuration, such as linux/amd64.
144 Declarations for other configurations (e.g. windows/arm) will be absent
145 from the node graph.
146
147 There may be some excessively large SCCs in the node graph that reflect
148 a circularity in the design. For the purposes of analysis, you can
149 break them arbitrarily by commenting out some code, though more thought
150 will be required for a principled fix (e.g. dependency injection).
151
152
153 TODO
154
155 - Make pretty and stable names for anonymous nodes such as:
156 func init() {...}
157 var _ int = ...
158 Currently their names are very sensitive to lexical perturbations.
159 - Infer more constraints from co-located declarations. Most of the stuff
160 in the runtime's residue could be disposed of this way.
161 - Analyze the package's *_test.go files too. If they define an external
162 test package, we'll have to deal with two packages at once.
163 - Write tests.
164
165 */
166 package main
167
168 import (
169 "bufio"
170 "bytes"
171 "flag"
172 "fmt"
173 "go/ast"
174 "go/token"
175 "os"
176 "reflect"
177 "sort"
178 "strings"
179 "unicode"
180 "unicode/utf8"
181
182 "golang.org/x/tools/go/loader"
183 "golang.org/x/tools/go/types"
184 )
185
186 var (
187 outdir = flag.String("outdir", "out", "output directory")
188
189 collapseMethods = flag.Bool("collapse_methods", true,
190 "treat all references to methods as references to the receiver t ype")
191
192 fuse = flag.Bool("fuse", false,
193 "fuse each single-predecessor SCC with its sole predecessor")
194
195 godoc = flag.String("godoc", "http://localhost:4999",
196 "base URL for godoc server")
197
198 clusterFile = flag.String("clusters", "", "File containing cluster annot ations")
199 )
200
201 const Usage = "Usage: sockdrawer [-clusters=file] [-fuse] [-godoc URL] package"
202
203 func main() {
204 flag.Parse()
205 args := flag.Args()
206 if err := doMain(args); err != nil {
207 fmt.Fprintf(os.Stderr, "sockdrawer: %s\n", err)
208 os.Exit(1)
209 }
210 }
211
212 func doMain(args []string) error {
213 conf := loader.Config{
214 SourceImports: true,
215 }
216
217 if len(args) == 0 {
218 fmt.Fprintln(os.Stderr, Usage)
219 return nil
220 }
221
222 // Use the initial packages from the command line.
223 // TODO(adonovan): support *_test.go files too.
224 _, err := conf.FromArgs(args, false /*FIXME*/)
225 if err != nil {
226 return err
227 }
228
229 // Typecheck only the necessary function bodies.
230 // TODO(adonovan): opt: type-check only the bodies of functions
231 // with the initial packages.
232 conf.TypeCheckFuncBodies = func(p string) bool { return true }
233
234 // Load, parse and type-check the whole program.
235 iprog, err := conf.Load()
236 if err != nil {
237 return err
238 }
239
240 clusters := make(map[string]*cluster)
241
242 o := organizer{
243 fset: conf.Fset,
244 }
245 for _, info := range iprog.InitialPackages() {
246 o.doPackage(info)
247 }
248
249 // Load groups from the cluster file, if any.
250 if *clusterFile != "" {
251 byName := make(map[string]*node)
252 for _, n := range o.nodes {
253 byName[n.name()] = n
254 }
255
256 f, err := os.Open(*clusterFile)
257 if err != nil {
258 return err
259 }
260 in := bufio.NewScanner(f)
261 var linenum int
262 var c *cluster
263 for in.Scan() {
264 linenum++
265 line := strings.TrimSpace(in.Text())
266 if i := strings.IndexByte(line, '#'); i >= 0 {
267 line = strings.TrimSpace(line[:i]) // strip comm ents
268 }
269 if line == "" {
270 continue // skip blanks
271 }
272 if strings.HasPrefix(line, "= ") {
273 if c != nil {
274 c.finish()
275 }
276
277 c = &cluster{
278 id: len(clusters),
279 name: line[2:],
280 nodes: make(map[*node]bool),
281 }
282 if prev := clusters[c.name]; prev != nil {
283 fmt.Fprintf(os.Stderr, "%s:%d: duplicate cluster name: %s\n",
284 *clusterFile, linenum, c.name)
285 continue
286 }
287 clusters[c.name] = c
288 fmt.Printf("\n# cluster %s\n", c.name)
289 continue
290 }
291 if c == nil {
292 fmt.Fprintf(os.Stderr, "%s:%d: node before '= cl uster' marker\n",
293 *clusterFile, linenum)
294 continue
295 }
296
297 n := byName[line]
298 if n == nil {
299 fmt.Fprintf(os.Stderr, "%s:%d: can't find node % q\n",
300 *clusterFile, linenum, line)
301 } else if n.cluster != nil {
302 fmt.Fprintf(os.Stderr, "%s:%d: node %q appears i n clusters %q and %q\n",
303 *clusterFile, linenum, line, n.cluster.n ame, c.name)
304 } else {
305 n.cluster = c
306 fmt.Printf("\t%s\n", n)
307 c.nodes[n] = true
308 }
309 }
310 if c != nil {
311 c.finish()
312 }
313
314 // The final cluster, residue, includes all other nodes.
315 c = &cluster{
316 id: len(clusters),
317 name: "residue",
318 nodes: make(map[*node]bool),
319 }
320 fmt.Printf("\n# cluster %s\n", c.name)
321 for _, n := range o.nodes {
322 if n.cluster == nil {
323 n.cluster = c
324 fmt.Printf("\t%-50s\n", n)
325 c.nodes[n] = true
326 }
327 }
328 c.finish()
329 if len(c.nodes) > 0 {
330 clusters[c.name] = c
331 }
332
333 f.Close()
334 if err := in.Err(); err != nil {
335 return err
336 }
337 }
338
339 scgraph := o.makeSCGraph()
340
341 // dump the partition to stdout
342 for _, n := range o.nodes {
343 fmt.Printf("n%d\t%-20s s%d c%d (%s)\t\n",
344 n.id, n.String(), n.scc.id, n.cluster.id, n.cluster.name )
345 }
346
347 return renderGraphs(clusters, scgraph)
348 }
349
350 type organizer struct {
351 fset *token.FileSet
352 nodes []*node
353 clusters map[string]*cluster
354 }
355
356 // A node represents a package-level declaration.
357 // An entire const declaration is a single node.
358 // An entire var or type "spec" is a single node.
359 //
360 // Examples:
361 // func f() // FuncDecl node
362 // func init() {...} // FuncDecl node (no types.Object)
363 // type (
364 // T int // TypeSpec node
365 // U int // TypeSpec node
366 // )
367 // type T int // TypeSpec node
368 // const ( a, b = 0, 1; c = 0 ) // GenDecl(CONST) node (multiple objects )
369 // var x = f() // ValueSpec(var) node
370 // var x, y = f() // ValueSpec(var) node (multiple objects )
371 // var _ T = C(0) // ValueSpec(var) node (no object)
372 //
373 type node struct {
374 o *organizer
375 info *loader.PackageInfo
376 id int
377 syntax ast.Node // *ast.{FuncDecl,ValueSpec(VAR),TypeSpec,Ge nDecl(CONST)}
378 objects []types.Object // declared objects in lexical order; blanks omitted
379 succs, preds map[*node]bool // node graph adjacency sets
380 scc *scnode // SCC to which this node belongs
381 cluster *cluster // cluster to which this node belongs
382 }
383
384 func (n *node) name() string {
385 // Only the first object of a group (e.g. const decl, or
386 // type+methods) is used for the node label.
387 // This is deterministic.
388 if len(n.objects) > 0 {
389 return n.objects[0].Name()
390 } else {
391 var kind string
392 switch n.syntax.(type) {
393 case *ast.FuncDecl:
394 // e.g. func init()
395 kind = "func"
396 case *ast.ValueSpec:
397 // e.g. var _ int
398 kind = "var"
399 default:
400 // can't happen?
401 kind = reflect.TypeOf(n.syntax).String()
402 }
403 // We give it a stable(-ish) name so we can mention it
404 // in annotations files. (The name will change if the
405 // source is edited though.) Make the numbering tighter.
406 return fmt.Sprintf("%s$%d", kind, n.id)
407 }
408 }
409
410 func (n *node) String() string {
411 var buf bytes.Buffer
412 buf.WriteString(n.name())
413 if nobj := len(n.objects); nobj > 1 {
414 fmt.Fprintf(&buf, " + %d", nobj-1)
415 }
416 return buf.String()
417 }
418
419 func (n *node) godocURL() string {
420 posn := n.o.fset.Position(n.syntax.Pos())
421 i := strings.Index(posn.Filename, "/src/") // hack!
422
423 selLen := 1
424 switch syntax := n.syntax.(type) {
425 case *ast.FuncDecl:
426 selLen = len("func")
427 case *ast.TypeSpec:
428 selLen = len("type")
429 case *ast.GenDecl:
430 selLen = len("const")
431 case *ast.ValueSpec:
432 // For "var (...; x, y = ...)", select "x, y".
433 selLen = int(syntax.Names[len(syntax.Names)-1].End() - syntax.Na mes[0].Pos())
434 }
435 return fmt.Sprintf("%s/%s?s=%d:%d#L%d", *godoc,
436 posn.Filename[i+1:], posn.Offset, posn.Offset+selLen, posn.Line)
437 }
438
439 func (n *node) exportedness() int {
440 for _, obj := range n.objects {
441 if obj.Exported() {
442 return 1
443 }
444 }
445 return 0
446 }
447
448 func addEdge(from, to *node) {
449 if from == to {
450 return // skip self-edges
451 }
452 from.succs[to] = true
453 to.preds[from] = true
454 }
455
456 type cluster struct {
457 id int
458 name string
459 nodes map[*node]bool
460 }
461
462 func (c *cluster) finish() {
463 // annotateReachable applies n's annotation to all unannotated
464 // nodes reachable from it.
465 var annotateReachable func(n *node)
466 annotateReachable = func(n *node) {
467 for s := range n.succs {
468 if s.cluster == nil {
469 s.cluster = n.cluster
470 n.cluster.nodes[s] = true
471 fmt.Printf("\t%-50s (indirect)\n", s)
472 annotateReachable(s)
473 }
474 }
475 }
476
477 // Add synthetic edges around the ring of nodes in the cluster.
478 // The cycle will ensure all appear in the same cluster.
479 //
480 // We must do this step on the node graph to avoid making the
481 // scgraph cyclic (but this does mean that we can't blindly
482 // choose cluster by picking one name from an SCC node, due to
483 // the sole-predecessor-fusion step).
484 var first, prev *node
485 for n := range c.nodes {
486 if first == nil {
487 first = n
488 }
489 if prev != nil {
490 annotateReachable(n)
491 }
492 prev = n
493 }
494 if prev != nil {
495 annotateReachable(first)
496 }
497 }
498
499 func (o *organizer) newNode(info *loader.PackageInfo, syntax ast.Node) *node {
500 n := &node{
501 o: o,
502 info: info,
503 id: len(o.nodes),
504 syntax: syntax,
505 succs: make(map[*node]bool),
506 preds: make(map[*node]bool),
507 }
508 o.nodes = append(o.nodes, n)
509 return n
510 }
511
512 func (o *organizer) doPackage(info *loader.PackageInfo) {
513 fmt.Fprintf(os.Stderr, "\n\n\n==== %s ====\n\n\n", info.Pkg.Path())
514
515 nodesByObj := make(map[types.Object]*node) // keys are package-level obj ects
516
517 // -- Pass 1: Defs ----------------------------------------------------
518
519 // Node creation (and hence ids) are deterministic.
520
521 // findDefs visits the AST, associating every object defined at
522 // package-level (and every struct field or interface method
523 // defined at any level) with node for its enclosing top-level
524 // syntax tree.
525 findDefs := func(n *node) {
526 ast.Inspect(n.syntax, func(syntax ast.Node) bool {
527 if id, ok := syntax.(*ast.Ident); ok {
528 // Definition of package-level object,
529 // or struct field or interface method?
530 if obj := info.Info.Defs[id]; obj != nil {
531 if obj.Pkg().Scope().Lookup(obj.Name()) == obj {
532 // ok: package-level object
533 n.objects = append(n.objects, ob j)
534 } else if v, ok := obj.(*types.Var); ok && v.IsField() {
535 // ok: struct field
536 } else if _, ok := obj.(*types.Func); ok {
537 // ok: concrete method or interf ace method
538 } else {
539 return true // ignore
540 }
541 nodesByObj[obj] = n
542 }
543 }
544 return true
545 })
546 }
547
548 var methods []*ast.FuncDecl
549 for _, f := range info.Files {
550 for _, decl := range f.Decls {
551 switch decl := decl.(type) {
552 case *ast.GenDecl:
553 switch decl.Tok {
554 case token.CONST:
555 // treat decl as one node
556 findDefs(o.newNode(info, decl))
557
558 case token.VAR, token.TYPE:
559 // each spec gets its own node
560 for _, spec := range decl.Specs {
561 findDefs(o.newNode(info, spec))
562 }
563 }
564
565 case *ast.FuncDecl:
566 if decl.Recv != nil && *collapseMethods {
567 methods = append(methods, decl)
568 continue // collapsed method get no node
569 }
570 findDefs(o.newNode(info, decl))
571 }
572 }
573 }
574
575 // -- Pass 2: Refs ----------------------------------------------------
576
577 findRefs := func(n *node, syntax ast.Node) {
578 ast.Inspect(syntax, func(syntax ast.Node) bool {
579 switch syntax := syntax.(type) {
580 case *ast.SelectorExpr:
581 // Selection of struct field or interface method of
582 // package-level named type?
583 if sel, ok := info.Info.Selections[syntax]; ok & & sel.Obj().Pkg() == info.Pkg {
584 //recv := sel.Recv()
585 //fmt.Printf("%s: %v\n", o.fset.Position (syntax.Pos()), recv)
586 // obj = recvTypeName(recv)
587 // TODO(adonovan): don't we need to do s omething here?
588 // Test.
589 }
590
591 case *ast.Ident:
592 // Reference to package-level object?
593 if obj, ok := info.Info.Uses[syntax]; ok {
594 // If --collapse_methods, treat referenc es to methods
595 // as references to their receiver type name.
596 if recv := methodRecv(obj); recv != nil && *collapseMethods && !isInterface(recv) {
597 obj = recvTypeName(recv)
598 }
599
600 // Need to do the same for struct fields too!
601
602 // Only same-package references matter f or now.
603 // TODO(adonovan): handle external test packages.
604 if n2, ok := nodesByObj[obj]; ok {
605 // reference to package-level ob ject (or method)
606 addEdge(n, n2)
607 }
608 }
609
610 }
611 return true
612 })
613 }
614
615 // Concrete method declarations (collapsed with the receiver).
616 for _, decl := range methods {
617 method := info.Info.Defs[decl.Name]
618 recv := recvTypeName(methodRecv(method))
619 // Collapsed method: treat references from this method as
620 // references from its named receiver type.
621 findRefs(nodesByObj[recv], decl)
622 }
623
624 // All other declarations.
625 for _, n := range o.nodes {
626 findRefs(n, n.syntax)
627
628 if decl, ok := n.syntax.(*ast.FuncDecl); ok && decl.Recv != nil {
629 recv := recvTypeName(methodRecv(info.Info.Defs[decl.Name ]))
630
631 // Add edges from each named receiver type to its method s,
632 // so they are coupled together.
633 addEdge(nodesByObj[recv], n)
634 }
635 }
636
637 fmt.Fprintf(os.Stderr, "\t%d nodes\n", len(o.nodes))
638 }
639
640 // -- SCCs -------------------------------------------------------------
rsc 2014/12/12 19:53:23 Banners like this are discouraged. http://golang.o
adonovan 2014/12/12 20:13:00 Reduced to 4 dashes either side. :)
641
642 // An scnode is a node in the scnode graph.
643 // It is (approximately; see -fuse) an SCC of the node graph.
644 type scnode struct {
645 id int // unique id
646 nodes map[*node]bool // elements of this SCC
647 succs, preds map[*scnode]bool // scnode graph adjacency sets
648 cluster *cluster // the (sole) cluster to which this SCC be longs
649 }
650
651 const maxLines = 8 // maximum number of lines in a label
652
653 func (s *scnode) String() string {
654 var buf bytes.Buffer
655 // Order nodes by exportedness and in-degree.
656 order := make([]*node, 0, len(s.nodes))
657 for n := range s.nodes {
658 order = append(order, n)
659 }
660 sort.Sort(byExportednessAndInDegree(order))
661 for i, n := range order {
662 if i > 0 {
663 buf.WriteByte('\n')
664 }
665 if i == maxLines-1 && len(order) > maxLines {
666 fmt.Fprintf(&buf, "+ %d more", len(order)-i)
667 break
668 }
669 buf.WriteString(n.String())
670 }
671 return buf.String()
672 }
673
674 type byExportednessAndInDegree []*node
675
676 func (b byExportednessAndInDegree) Len() int { return len(b) }
677 func (b byExportednessAndInDegree) Less(i, j int) bool {
678 if r := b[i].exportedness() - b[j].exportedness(); r != 0 {
679 return r > 0
680 }
681 if r := len(b[i].preds) - len(b[j].preds); r != 0 {
682 return r > 0
683 }
684 return false
685 }
686 func (b byExportednessAndInDegree) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
687
688 func (o *organizer) makeSCGraph() map[*scnode]bool {
689 // Kosaraju's algorithm---Tarjan is overkill here.
690
691 // Forward pass.
692 S := make([]*node, 0, len(o.nodes)) // postorder stack
693 seen := make(map[*node]bool)
694 var visit func(n *node)
695 visit = func(n *node) {
696 if !seen[n] {
697 seen[n] = true
698 for s := range n.succs {
699 visit(s)
700 }
701 S = append(S, n)
702 }
703 }
704
705 for _, n := range o.nodes {
706 visit(n)
707 }
708
709 // Reverse pass.
710 var current *scnode
711 seen = make(map[*node]bool)
712 var rvisit func(d *node)
713 rvisit = func(d *node) {
714 if !seen[d] {
715 seen[d] = true
716 current.nodes[d] = true
717 d.scc = current
718 for p := range d.preds {
719 rvisit(p)
720 }
721 }
722 }
723 scnodes := make(map[*scnode]bool)
724 for len(S) > 0 {
725 top := S[len(S)-1]
726 S = S[:len(S)-1] // pop
727 if !seen[top] {
728 current = &scnode{
729 id: len(scnodes),
730 cluster: top.cluster,
731 nodes: make(map[*node]bool),
732 succs: make(map[*scnode]bool),
733 preds: make(map[*scnode]bool),
734 }
735 rvisit(top)
736 scnodes[current] = true
737 }
738 }
739
740 // Build the strong-component DAG by
741 // projecting the edges of the node graph,
742 // discarding self-edges.
743 for s := range scnodes {
744 for n := range s.nodes {
745 for pred := range n.preds {
746 if s != pred.scc {
747 s.preds[pred.scc] = true
748 }
749 }
750 for succ := range n.succs {
751 if s != succ.scc {
752 s.succs[succ.scc] = true
753 }
754 }
755 }
756 }
757
758 fmt.Fprintf(os.Stderr, "\t%d SCCs\n", len(scnodes))
759
760 if *fuse {
761 // Now fold each single-predecessor scnode into that predecessor .
762 // Iterate until a fixed point is reached.
763 //
764 // Example: a -> b -> c
765 // b -> d
766 // Becomes: ab -> c
767 // ab -> d
768 // Then: abcd
769 //
770 // Since the loop conserves predecessor count for all
771 // non-deleted scnodes, the algorithm is order-invariant.
772 for {
773 var changed bool
774 for b := range scnodes {
775 if b == nil || len(b.preds) != 1 {
776 continue
777 }
778 var a *scnode
779 for a = range b.preds {
780 }
781 // a is sole predecessor of b
782 if a.cluster != b.cluster {
783 // don't fuse SCCs belonging to differen t clusters!
784 continue
785 }
786
787 changed = true
788
789 b.preds = nil
790 delete(a.succs, b)
791
792 // a gets all b's nodes
793 for n := range b.nodes {
794 a.nodes[n] = true
795 n.scc = a
796 }
797 b.nodes = nil
798
799 // a gets all b's succs
800 for c := range b.succs {
801 a.succs[c] = true
802 c.preds[a] = true
803 delete(c.preds, b)
804 }
805 b.succs = nil
806
807 delete(scnodes, b)
808 }
809 if !changed {
810 break
811 }
812 }
813
814 fmt.Fprintf(os.Stderr, "\t%d SCCs (excluding single-predecessor ones)\n",
815 len(scnodes))
816 }
817
818 return scnodes
819 }
820
821 // -- util -------------------------------------------------------------
822
823 func recvTypeName(T types.Type) *types.TypeName {
rsc 2014/12/12 19:53:23 It is very unusual in Go to see capitalized local
adonovan 2014/12/12 20:13:00 Agreed, but T seems to have become the default nam
rsc 2014/12/12 21:00:04 I'm happy for you both but it's not idiomatic Go a
824 if ptr, ok := T.(*types.Pointer); ok {
825 T = ptr.Elem()
826 }
827 return T.(*types.Named).Obj()
828 }
829
830 // methodRecv returns the receiver type of obj,
831 // if it's a method, or nil otherwise.
832 // TODO(adonovan): move this to go/types. It gets re-invented a lot.
833 func methodRecv(obj types.Object) types.Type {
834 if obj, ok := obj.(*types.Func); ok {
835 recv := obj.Type().(*types.Signature).Recv()
836 if recv != nil {
837 return recv.Type()
838 }
839 }
840 return nil
841 }
842
843 // isInterface reports whether T's underlying type is an interface.
844 func isInterface(T types.Type) bool {
845 _, ok := T.Underlying().(*types.Interface)
846 return ok
847 }
848
849 // from go/ast
850 func IsExported(name string) bool {
851 ch, _ := utf8.DecodeRuneInString(name)
852 return unicode.IsUpper(ch)
853 }
OLDNEW
« no previous file with comments | « cmd/sockdrawer/dot.go ('k') | no next file » | no next file with comments »

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b