github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/unused/serialize.go (about)

     1  package unused
     2  
     3  import (
     4  	"fmt"
     5  	"go/token"
     6  	"os"
     7  
     8  	"golang.org/x/tools/go/types/objectpath"
     9  )
    10  
    11  type ObjectPath struct {
    12  	PkgPath string
    13  	ObjPath objectpath.Path
    14  }
    15  
    16  // XXX make sure that node 0 always exists and is always the root
    17  
    18  type SerializedGraph struct {
    19  	nodes       []Node
    20  	nodesByPath map[ObjectPath]NodeID
    21  	// XXX deduplicating on position is dubious for `switch x := foo.(type)`, where x will be declared many times for
    22  	// the different types, but all at the same position. On the other hand, merging these nodes is probably fine.
    23  	nodesByPosition map[token.Position]NodeID
    24  }
    25  
    26  func trace(f string, args ...interface{}) {
    27  	fmt.Fprintf(os.Stderr, f, args...)
    28  	fmt.Fprintln(os.Stderr)
    29  }
    30  
    31  func (g *SerializedGraph) Merge(nodes []Node) {
    32  	if g.nodesByPath == nil {
    33  		g.nodesByPath = map[ObjectPath]NodeID{}
    34  	}
    35  	if g.nodesByPosition == nil {
    36  		g.nodesByPosition = map[token.Position]NodeID{}
    37  	}
    38  	if len(g.nodes) == 0 {
    39  		// Seed nodes with a root node
    40  		g.nodes = append(g.nodes, Node{})
    41  	}
    42  	// OPT(dh): reuse storage between calls to Merge
    43  	remapping := make([]NodeID, len(nodes))
    44  
    45  	// First pass: compute remapping of IDs of to-be-merged nodes
    46  	for _, n := range nodes {
    47  		// XXX Column is never 0. it's 1 if there is no column information in the export data. which sucks, because
    48  		// objects can also genuinely be in column 1.
    49  		if n.id != 0 && n.obj.Path == (ObjectPath{}) && n.obj.Position.Column == 0 {
    50  			// If the object has no path, then it couldn't have come from export data, which means it needs to have full
    51  			// position information including a column.
    52  			panic(fmt.Sprintf("object %q has no path but also no column information", n.obj.Name))
    53  		}
    54  
    55  		if orig, ok := g.nodesByPath[n.obj.Path]; ok {
    56  			// We already have a node for this object
    57  			trace("deduplicating %d -> %d based on path %s", n.id, orig, n.obj.Path)
    58  			remapping[n.id] = orig
    59  		} else if orig, ok := g.nodesByPosition[n.obj.Position]; ok && n.obj.Position.Column != 0 {
    60  			// We already have a node for this object
    61  			trace("deduplicating %d -> %d based on position %s", n.id, orig, n.obj.Position)
    62  			remapping[n.id] = orig
    63  		} else {
    64  			// This object is new to us; change ID to avoid collision
    65  			newID := NodeID(len(g.nodes))
    66  			trace("new node, remapping %d -> %d", n.id, newID)
    67  			remapping[n.id] = newID
    68  			g.nodes = append(g.nodes, Node{
    69  				id:   newID,
    70  				obj:  n.obj,
    71  				uses: make([]NodeID, 0, len(n.uses)),
    72  				owns: make([]NodeID, 0, len(n.owns)),
    73  			})
    74  			if n.id == 0 {
    75  				// Our root uses all the roots of the subgraphs
    76  				g.nodes[0].uses = append(g.nodes[0].uses, newID)
    77  			}
    78  			if n.obj.Path != (ObjectPath{}) {
    79  				g.nodesByPath[n.obj.Path] = newID
    80  			}
    81  			if n.obj.Position.Column != 0 {
    82  				g.nodesByPosition[n.obj.Position] = newID
    83  			}
    84  		}
    85  	}
    86  
    87  	// Second step: apply remapping
    88  	for _, n := range nodes {
    89  		n.id = remapping[n.id]
    90  		for i := range n.uses {
    91  			n.uses[i] = remapping[n.uses[i]]
    92  		}
    93  		for i := range n.owns {
    94  			n.owns[i] = remapping[n.owns[i]]
    95  		}
    96  		g.nodes[n.id].uses = append(g.nodes[n.id].uses, n.uses...)
    97  		g.nodes[n.id].owns = append(g.nodes[n.id].owns, n.owns...)
    98  	}
    99  }