code: func (g *Graph) AddEdge(parent, child *Key, title string, trace *builtins.CapturedStacktrace) error {
  	if g.finalized {
  		return ErrFinalized
  	}
  	if err := g.validateKey("parent", parent); err != nil {
  		return err
  	}
  	if err := g.validateKey("child", child); err != nil {
  		return err
  	}
  
  	edge := &Edge{
  		Parent: g.initNode(parent),
  		Child:  g.initNode(child),
  		Title:  title,
  		Trace:  trace,
  	}
  
  	// Have this exact edge already? This is fine.
  	for _, e := range edge.Parent.children {
  		if e.Child == edge.Child && e.Title == title {
  			return nil
  		}
  	}
  
  	// The child may have the parent among its descendants already? Then adding
  	// the edge would introduce a cycle.
  	err := edge.Child.visitDescendants(nil, func(n *Node, path []*Edge) error {
  		if n == edge.Parent {
  			return &CycleError{
  				Trace: trace,
  				Edge:  edge,
  				Path:  append([]*Edge(nil), path...),
  			}
  		}
  		return nil
  	})
  	if err != nil {
  		return err
  	}
  
  	edge.Parent.children = append(edge.Parent.children, edge)
  	edge.Child.parents = append(edge.Child.parents, edge)
  	g.edges = append(g.edges, edge)
  	return nil
  }
docs: // AddEdge adds an edge to the graph.
  //
  // Trying to use AddEdge after the graph has been finalized is an error.
  //
  // Neither of the nodes have to exist yet: it is OK to declare nodes and edges
  // in arbitrary order as long as at the end of the graph construction (when it
  // is finalized) the graph is complete.
  //
  // It is OK to add the same edge (with the same title) more than once. Only
  // the trace of the first definition is recorded.
  //
  // Returns an error if the new edge introduces a cycle.