code: def assignment_propagation(node):
    
    n_reads = read_counts(node)
  
    to_remove = []
    for succ in gast.walk(node):
      # We found an assignment of the form a = b
      # - Left-hand side is a Name, right-hand side is a Name.
      if (isinstance(succ, gast.Assign) and isinstance(succ.value, gast.Name) and
          len(succ.targets) == 1 and isinstance(succ.targets[0], gast.Name)):
        rhs_name = succ.value.id
        # We now find all the places that b was defined
        rhs_defs = [def_[1] for def_ in anno.getanno(succ, 'definitions_in')
                    if def_[0] == rhs_name]
        # If b was defined in only one place (not an argument), and wasn't used
        # anywhere else but in a == b, and was defined as b = x, then we can fold
        # the statements
        if (len(rhs_defs) == 1 and isinstance(rhs_defs[0], gast.Assign) and
            n_reads[rhs_defs[0]] == 1 and
            isinstance(rhs_defs[0].value, gast.Name) and
            isinstance(rhs_defs[0].targets[0], gast.Name)):
          # Mark rhs_def for deletion
          to_remove.append(rhs_defs[0])
          # Propagate the definition
          succ.value = rhs_defs[0].value
  
    # Remove the definitions we folded
    transformers.Remove(to_remove).visit(node)
    anno.clearanno(node)
    return node
docs: Perform assignment propagation.
  
    Assignment propagation is not a compiler optimization as much as a
    readability optimization. If a variable name is used only once, it gets
    renamed when possible e.g. `y = x; z = y` will become `z = x`.
  
    Args:
      node: The AST to optimize.
  
    Returns:
      The optimized AST.