def dependency_sort(assignments):
    """ We define a dependency sort as a Tarjan strongly-connected
    components resolution. Tarjan's algorithm happens to topologically
    sort as a by-product of finding strongly-connected components.

    Use this in code generators to sort assignments in dependency order, if
    there are circular dependencies. It is slower than ``topological_sort``.

    In the sema model, each node depends on types mentioned in its
    ``descendants``. The model is nominally a tree, except ``descendants``
    can contain node references forming a cycle.

    Returns a list of tuples, where each item represents a component
    in the graph. Ideally they're one-tuples, but if the graph has cycles
    items can have any number of elements constituting a cycle.

    This is nice, because the output is in perfect dependency order,
    except for the cycle components, where there is no order. They
    can be detected on the basis of their plurality and handled
    separately.
    """
    # Build reverse-lookup table from name -> node.
    assignments_by_name = {a.reference_name(): a for a in assignments}

    # Build the dependency graph.
    graph = {}
    for assignment in assignments:
        references = assignment.references()
        graph[assignment] = [assignments_by_name[r] for r in references
                             if r in assignments_by_name]

    # Now let Tarjan do its work! Adapted from here:
    # http://www.logarithmic.net/pfh-files/blog/01208083168/tarjan.py
    index_counter = [0]
    stack = []
    lowlinks = {}
    index = {}
    result = []

    def strongconnect(node):
        # Set the depth index for this node to the smallest unused index
        index[node] = index_counter[0]
        lowlinks[node] = index_counter[0]
        index_counter[0] += 1
        stack.append(node)

        # Consider successors of `node`
        successors = graph.get(node, [])
        for successor in successors:
            if successor not in lowlinks:
                # Successor has not yet been visited; recurse on it
                strongconnect(successor)
                lowlinks[node] = min(lowlinks[node], lowlinks[successor])
            elif successor in stack:
                # the successor is in the stack and hence in the current
                # strongly connected component (SCC)
                lowlinks[node] = min(lowlinks[node], index[successor])

        # If `node` is a root node, pop the stack and generate an SCC
        if lowlinks[node] == index[node]:
            connected_component = []

            while True:
                successor = stack.pop()
                connected_component.append(successor)
                if successor == node: break

            component = tuple(connected_component)
            result.append(component)

    for node in sorted(graph.keys(), key=lambda a: a.reference_name()):
        if node not in lowlinks:
            strongconnect(node)

    return result