import ast
import operator
import sys

from astoptimizer import UNSET
from astoptimizer.ast_tools import (
    copy_lineno,
    new_constant, new_literal, new_call, new_pass,
    sort_set_elts, new_tuple, new_tuple_elts, new_list, new_list_elts,
    new_dict_elts,
    ast_contains, check_func_args,
    clone_node_list, is_empty_body)
from astoptimizer.config import optimize_unicode
from astoptimizer.compatibility import (
    u,
    PYTHON2, PYTHON27, PYTHON3, PYTHON33, PYTHON34,
    is_bytes_ascii, is_singleton,
    INT_TYPES, FLOAT_TYPES, COMPLEX_TYPES, NONE_TYPE,
    BYTES_TYPE, UNICODE_TYPE, STR_TYPES,
    IMMUTABLE_ITERABLE_TYPES, ITERABLE_TYPES)
if PYTHON27:
    from astoptimizer.ast_tools import new_set, new_set_elts

DROP_NODE = object()

def operator_not_in(data, item):
    return item not in data

EVAL_COMPARE = {
    # a == b, a != b
    ast.Eq: operator.eq,
    ast.NotEq: operator.ne,
    # a < b, a <= b
    ast.Lt: operator.lt,
    ast.LtE: operator.le,
    # a > b, a >= b
    ast.Gt: operator.gt,
    ast.GtE: operator.ge,
    # a in b, a not in b
    ast.In: operator.contains,
    ast.NotIn: operator_not_in,
    # a is b, a is not b
    ast.Is: operator.is_,
    ast.IsNot: operator.is_not,
}

EVAL_UNARYOP = {
    # not a, ~a, +a, -a
    ast.Not: operator.not_,
    ast.Invert: operator.invert,
    ast.UAdd: operator.pos,
    ast.USub: operator.neg,
}

EVAL_BINOP = {
    # a + b, a - b, a * b
    ast.Add: operator.add,
    ast.Sub: operator.sub,
    ast.Mult: operator.mul,
    # see binop(): floordiv() may be used for int/int on Python 2
    ast.Div: operator.truediv,
    ast.FloorDiv: operator.floordiv,
    ast.Mod: operator.mod,
    # a ** b
    ast.Pow: operator.pow,
    # a << b, a >> b
    ast.LShift: operator.lshift,
    ast.RShift: operator.rshift,
    # a & b, a | b, a ^ b
    ast.BitAnd: operator.and_,
    ast.BitOr: operator.or_,
    ast.BitXor: operator.xor,
}

FLOAT_BINOPS = (
    ast.Add, ast.Sub,
    ast.Mult, ast.Div, ast.FloorDiv, ast.Mod,
    ast.Pow)

DIVIDE_BINOPS = (ast.Div, ast.FloorDiv, ast.Mod)
COMPLEX_BINOPS = (
    ast.Add, ast.Sub,
    ast.Mult, ast.Div)
if PYTHON2:
    COMPLEX_BINOPS += (ast.FloorDiv, ast.Mod)

TUPLE_BINOPS = (ast.Add,)
FROZENSET_BINOPS = (ast.BitAnd, ast.BitOr, ast.BitXor, ast.Sub)

# builtin functions accepting an iterable as input
BUILTIN_ACCEPTING_ITERABLE = ('dict', 'frozenset', 'list', 'set', 'tuple')

class Namespace:
    def __init__(self):
        self._aliases_enabled = True
        self.qualnames = {}
        self._removed_aliases = set()

        self._vars_enabled = False
        self.values = {}
        self._unassigned = set()

    @property
    def vars_enabled(self):
        return self._vars_enabled

    def enable_experimental_vars(self):
        self._vars_enabled = True

    def copy(self):
        ns = Namespace()
        ns._aliases_enabled = self._aliases_enabled
        ns.qualnames = self.qualnames.copy()
        ns._removed_aliases = self._removed_aliases

        # don't copy variables
        # ns._vars_enabled = self._vars_enabled
        # ns.values = self.values.copy()
        # ns._unassigned = self._unassigned.copy()
        return ns

    def add_alias(self, qualname, name):
        """
        Register name as an alias to qualname.

        If name is already registered, unregister the previous
        alias. If the name was already removed, don't register
        the new name.
        """
        if name in self._removed_aliases:
            return
        if name in self.qualnames:
            if self.qualnames[name] == qualname:
                return
            self._removed_aliases.add(name)
            del self.qualnames[name]
            return
        self.qualnames[name] = qualname

    def disable_aliases(self):
        self._aliases_enabled = False
        self.qualnames.clear()

    def _get_qualname(self, name):
        if name in self.qualnames:
            return self.qualnames[name]
        fullname = name
        prefixes = []
        while '.' in name:
            prefix, name = name.split(".", 1)
            prefixes.append(prefix)
            key = '.'.join(prefixes)
            if key in self._unassigned:
                break
            if key in self.qualnames:
                return self.qualnames[key] + '.' + name
        if self._aliases_enabled:
            return fullname
        else:
            return UNSET

    def is_builtin_shadowed(self, name):
        if name in self.qualnames:
            return True
        if not self._aliases_enabled:
            return True
        if name in self._unassigned:
            return True
        if name in self.values:
            return True
        return False

    def get_qualname(self, name):
        qualname = self._get_qualname(name)
        if qualname is UNSET:
            return UNSET
        if qualname not in self._unassigned:
            return qualname
        else:
            return UNSET

    def assign(self, qualname, value):
        if not self._vars_enabled:
            return
        if qualname in self._unassigned:
            return
        self.values[qualname] = value

    def unassign(self, qualname):
        self._unassigned.add(qualname)
        self.values.pop(qualname, None)

    def disable_vars(self):
        self._vars_enabled = False
        self.values.clear()

    def get_var(self, qualname):
        if self._vars_enabled:
            return self.values.get(qualname, UNSET)
        else:
            return UNSET

def remove_duplicate_pass(node_list):
    # Remove duplicate pass instructions
    index = 1
    while index < len(node_list):
        if isinstance(node_list[index], ast.Pass):
            del node_list[index]
        else:
            index += 1
    if (len(node_list) > 1
    and isinstance(node_list[0], ast.Pass)):
        del node_list[0]

class Optimizer(ast.NodeTransformer):
    def __init__(self, config):
        ast.NodeTransformer.__init__(self)
        self.config = config
        self.namespace = None
        self.root = None
        self.is_conditional = None
        self.filename = None

    def _optimize(self, tree, namespace):
        self.namespace = namespace
        if self.config.use_experimental_vars:
            self.namespace.enable_experimental_vars()
        self.root = tree
        self.is_conditional = False
        return self.visit(tree)

    def optimize(self, tree, filename):
        namespace = Namespace()
        self.filename = filename
        return self._optimize(tree, namespace)

    def info(self, message):
        self.config.info_func(message)

    def log_node_removal(self, node, message="Remove dead code"):
        if self.filename:
            filename = self.filename
        else:
            filename = "<string>"
        code = ast.dump(node)
        if len(code) > 100:
            code = code[:100] + "..."
        self.info("%s:%s: %s: %s"
                  % (filename, node.lineno, message, code))

    def warning(self, message):
        self.config.warning_func(message)

    def load_name(self, name):
        qualname = self.namespace.get_qualname(name)
        if qualname is UNSET:
            return UNSET
        value = self.namespace.get_var(qualname)
        if value is not UNSET:
            return value
        constant = self.config.get_constant(qualname)
        if constant is not UNSET:
            return constant
        return UNSET

    def check_func(self, node, name, min_narg=None, max_narg=None):
        if not isinstance(node, ast.Call):
            return False
        if not isinstance(node.func, ast.Name):
            return False
        qualname = self.namespace.get_qualname(node.func.id)
        if qualname is UNSET:
            return False
        if isinstance(name, str):
            if qualname != name:
                return False
        else:
            if qualname not in name:
                return False
        return check_func_args(node, min_narg, max_narg)

    def check_builtin_func(self, node, name, min_narg, max_narg):
        if 'builtin_funcs' not in self.config.features:
            return False
        return self.check_func(node, name, min_narg, max_narg)

    def get_constant(self, node, to_type=None, max_length=None):
        if node is None:
            constant = None
        elif isinstance(node, ast.Num):
            constant = node.n
        elif isinstance(node, ast.Str):
            constant = node.s
            if (isinstance(constant, UNICODE_TYPE)
            and not optimize_unicode(self.config, constant)):
                return UNSET
        elif PYTHON3 and isinstance(node, ast.Bytes):
            constant = node.s
        elif isinstance(node, ast.Name):
            constant = self.load_name(node.id)
        elif PYTHON34 and isinstance(node, ast.NameConstant):
            constant = node.value
        elif isinstance(node, ast.Tuple):
            if (to_type is not None
            and not issubclass(tuple, to_type)):
                return UNSET
            elts = node.elts
            if max_length is None:
                max_length = self.config.max_tuple_length
            if len(elts) > max_length:
                return UNSET
            constants = []
            for elt in elts:
                constant = self.get_constant(elt)
                if constant is UNSET:
                    return UNSET
                constants.append(constant)
            return tuple(constants)
        elif self.check_builtin_func(node, 'frozenset', 0, 1):
            if (to_type is not None
            and not issubclass(frozenset, to_type)):
                return UNSET
            if len(node.args) == 1:
                arg = self.get_literal(node.args[0], ITERABLE_TYPES)
                if arg is UNSET:
                    return UNSET
                if len(arg) > self.config.max_tuple_length:
                    return UNSET
                return frozenset(arg)

            # else: len(node.args) == 0:
            return frozenset()
        else:
            return UNSET

        if (to_type is not None
        and not isinstance(constant, to_type)):
            return UNSET
        return constant

    def get_literal(self, node, to_type=None, check_length=True):
        if isinstance(node, ast.List):
            if (to_type is not None
            and not issubclass(list, to_type)):
                return UNSET
            if (check_length
            and len(node.elts) > self.config.max_tuple_length):
                return UNSET
            result = []
            for elt in node.elts:
                literal = self.get_literal(elt)
                if literal is UNSET:
                    return UNSET
                result.append(literal)
            return result
        elif PYTHON27 and isinstance(node, ast.Set):
            if (to_type is not None
            and not issubclass(set, to_type)):
                return UNSET
            if (check_length
            and len(node.elts) > self.config.max_tuple_length):
                return UNSET
            result = set()
            for elt in node.elts:
                literal = self.get_literal(elt)
                if literal is UNSET:
                    return UNSET
                result.add(literal)
            return result
        else:
            return self.get_constant(node, to_type)

    def get_constant_list(self, nodes, to_type=None):
        constants = []
        for node in nodes:
            constant = self.get_constant(node, to_type)
            if constant is UNSET:
                return UNSET
            constants.append(constant)
        return constants

    def can_drop(self, node_list):
        if PYTHON3:
            return not ast_contains(node_list, (ast.Global, ast.Nonlocal))
        else:
            return not ast_contains(node_list, ast.Global)

    if PYTHON3:
        def fullvisit_Nonlocal(self, node):
            for name in node.names:
                self.namespace.unassign(name)

    def fullvisit_Global(self, node):
        for name in node.names:
            self.namespace.unassign(name)

    def remove_dead_code(self, node_list):
        # Remove dead code
        # Example: "return 1; return 2" => "return 1"
        truncate = None
        for index, node in enumerate(node_list[:-1]):
            if not isinstance(node, (ast.Return, ast.Raise)):
                continue
            if not self.can_drop(node_list[index+1:]):
                continue
            truncate = index
            break
        if truncate is None:
            return
        if truncate == len(node_list) - 1:
            return
        for node in node_list[truncate+1:]:
            self.log_node_removal(node, 'Remove unreachable code')
        del node_list[truncate+1:]

    def optimize_node_list(self, node_list):
        if not self.config.remove_dead_code:
            return
        remove_duplicate_pass(node_list)
        self.remove_dead_code(node_list)

    def generic_visit(self, node):
        for field, old_value in ast.iter_fields(node):
            old_value = getattr(node, field, None)
            if isinstance(old_value, list):
                new_values = self.visit_list(old_value)
                old_value[:] = new_values
            elif isinstance(old_value, ast.AST):
                new_node = self.visit(old_value)
                if new_node is None:
                    delattr(node, field)
                else:
                    setattr(node, field, new_node)
        return node

    def visit(self, node, conditional=None):
        old_is_conditional = self.is_conditional
        try:
            if conditional:
                self.is_conditional = True
            return self._visit(node)
        finally:
            self.is_conditional = old_is_conditional

    def _visit(self, node):
        method = 'fullvisit_' + node.__class__.__name__
        visitor = getattr(self, method, None)
        if visitor is not None:
            new_node = visitor(node)
            if new_node is not None:
                return new_node
            else:
                return node

        new_node = self.generic_visit(node)

        method = 'visit_' + node.__class__.__name__
        visitor = getattr(self, method, UNSET)
        if visitor is UNSET:
            return new_node

        optimized_node = visitor(new_node)
        if optimized_node is not None:
            return optimized_node
        else:
            return new_node

    def visit_list(self, node_list, conditional=False):
        new_node_list = []
        for node in node_list:
            new_node = self.visit(node, conditional=conditional)
            if new_node is None:
                continue
            elif isinstance(new_node, ast.AST):
                new_node_list.append(new_node)
            elif isinstance(new_node, list):
                assert all(isinstance(node, ast.AST) for node in new_node)
                new_node_list.extend(new_node)
            else:
                raise TypeError(type(new_node))
        self.optimize_node_list(new_node_list)
        return new_node_list

    def _remove_node(self, index, node):
        if not isinstance(node, ast.Expr):
            return False
        expr = node.value
        if PYTHON33:
            obj_types = (ast.Call, ast.Yield, ast.YieldFrom)
        else:
            obj_types = (ast.Call, ast.Yield)
        if isinstance(expr, obj_types):
            return False
        if self.config.remove_almost_dead_code:
            # Keep division by zero
            if (isinstance(expr, ast.BinOp)
            and isinstance(expr.op, (ast.Div, ast.FloorDiv))
            and isinstance(expr.right, ast.Num)
            and expr.right.n == 0):
                return False
        else:
            if PYTHON3:
                obj_types = (ast.Str, ast.Num, ast.Bytes)
            else:
                obj_types = (ast.Str, ast.Num)
            if not isinstance(expr, obj_types):
                return False
        if (index == 0
        and isinstance(expr, ast.Str)):
            if self.config.remove_docstring:
                # remove docstring, but don't log removal
                return (True, False)
            else:
                return False
        return (True, True)

    def visit_expr_list(self, node_list, conditional=False):
        if self.config.remove_dead_code and len(node_list):
            new_node_list = []
            for index, node in enumerate(node_list):
                remove_log = self._remove_node(index, node)
                if remove_log:
                    if remove_log[1]:
                        self.log_node_removal(node)
                else:
                    new_node_list.append(node)
            if not new_node_list:
                pass_node = ast.Pass()
                copy_lineno(node_list[0], pass_node)
                new_node_list.append(pass_node)
        else:
            new_node_list = node_list

        return self.visit_list(new_node_list, conditional)

    def visit_Name(self, node):
        constant = self.load_name(node.id)
        if constant is UNSET:
            return
        return new_constant(node, constant)

    def fullvisit_Module(self, node):
        node.body = self.visit_expr_list(node.body)

    def get_attribute_name(self, node):
        if isinstance(node.value, ast.Name):
            name = node.value.id
        elif isinstance(node.value, ast.Attribute):
            name = self.get_attribute_name(node.value)
            if name is UNSET:
                return UNSET
        else:
            return UNSET
        return "%s.%s" % (name, node.attr)

    def visit_Attribute(self, node):
        name = self.get_attribute_name(node)
        if name is UNSET:
            return
        value = self.load_name(name)
        if value is UNSET:
            return
        return new_constant(node, value)

    def check_format_args(self, format, values):
        if PYTHON2:
            if isinstance(format, BYTES_TYPE):
                if is_bytes_ascii(format):
                    return True
                # b'x\xe9=%s' % u'abc' gives a different result
                # depending on the default encoding
                return all(not isinstance(value, UNICODE_TYPE) for value in values)
            elif isinstance(format, UNICODE_TYPE):
                return all(
                    (not isinstance(value, BYTES_TYPE)) or is_bytes_ascii(value)
                    for value in values)
        else:
            # Python 3
            return all(not isinstance(value, BYTES_TYPE) for value in values)
        return True

    def string_format(self, node, format, arg):
        if isinstance(arg, tuple):
            values = arg
        else:
            values = (arg,)
        if not self.check_format_args(format, values):
            return UNSET
        try:
            result = format % arg
        except (TypeError, ValueError, UnicodeError):
            # format string or arguments are invalid
            return UNSET
        if not self.config.check_result(result):
            return UNSET
        return new_constant(node, result)

    def check_binop_cst(self, op, left, right):
        if (isinstance(left, COMPLEX_TYPES)
        and isinstance(right, COMPLEX_TYPES)):
            if (isinstance(op, DIVIDE_BINOPS)
            and not right):
                # division by zero
                return False

            if (isinstance(op, ast.Pow)
            and isinstance(left, FLOAT_TYPES)
            and isinstance(right, FLOAT_TYPES)):
                # -1.0 ** 0.5 raises a ValueError
                if right < 1.0 and right != 0.0 and left < 0:
                    return False

            if (isinstance(op, (ast.LShift, ast.RShift))
            and right < 0):
                # 1 << -3 and 1 >> -3 raise a ValueError
                return False

        if (isinstance(left, INT_TYPES)
        and isinstance(right, INT_TYPES)):
            return True

        if (isinstance(left, FLOAT_TYPES)
        and isinstance(right, FLOAT_TYPES)):
            return isinstance(op, FLOAT_BINOPS)

        if (isinstance(left, COMPLEX_TYPES)
        and isinstance(right, COMPLEX_TYPES)):
            return isinstance(op, COMPLEX_BINOPS)

        if isinstance(op, ast.Mult):
            if isinstance(right, INT_TYPES):
                # str * int
                if isinstance(left, STR_TYPES):
                    return (len(left) * right <= self.config.max_string_length)
                # tuple * int
                if isinstance(left, tuple):
                    return (len(left) * right <= self.config.max_tuple_length)

            if isinstance(left, INT_TYPES):
                # int * str
                if isinstance(right, STR_TYPES):
                    return (left * len(right) <= self.config.max_string_length)
                # int * tuple
                if isinstance(right, tuple):
                    return (left * len(right) <= self.config.max_tuple_length)

        if (isinstance(left, tuple)
        and isinstance(right, tuple)):
            return isinstance(op, TUPLE_BINOPS)

        if (isinstance(left, frozenset)
        and isinstance(right, frozenset)):
            return isinstance(op, FROZENSET_BINOPS)

        return False

    def binop(self, node, eval_binop, left, left_cst, right, right_cst):
        if (isinstance(node.op, ast.Mod)
        and isinstance(left_cst, STR_TYPES)):
            # str % args
            if PYTHON3 and isinstance(left_cst, BYTES_TYPE):
                return UNSET
            return self.string_format(node, left_cst, right_cst)

        if (PYTHON2
        and isinstance(node.op, ast.Div)
        and (isinstance(left_cst, INT_TYPES)
             or isinstance(right_cst, INT_TYPES))):
            # Don't optimize a / b on Python 2, because it depends on the
            # -Q command line option, except if pythonenv feature is enable
            if 'pythonenv' not in self.config.features:
                return UNSET
            if not sys.flags.division_new:
                eval_binop = operator.floordiv

        ok = self.check_binop_cst(node.op, left_cst, right_cst)
        if not ok:
            return UNSET

        result = eval_binop(left_cst, right_cst)
        if not self.config.check_result(result):
            return UNSET
        return new_constant(node, result)

    def visit_BinOp(self, node):
        eval_binop = EVAL_BINOP.get(node.op.__class__)
        if not eval_binop:
            return

        left = node.left
        left_cst = self.get_constant(left)
        if left_cst is UNSET:
            return

        right = node.right
        right_cst = self.get_constant(right)
        if right_cst is UNSET:
            return

        new_node = self.binop(node, eval_binop, left, left_cst, right, right_cst)
        if new_node is not UNSET:
            return new_node

    def visit_BoolOp(self, node):
        if (not hasattr(node, 'op')
        and 'cpython_tests' in self.config.features):
            # test_compile of Python 2.7 tries to compile an invalid AST tree:
            # ast.BoolOp() node without any attribute
            return
        if not isinstance(node.op, (ast.And, ast.Or)):
            return
        if len(node.values) < 2:
            return
        values = node.values
        constants = []
        for value in values:
            constant = self.get_constant(value)
            if constant is UNSET:
                break
            constants.append(constant)
        else:
            # all arguments are constant
            result = constants[0]
            if isinstance(node.op, ast.And):
                for constant in constants[1:]:
                    result = result and constant
            else:
                for constant in constants[1:]:
                    result = result or constant
            return new_constant(node, result)

        result = values
        for index, constant in enumerate(constants):
            test = constant
            if isinstance(node.op, ast.Or):
                test = not constant
            if test:
                result = values[index+1:]
            else:
                return new_constant(node, constant)
        if len(result) > 1:
            new_node = ast.BoolOp(node.op, result)
            return copy_lineno(node, new_node)
        else:
            return result[0]

    def check_unary_op(self, op, value):
        if isinstance(op, ast.Not):
            return True
        if isinstance(op, ast.Invert):
            return isinstance(value, INT_TYPES)
        if isinstance(op, (ast.UAdd, ast.USub, ast.Invert)):
            return isinstance(value, COMPLEX_TYPES)
        return True

    def compare_not(self, node, operand):
        if len(operand.ops) != 1:
            return UNSET
        op = operand.ops[0]
        if isinstance(op, ast.In):
            # Replace "not(a in b)" with "a not in b"
            operand.ops[0] = ast.NotIn()
            return operand
        elif isinstance(op, ast.Is):
            # Replace "not(a is b)" with "a is not b"
            operand.ops[0] = ast.IsNot()
            return operand
        elif isinstance(op, ast.NotIn):
            # Replace "not(a not is b)" with "a is b"
            operand.ops[0] = ast.In()
            return operand
        elif isinstance(op, ast.IsNot):
            # Replace "not(a not in b)" with "a in b"
            operand.ops[0] = ast.Is()
            return operand
        else:
            return UNSET

    def visit_UnaryOp(self, node):
        eval_unaryop = EVAL_UNARYOP.get(node.op.__class__)
        if eval_unaryop is None:
            return
        operand = node.operand
        constant = self.get_constant(operand, COMPLEX_TYPES)
        if constant is not UNSET:
            if not self.check_unary_op(node.op, constant):
                return
            result = eval_unaryop(constant)
            if not self.config.check_result(result):
                return
            return new_constant(node, result)
        elif (isinstance(node.op, ast.Not)
        and isinstance(operand, ast.Compare)):
            new_node = self.compare_not(node, operand)
            if new_node is not UNSET:
                return new_node

    def subscript_slice(self, node, value, lower, upper, step):
        lower_cst = self.get_constant(lower, INT_TYPES + (NONE_TYPE,))
        if lower_cst is UNSET:
            return UNSET
        upper_cst = self.get_constant(upper, INT_TYPES + (NONE_TYPE,))
        if upper_cst is UNSET:
            return UNSET
        step_cst = self.get_constant(step, INT_TYPES + (NONE_TYPE,))
        if step_cst is UNSET:
            return UNSET
        myslice = slice(lower_cst, upper_cst, step_cst)
        return new_constant(node, value[myslice])

    def subscript_index(self, node, value, index):
        index_constant = self.get_constant(index, INT_TYPES)
        if index_constant is UNSET:
            return UNSET
        if index_constant >= 0:
            if index_constant >= len(value):
                return UNSET
        else:
            if (len(value) + index_constant) < 0:
                return UNSET
        result = value[index_constant]
        return new_constant(node, result)

    def visit_Subscript(self, node):
        value = node.value
        value_constant = self.get_constant(value, IMMUTABLE_ITERABLE_TYPES)
        if value_constant is UNSET:
            return
        if isinstance(node.slice, ast.Slice):
            lower = node.slice.lower
            upper = node.slice.upper
            step = node.slice.step
            new_node = self.subscript_slice(node, value_constant, lower, upper, step)
            if new_node is not UNSET:
                return new_node
        elif isinstance(node.slice, ast.Index):
            index = node.slice.value
            new_node = self.subscript_index(node, value_constant, index)
            if new_node is not UNSET:
                return new_node

    def check_compare_in(self, item, data):
        if isinstance(data, (tuple, frozenset)):
            return True
        if (isinstance(data, STR_TYPES)
        and type(item) == type(data)):
            return True
        return False

    def check_compare(self, left, op, right):
        if isinstance(op, (ast.Is, ast.IsNot)):
            return (is_singleton(self.config, left)
                    and is_singleton(self.config, right))

        if (isinstance(left, COMPLEX_TYPES)
        and isinstance(right, COMPLEX_TYPES)):
            return True

        if (isinstance(left, STR_TYPES + (tuple, frozenset))
        and type(right) == type(left)):
            return True

        return False

    def compare_cst(self, node, op, left, right):
        eval_compare = EVAL_COMPARE.get(op.__class__)
        if eval_compare is None:
            return UNSET
        if isinstance(op, (ast.In, ast.NotIn)):
            if not self.check_compare_in(left, right):
                return UNSET
            result = eval_compare(right, left)
        else:
            if not self.check_compare(left, op, right):
                return UNSET
            result = eval_compare(left, right)
        if not self.config.check_result(result):
            return UNSET
        return new_constant(node, result)

    def try_compare_cst(self, node, op):
        left = node.left
        left_cst = self.get_constant(left)
        if left_cst is UNSET:
            return UNSET

        right = node.comparators[0]
        right_cst = self.get_constant(right)
        if right_cst is UNSET:
            return UNSET

        return self.compare_cst(node, op, left_cst, right_cst)

    def compare_in(self, data):
        if isinstance(data, ast.List):
            # x in [1, 2] => x in (1, 2)
            return self.node_to_type(data, tuple)

        if sys.version_info >= (3, 2):
            constant = self.get_constant(data, frozenset)
            if constant is UNSET:
                return
            # x in frozenset((1, 2)) => x in {1, 2}
            # Python 3.2+ bytecode peepholer replaces x in {1, 2} with x in
            # frozenset({1, 2}), where frozenset({1, 2}) is a constant.
            return new_set(data, constant)

    def visit_Compare(self, node):
        # FIXME: implement 1 < 2 < 3
        if len(node.ops) != 1:
            return
        if len(node.comparators) != 1:
            return
        op = node.ops[0]

        new_node = self.try_compare_cst(node, op)
        if new_node is not UNSET:
            return new_node

        data = node.comparators[0]
        if isinstance(op, (ast.In, ast.NotIn)):
            new_data = self.compare_in(data)
            if new_data is not None:
                node.comparators[0] = new_data

    def if_block(self, parent, node_list):
        # Create a pass instruction if needed.
        # Example: if 0: print("debug") => pass
        if len(node_list) == 0:
            return [new_pass(parent)]
        else:
            return node_list

    def constant_to_test(self, node, constant):
        # replace True with 1: avoid a lookup
        # replace "long string" with 1: smaller constant
        # replace [] with 0: smaller constant
        return new_constant(node, int(bool(constant)))

    def fullvisit_If(self, node):
        if (not hasattr(node, 'test')
        and 'cpython_tests' in self.config.features):
            # test_compile of Python 2.7 tries to compile an invalid AST tree:
            # ast.If() node without any attribute
            return
        node.test = self.visit(node.test)

        drop = False
        constant = self.get_constant(node.test)
        if self.config.remove_dead_code:
            if constant is not UNSET:
                if constant:
                    check_node = node.orelse
                else:
                    check_node = node.body
                drop = self.can_drop(check_node)
        else:
            if constant is not UNSET:
                node.test = self.constant_to_test(node.test, constant)

        if drop:
            if constant:
                node.body = self.visit_list(node.body, conditional=True)
                new_node = self.if_block(node, node.body)
            else:
                node.orelse = self.visit_list(node.orelse, conditional=True)
                new_node = self.if_block(node, node.orelse)
            return new_node

        node.body = self.visit_list(node.body, conditional=True)
        node.orelse = self.visit_list(node.orelse, conditional=True)

        if not node.orelse and len(node.body) == 1:
            node2 = node.body[0]
            if isinstance(node2, ast.If) and not node2.orelse :
                # if test: if test2: body
                # => if test and test2: body
                values = [node.test, node2.test]
                new_test = ast.BoolOp(ast.And(), values)
                node.test = copy_lineno(node.test, new_test)
                node.body = node2.body
                return node

    def visit_IfExp(self, node):
        # a if test else b
        constant = self.get_constant(node.test)
        if constant is UNSET:
            return
        if not self.config.remove_dead_code:
            if constant is not UNSET:
                node.test = self.constant_to_test(node.test, constant)
            return
        if constant:
            return node.body
        else:
            return node.orelse

    def fullvisit_While(self, node):
        # Call can_drop() before visit_list(node.body) because
        # FunctionOptimizer sets its seen_yield attribute
        can_drop = self.can_drop(node.body)

        # visit body and orelse before test, because test may use a variable
        # unassigned during visit of body or orelse
        node.body = self.visit_list(node.body, conditional=True)
        node.orelse = self.visit_list(node.orelse, conditional=True)
        node.test = self.visit(node.test)

        constant = self.get_constant(node.test)
        if constant is UNSET:
            return
        if not self.config.remove_dead_code:
            node.test = self.constant_to_test(node.test, constant)
            return
        if constant:
            always_true = new_constant(node.test, 1)
            node.test = always_true
            return
        # if not self.can_drop(node.body):
        if not can_drop:
            return

        # while False: ...
        return self.if_block(node, node.orelse)

    def print_func(self, node, args):
        constants = self.get_constant_list(args)
        if constants is UNSET:
            return
        if PYTHON3:
            if any(isinstance(constant, BYTES_TYPE)
                   for constant in constants):
                return
        else:
            # print(unicode) depends on the locale encoding
            if any(isinstance(constant, UNICODE_TYPE)
                   for constant in constants):
                return
        text = ' '.join(str(constant) for constant in constants)
        new_arg = new_constant(node, text)
        node.args = [new_arg]

    def call_func(self, node, name):
        if name not in self.config.functions:
            return UNSET
        func = self.config.functions[name]
        constants = self.get_constant_list(node.args)
        if constants is UNSET:
            return UNSET
        result = func.call(self.config, constants)
        if result is UNSET:
            return UNSET
        return new_constant(node, result)

    def call_method(self, node, func):
        obj_cst = self.get_constant(func.value)
        if obj_cst is UNSET:
            return UNSET
        attr = func.attr

        obj_type = type(obj_cst)
        if obj_type not in self.config.methods:
            return UNSET
        methods = self.config.methods[obj_type]
        if attr not in methods:
            return UNSET
        method = methods[attr]

        constants = self.get_constant_list(node.args)
        if constants is UNSET:
            return UNSET
        result = method.call(self.config, obj_cst, constants)
        if result is UNSET:
            return UNSET
        return new_constant(node, result)

    def is_empty_iterable(self, node):
        if isinstance(node, (ast.List, ast.Tuple)):
            return len(node.elts) == 0

        constant = self.get_literal(node, ITERABLE_TYPES)
        if constant is not UNSET:
            return len(constant) == 0

        if self.check_builtin_func(node, BUILTIN_ACCEPTING_ITERABLE, 0, 0):
            return True

        # iter(())
        if (self.check_builtin_func(node, 'iter', 1, 1)
        and isinstance(node.args[0], ast.Tuple)
        and not node.args[0].elts):
            return True

        if (isinstance(node, ast.Dict)
        and not node.keys
        and not node.values):
            return True

        if (PYTHON27
        and isinstance(node, ast.Set)
        and not node.elts):
            return True

        return False

    def call_builtin_iterable(self, node):
        qualname = self.namespace.get_qualname(node.func.id)
        if len(node.args) == 1:
            arg = node.args[0]
            if qualname == 'tuple':
                to_type = tuple
            elif qualname == 'list':
                to_type = list
            elif qualname == 'set':
                to_type = set
            elif qualname == 'frozenset':
                to_type = tuple
            elif qualname == 'dict':
                to_type = dict
            else:
                to_type = None
            new_arg = self.optimize_iter(arg, to_type)
        else:
            arg = UNSET
            new_arg = DROP_NODE
        if new_arg is DROP_NODE:
            if qualname == 'tuple':
                return new_constant(node, ())
            if qualname == 'list':
                return new_list_elts(node)
            if qualname == 'dict':
                return new_dict_elts(node)
            if (PYTHON27
            and qualname == 'set'):
                return new_set(node)
            if arg is not UNSET:
                del node.args[0]
            return

        if new_arg is not None:
            arg = new_arg

        if qualname == 'tuple':
            if isinstance(arg, ast.Tuple):
                # tuple((1, 2, 3)) => (1, 2, 3)
                return arg
        elif qualname == 'list':
            if isinstance(arg, ast.List):
                # list([1, 2, 3]) => [1, 2, 3]
                return arg
        elif qualname == 'dict':
            if isinstance(arg, ast.Dict):
                # dict({1: 2, 3: 4}) => {1: 2, 3: 4}
                return arg
        elif qualname in ('frozenset', 'set'):
            if (qualname == 'set'
            and PYTHON27
            and isinstance(node, ast.Set)):
                # set({1, 2, 3}) => {1, 2, 3}
                return arg

            constant = self.get_literal(arg, ITERABLE_TYPES, check_length=False)
            if constant is not UNSET:
                elts = frozenset(constant)
                use_literal_set = (qualname == 'set' and PYTHON27)
                if (len(elts) != len(constant)) or use_literal_set:
                    elts = sort_set_elts(elts)
                    if len(elts) <= self.config.max_tuple_length:
                        if use_literal_set:
                            # set((1, 2, 3)) => {1, 2, 3}
                            return new_set(node, elts)
                        else:
                            # frozenset("abab") => frozenset(("a", "b"))
                            arg = new_constant(arg, tuple(elts))

        node.args[0] = arg

    def call_name(self, node):
        name = self.namespace.get_qualname(node.func.id)
        if name is not UNSET:
            new_node = self.call_func(node, name)
            if new_node is not UNSET:
                return new_node

        if self.check_builtin_func(node, BUILTIN_ACCEPTING_ITERABLE,  0, 1):
            return self.call_builtin_iterable(node)

        elif (self.check_builtin_func(node, 'iter', 1, 1)
        and self.is_empty_iterable(node.args[0])):
            # iter(set()) => iter(())
            node.args[0] = new_constant(node.args[0], ())

        elif self.check_builtin_func(node, 'print', None, None):
            self.print_func(node, node.args)

        else:
            new_node = self.call_func(node, name)
            if new_node is not UNSET:
                return new_node

    def visit_Call(self, node):
        func = node.func
        if not check_func_args(node):
            return
        if isinstance(func, ast.Name):
            return self.call_name(node)
        elif isinstance(func, ast.Attribute) and isinstance(func.value, ast.Name):
            name = "%s.%s" % (func.value.id, func.attr)
            qualname = self.namespace.get_qualname(name)
            if qualname is UNSET:
                return
            new_node = self.call_func(node, qualname)
            if new_node is not UNSET:
                return new_node
        elif isinstance(func, ast.Attribute):
            new_node = self.call_method(node, func)
            if new_node is not UNSET:
                return new_node

    if PYTHON2:
        def visit_Print(self, node):
            if not node.values:
                return
            constants = self.get_constant_list(node.values)
            if constants is UNSET:
                return
            if any(isinstance(constant, UNICODE_TYPE)
                   for constant in constants):
                chunks = []
                for constant in constants:
                    if isinstance(constant, BYTES_TYPE):
                        if not is_bytes_ascii(constant):
                            return
                    else:
                        text = unicode(constant)
                    chunks.append(text)
                text = u(' ').join(chunks)
            else:
                text = ' '.join(str(constant) for constant in constants)
            text = new_constant(node, text)
            new_node = ast.Print(node.dest, [text], node.nl)
            return copy_lineno(node, new_node)

    def import_all(self, module_name):
        module = __import__(module_name, globals(), locals())
        for part in module_name.split(".")[1:]:
            module = getattr(module, part)
        if hasattr(module, '__all__'):
            all_attributes = module.__all__
        else:
            all_attributes = [name for name in dir(module)
                              if not name.startswith('_')]
        all_attributes = set(all_attributes) - set(('__doc__', '__name__', '__package__'))
        for attr in all_attributes:
            qualname = "%s.%s" % (module_name, attr)
            self.namespace.add_alias(qualname, attr)

    def visit_ImportFrom(self, node):
        # from sys import platform: sys -> sys.platform
        # from sys import platform as plat: plat -> sys.platform
        module = node.module
        names = node.names
        for alias in names:
            assert isinstance(alias, ast.alias)
            if (not hasattr(alias, 'asname')
            and 'cpython_tests' in self.config.features):
                continue
            qualname = "%s.%s" % (module, alias.name)
            if alias.asname:
                name = alias.asname
            else:
                name = alias.name
            if name == '*':
                if self.config.is_module_used(module):
                    self.import_all(module)
                else:
                    self.warning('Disable optimizations because of "from %s import *"'
                                 % module)
                    self.namespace.disable_aliases()
                    return
            else:
                self.namespace.add_alias(qualname, name)

    def visit_Import(self, node):
        # import math as M: M -> math
        for alias in node.names:
            assert isinstance(alias, ast.alias)
            if not alias.asname:
                continue
            self.namespace.add_alias(alias.name, alias.asname)
        return node

    def assign_multiple(self, names, values):
        if len(names) != len(values):
            return False
        values = self.get_constant_list(values)
        if values is UNSET:
            return False
        for name, value in zip(names, values):
            self.namespace.assign(name, value)
        return True

    def _get_assign_name(self, node):
        if isinstance(node, ast.Name):
            # var = value
            return (node.id, True)
        elif isinstance(node, ast.Attribute):
            # var.attr = value, var1.attr1.attr2 = value
            result = self._get_assign_name(node.value)
            if result is None:
                return None
            name, supported = result
            if not supported:
                return None
            # obj.attr is not supported: obj.attr can be a property, obj
            # may have a special __setattr__() method, etc.
            name = "%s.%s" % (name, node.attr)
            return (name, False)
        elif isinstance(node, ast.Subscript):
            # var[index] = value, var[a:b] = value
            result = self._get_assign_name(node.value)
            if result is None:
                return None
            name, supported = result
            if not supported:
                return None
            return (name, False)
        elif isinstance(node, ast.Call):
            # func().attr = value
            return (None, False)

        # Not supported: disable optimizations on variables
        return None

    def assign_single(self, name, supported, value):
        if name is None:
            return

        if not supported:
            self.namespace.unassign(name)
            return

        value = self.get_constant(value)
        if value is UNSET:
            self.namespace.unassign(name)
            return
        self.namespace.assign(name, value)

    def assign(self, node, target, value, assign_supported):
        result = self._get_assign_name(target)
        if result is not None:
            # x = value
            name, supported = result
            self.assign_single(name, assign_supported and supported, value)
            return

        if (not isinstance(target, ast.Tuple)
        or len(target.elts) <= 1):
            self.disable_vars(node)
            return

        names = []
        supported = assign_supported
        for elt in target.elts:
            result = self._get_assign_name(elt)
            if result is None:
                self.disable_vars(node)
                return
            name, elt_supported = result
            names.append(name)
            supported &= elt_supported

        if supported and isinstance(value, ast.Tuple):
            # "x, y = value" or "x, y = 1, 2"
            values = value.elts
            if not self.assign_multiple(names, values):
                for name in names:
                    self.namespace.unassign(name)
        else:
            # Example: a, b = func()
            for name in names:
                if name is None:
                    continue
                self.namespace.unassign(name)

    if sys.version_info >= (3, 3):
        # Python 3.3+
        def fullvisit_With(self, node):
            # with
            for item in node.items:
                # don't visit optional_vars to not replace a variable name by its
                # value

                var = item.optional_vars
                if var is not None:
                    self.unassign(var)

            for item in node.items:
                item.context_expr = self.visit(item.context_expr)
            node.body = self.visit_list(node.body)

        def fullvisit_Try(self, node):
            # try/except/else/finally
            node.body = self.visit_list(node.body, True)
            node.orelse = self.visit_list(node.orelse, True)
            node.finalbody = self.visit_list(node.finalbody, True)

            if (self.config.remove_dead_code
            and is_empty_body(node.body)):
                if is_empty_body(node.orelse):
                    # try: pass finally: code
                    # => code
                    self.log_node_removal(node)
                    return self.if_block(node, node.finalbody)
                elif is_empty_body(node.finalbody):
                    # try: pass except: ... else: code
                    # => code
                    self.log_node_removal(node)
                    return self.if_block(node, node.orelse)
                else:
                    # try: pass except: ... else: code1 finally: code2
                    # => try: code1 finally: code2
                    self.log_node_removal(node)
                    node.body = node.orelse
                    del node.handlers[:]
                    node.orelse = []
                    return

            node.handlers = self.visit_list(node.handlers, True)
    else:
        # Python 3.2 and older
        def fullvisit_With(self, node):
            # with
            var = node.optional_vars
            if var is not None:
                self.unassign(var)

            node.context_expr = self.visit(node.context_expr)
            # don't visit optional_vars to not replace a variable name by its
            # value
            node.body = self.visit_list(node.body)

        def fullvisit_TryFinally(self, node):
            # try/finally
            node.body = self.visit_list(node.body, True)
            node.finalbody = self.visit_list(node.finalbody, True)
            if (self.config.remove_dead_code
            and is_empty_body(node.body)):
                # try: pass finally: code
                # => code
                self.log_node_removal(node)
                return self.if_block(node, node.finalbody)

        def fullvisit_TryExcept(self, node):
            # try/except/else
            node.body = self.visit_list(node.body, True)
            node.orelse = self.visit_list(node.orelse, True)
            if (self.config.remove_dead_code
            and is_empty_body(node.body)):
                # try: pass except: ... else: code
                # => code
                self.log_node_removal(node)
                return self.if_block(node, node.orelse)
            node.handlers = self.visit_list(node.handlers, True)

    def fullvisit_Assign(self, node):
        supported = self.config.use_experimental_vars and not self.is_conditional
        if supported:
            node.value = self.visit(node.value)
            for target in node.targets:
                self.assign(node, target, node.value, supported)
        else:
            # FIXME: use two steps in the optimizer instead of using
            # such ugly hack (unassign before visiting the value)
            for target in node.targets:
                self.assign(node, target, None, False)
            # Visit after unassign to avoid issues like:
            # "i = 0; # for x in range(n): i = i + 1"
            # Don't replace "i = i + 1" with "i = 0 + 1"
            node.value = self.visit(node.value)

    def fullvisit_AugAssign(self, node):
        node.value = self.visit(node.value)
        self.assign(node, node.target, None, False)

    def fullvisit_Delete(self, node):
        for target in node.targets:
            self.assign(node, target, None, False)

    def disable_vars(self, node):
        if self.namespace.vars_enabled:
            self.warning('Disable optimizations on variables: %s is not supported'
                         % ast.dump(node))
        self.namespace.disable_vars()

    def unassign(self, node):
        if isinstance(node, ast.Tuple):
            for elt in node.elts:
                self.unassign(elt)
            return

        result = self._get_assign_name(node)
        if result is None:
            self.disable_vars(node)
            return
        name, supported = result
        if not supported:
            self.disable_vars(node)
            return
        self.namespace.unassign(name)

    def optimize_range(self, node, to_type):
        args = self.get_constant_list(node.args)
        if args is UNSET:
            return
        if len(args) == 1:
            start = 0
            stop = args[0]
            step = 1
        elif len(args) == 2:
            start = args[0]
            stop = args[1]
            step = 1
        elif len(args) == 3:
            start = args[0]
            stop = args[1]
            step = args[2]

        if step == 0:
            return
        if not all(isinstance(arg, INT_TYPES) for arg in args):
            return

        if PYTHON2:
            minval = self.config.min_c_long
            maxval = self.config.max_c_long
            if not all(minval <= arg <= maxval for arg in args):
                return

        if PYTHON3:
            numbers = range(*args)
        else:
            numbers = xrange(*args)
        try:
            range_len = len(numbers)
        except OverflowError:
            # OverflowError: Python int too large to convert to C ssize_t
            pass
        else:
            if (self.config.remove_dead_code
            and range_len <= self.config.max_tuple_length):
                # range(3) => (0, 1, 2)
                if to_type == list:
                    constant = list(numbers)
                elif to_type == set:
                    constant = set(numbers)
                else:
                    constant = tuple(numbers)
                return new_literal(node, constant)

        if PYTHON3:
            return
        qualname = self.namespace.get_qualname(node.func.id)
        if qualname != 'range':
            return
        if not self.can_use_builtin('xrange'):
            return

        # range(...) => xrange(...)
        node.func.id = 'xrange'
        return node

    def node_to_set(self, node):
        if not isinstance(node, (ast.Tuple, ast.List)):
            return

        # [1, 2, 3] => {1, 2, 3}
        literal = self.get_literal(node)
        if literal is UNSET:
            return
        literal = sort_set_elts(literal)
        if PYTHON27:
            return new_set(node, literal)
        else:
            return new_tuple(node, literal)

    def node_to_type(self, node, to_type):
        if PYTHON27:
            ast_types = (ast.Tuple, ast.List, ast.Dict, ast.Set)
        else:
            ast_types = (ast.Tuple, ast.List, ast.Dict)
        if not isinstance(node, ast_types):
            return
        if isinstance(node, ast.Dict):
            length = len(node.keys)
            assert len(node.keys) == len(node.values)
        else:
            length = len(node.elts)
        if length > self.config.max_tuple_length:
            return

        if isinstance(node, ast.Tuple):
            if to_type == tuple:
                # (1, 2, 3)
                return node
            if to_type == list:
                # [1, 2, 3] => (1, 2, 3)
                return new_list_elts(node, node.elts)
            if to_type == set:
                return self.node_to_set(node)
        elif isinstance(node, ast.List):
            if to_type == list:
                return node
            if to_type == tuple:
                # [1, 2, 3] => (1, 2, 3)
                return new_tuple_elts(node, node.elts)
            if to_type == set:
                return self.node_to_set(node)
        elif isinstance(node, ast.Dict):
            if to_type == dict:
                return node
            # FIXME: support other types
        elif isinstance(node, ast.Set):
            if to_type == set:
                return node
            if to_type in (tuple, list):
                literal = self.get_literal(node)
                if literal is UNSET:
                    return
                literal = sort_set_elts(literal)
                if to_type == tuple:
                    # {3, 1, 2} => (1, 2, 3)
                    return new_tuple(node, literal)
                else:
                    # {3, 1, 2} => [1, 2, 3]
                    return new_list(node, literal)

    def literal_to_type(self, node, literal, to_type):
        if not isinstance(literal, ITERABLE_TYPES):
            return UNSET

        if to_type == set:
            # "abc" => {"a", "b", "c"}
            literal = set(literal)
            if len(literal) > self.config.max_tuple_length:
                return UNSET
            if PYTHON27:
                return new_literal(node, literal)
        else:
            if len(literal) > self.config.max_tuple_length:
                return UNSET

        if isinstance(literal, (frozenset, set)):
            literal = sort_set_elts(literal)
        if to_type == list:
            # "abc" => ["a", "b", "c"]
            literal = list(literal)
        elif to_type in (tuple, set):
            # "abc" => ("a", "b", "c")
            literal = tuple(literal)
        else:
            return UNSET

        return new_literal(node, literal)

    def _optimize_iter(self, node, is_generator, to_type):
        if (self.config.remove_dead_code
        and self.is_empty_iterable(node)):
            # set("") => set()
            return DROP_NODE

        if PYTHON27:
            ast_types = (ast.Tuple, ast.List, ast.Dict, ast.Set)
        else:
            ast_types = (ast.Tuple, ast.List, ast.Dict)
        if isinstance(node, ast_types):
            return self.node_to_type(node, to_type)

        literal = self.get_literal(node, ITERABLE_TYPES)
        if literal is not UNSET:
            new_literal = self.literal_to_type(node, literal, to_type)
            if new_literal is UNSET:
                return
            return new_literal

        if isinstance(node, ast.GeneratorExp):
            # (x for x in "abc") => "abc"
            new_iter = self.optimize_comprehension(node,
                                                   is_generator=False,
                                                   to_type=to_type)
            if new_iter is not None:
                return new_iter

        if (not is_generator
        and self.config.remove_dead_code
        and self.check_builtin_func(node, ('list', 'tuple'),  1, 1)):
            # list(iterable) => iterable
            return node.args[0]

        if (to_type == set
        and self.config.remove_dead_code
        and self.check_builtin_func(node, ('frozenset', 'set'),  1, 1)):
            # set(set(iterable)) => set(iterable)
            return node.args[0]

        if (self.config.remove_dead_code
        and self.check_builtin_func(node, 'iter', 1, 1)):
            iter_arg = node.args[0]
            # no need to call is_empty_iterable(), iter(iterable) was already
            # optimized by optimize_iter()
            if (isinstance(iter_arg, ast.Tuple)
            and len(iter_arg.elts) == 0):
                # set(iter(())) => set()
                return DROP_NODE
            # set(iter(iterable)) => set(iterable)
            return iter_arg

        if PYTHON2:
            range_names = ('range', 'xrange')
        else:
            range_names = ('range',)
        if self.check_builtin_func(node, range_names, 1, 3):
            return self.optimize_range(node, to_type)

    def optimize_generator(self, node):
        return self._optimize_iter(node, True, tuple)

    def optimize_iter(self, node, to_type):
        return self._optimize_iter(node, False, to_type)

    def create_expr_list(self, node, new_node_list):
        test = ast.Num(1)
        copy_lineno(node, test)
        new_node = ast.If(test=test, body=new_node_list, orelse=[])
        copy_lineno(node, new_node)
        return new_node

    def try_unroll_loop(self, node):
        if self.config.unroll_limit <= 0:
            return UNSET

        itercst = self.get_constant(node.iter, IMMUTABLE_ITERABLE_TYPES,
                                    max_length=self.config.unroll_limit)
        if itercst is UNSET:
            return UNSET

        target = node.target
        if not isinstance(target, ast.Name):
            return UNSET
        target_id = target.id

        if (ast_contains(node.body, (ast.Break, ast.Continue))
        or ast_contains(node.orelse, (ast.Break, ast.Continue))):
            return UNSET

        # FIXME: don't unassign temporary the target?
        was_unassigned = target_id in self.namespace._unassigned
        self.namespace._unassigned.add(target_id)
        node.body = self.visit_list(node.body, conditional=True)
        node.orelse = self.visit_list(node.orelse, conditional=True)
        if not was_unassigned:
            self.namespace._unassigned.remove(target_id)

        if (self.config.remove_dead_code
        and is_empty_body(node.body)):
            self.log_node_removal(node)

            value = new_constant(node, itercst[-1])
            assign = ast.Assign(targets=[target], value=value)
            copy_lineno(node, assign)

            node_list = [assign]
            node_list.extend(node.orelse)
            return self.if_block(node, node_list)

        unroll = []
        for cst in itercst:
            value = new_constant(node, cst)
            assign = ast.Assign(targets=[target], value=value)
            copy_lineno(node.body[0], assign)
            unroll.append(assign)
            body = clone_node_list(node.body)
            unroll.extend(body)
        unroll.extend(node.orelse)

        unroll = self.visit_list(unroll)
        return self.if_block(node, unroll)

    def replace_var(self, node, name, value):
        replace = ReplaceVariable(self.config, name, value)
        return replace.optimize(node, self.filename)

    def try_unroll_listcomp(self, node):
        if self.config.unroll_limit <= 0:
            return

        # FIXME: support more than 1 generator
        if len(node.generators) != 1:
            return
        generator = node.generators[0]

        if generator.ifs:
            return

        itercst = self.get_constant(generator.iter, IMMUTABLE_ITERABLE_TYPES,
                                    max_length=self.config.unroll_limit)
        if itercst is UNSET:
            return

        target = generator.target
        if not isinstance(target, ast.Name):
            return
        target_id = target.id

        items = []
        for cst in itercst:
            item = []
            value = new_constant(node, cst)
            elt = clone_node_list(node.elt)
            elt = self.replace_var(elt, target_id, value)
            items.append(elt)

        return new_list_elts(node, items)

    def fullvisit_For(self, node):
        node.iter = self.visit(node.iter)

        new_iter = self.optimize_generator(node.iter)
        if new_iter is DROP_NODE:
            return new_pass(node)
        elif new_iter is not None:
            node.iter = new_iter

        unroll = self.try_unroll_loop(node)
        if unroll is not UNSET:
            return unroll

        self.unassign(node.target)
        node.body = self.visit_list(node.body, conditional=True)
        node.orelse = self.visit_list(node.orelse, conditional=True)
        return node

    def fullvisit_arguments(self, node):
        # Don't visit arguments
        if PYTHON3:
            for arg in node.args:
                self.namespace.unassign(arg.arg)
        else:
            for arg in node.args:
                self.unassign(arg)
        node.defaults = self.visit_list(node.defaults)

    def fullvisit_FunctionDef(self, node):
        optimizer = FunctionOptimizer(self.config)
        optimizer.filename = self.filename
        func_namespace = self.namespace.copy()
        return optimizer._optimize(node, func_namespace)

    def fullvisit_ClassDef(self, node):
        if node is self.root:
            return self.generic_visit(node)
        else:
            optimizer = Optimizer(self.config)
            optimizer.filename = self.filename
            class_namespace = self.namespace.copy()
            return optimizer._optimize(node, class_namespace)

    def fullvisit_comprehension(self, node):
        # don't visit node.target
        node.iter = self.visit(node.iter)

        new_ifs = []
        empty_gen = False
        for ifexp in node.ifs:
            constant = self.get_constant(ifexp)
            if constant is UNSET:
                new_ifs.append(ifexp)
            else:
                if self.config.remove_dead_code:
                    if not constant:
                        empty_gen = True
                        break
                else:
                    cst = self.constant_to_test(ifexp, constant)
                    new_ifs.append(cst)
        if empty_gen:
            false_cst = new_constant(ifexp, 0)
            new_ifs = [false_cst]
        node.ifs = new_ifs

    def optimize_comprehension(self, node, is_generator=False, to_type=None):
        # FIXME: support more than 1 generator
        if len(node.generators) != 1:
            return
        generator = node.generators[0]

        if generator.ifs:
            if not self.config.remove_dead_code:
                return

            # fullvisit_comprehension() already optimized ifs
            if len(generator.ifs) != 1:
                return
            test_expr = generator.ifs[0]
            constant = self.get_constant(test_expr)
            if constant is UNSET:
                return
            if constant:
                return
            # (x for x in data if False) => iter(())
            return DROP_NODE

        if to_type == dict:
            # dict comprehension
            target = generator.target
            if not (isinstance(target, ast.Tuple)
                    and len(target.elts) == 2
                    and isinstance(target.elts[0], ast.Name)
                    and isinstance(target.elts[1], ast.Name)):
                return
            key = target.elts[0].id
            value = target.elts[1].id
            if isinstance(node, ast.DictComp):
                simple_gen = (
                    isinstance(node.key, ast.Name)
                    and isinstance(node.value, ast.Name)
                    and node.key.id == key
                    and node.value.id == value)
            else:
                simple_gen = (
                    isinstance(node.elt, ast.Tuple)
                    and len(node.elt.elts) == 2
                    and isinstance(node.elt.elts[0], ast.Name)
                    and node.elt.elts[0].id == key
                    and isinstance(node.elt.elts[1], ast.Name)
                    and node.elt.elts[1].id == value)
            if not simple_gen:
                # Example: {value: key for key, value in iterable}
                if is_generator:
                    new_iter = self.optimize_generator(generator.iter)
                else:
                    new_iter = self.optimize_iter(generator.iter, tuple)
                if new_iter is DROP_NODE:
                    # y for x in ()
                    return DROP_NODE
                if new_iter is not None:
                    # y for x in [1, 2] => y for x in (1, 2)
                    generator.iter = new_iter
                return
        else:
            # generator expression, list or set comprehension:
            # to_type in (tuple, list, set)
            if not isinstance(generator.target, ast.Name):
                return
            name = generator.target.id
            if (not isinstance(node.elt, ast.Name)
            or node.elt.id != name):
                if is_generator:
                    new_iter = self.optimize_generator(generator.iter)
                else:
                    new_iter = self.optimize_iter(generator.iter, tuple)
                if new_iter is DROP_NODE:
                    # y for x in ()
                    return DROP_NODE
                if new_iter is not None:
                    # y for x in [1, 2] => y for x in (1, 2)
                    generator.iter = new_iter
                return
        iter_expr = generator.iter

        if is_generator:
            new_iter = self.optimize_generator(iter_expr)
        else:
            new_iter = self.optimize_iter(iter_expr, to_type)
        if new_iter is not None:
            return new_iter
        else:
            return iter_expr

    def can_use_builtin(self, name):
        if 'builtin_funcs' not in self.config.features:
            return False
        if self.namespace.is_builtin_shadowed(name):
            self.warning("Optimization missed "
                         "because the builtin %s is shadowed" % name)
            return False
        return True

    def visit_GeneratorExp(self, node):
        # use iter() builtin function
        iter_expr = self.optimize_comprehension(node, is_generator=True)
        if iter_expr is None:
            return

        generator = node.generators[0]
        if iter_expr is DROP_NODE:
            # (x*2 for x in "abc" if False) => (None for x in ())
            # (x*2 for x in []) => (None for x in ())
            node.elt = new_constant(node, None)
            empty_tuple = new_constant(node, ())
            generator.iter = empty_tuple
            del generator.ifs[:]
        else:
            generator.iter = iter_expr

    def fullvisit_ListComp(self, node):
        for generator in node.generators:
            self.unassign(generator.target)

        node.generators = self.visit_list(node.generators)
        node.elt = self.visit(node.elt)

        iter_expr = self.optimize_comprehension(node, to_type=list)
        if iter_expr is not None:
            if iter_expr is DROP_NODE:
                # [x*2 for x in "abc" if False] => []
                # [x*2 for x in []] => []
                return new_list_elts(node)

            if isinstance(iter_expr, ast.List):
                # [x for x in "abc"] => ["a", "b", "c"]
                return iter_expr

            if self.can_use_builtin('list'):
                # [x for x in range(1000)] => list(xrange(1000))
                return new_call(node, 'list', iter_expr)

            node.generators[0].iter = iter_expr

        return self.try_unroll_listcomp(node)

    def fullvisit_SetComp(self, node):
        for generator in node.generators:
            self.unassign(generator.target)

        node.generators = self.visit_list(node.generators)
        node.elt = self.visit(node.elt)

        iter_expr = self.optimize_comprehension(node, to_type=set)
        if iter_expr is None:
            return
        if iter_expr is DROP_NODE:
            # [x*2 for x in "abc" if False] => {}
            # [x*2 for x in []] => {}
            return new_set(node)

        if isinstance(iter_expr, ast.Set):
            # {x for x in "abc"} => {"a", "b", "c"}
            return iter_expr
        elif self.can_use_builtin('set'):
            # {x for x in range(1000)} => set(xrange(1000))
            return new_call(node, 'set', iter_expr)

    def fullvisit_DictComp(self, node):
        for generator in node.generators:
            self.unassign(generator.target)

        node.generators = self.visit_list(node.generators)
        node.key = self.visit(node.key)
        node.value = self.visit(node.value)

        iter_expr = self.optimize_comprehension(node, to_type=dict)
        if iter_expr is None:
            return
        if iter_expr is DROP_NODE:
            # [x*2 for x in "abc" if False] => {}
            # [x*2 for x in []] => {}
            return new_dict_elts(node)

        if isinstance(iter_expr, ast.Dict):
            # {x, y for x, y in {1: 2, 3: 4}} => {1: 2, 3: 4}
            return iter_expr
        elif self.can_use_builtin('dict'):
            # {x, y for x, y in iterable} => dict(iterable)
            return new_call(node, 'dict', iter_expr)


class FunctionOptimizer(Optimizer):
    def __init__(self, config):
        Optimizer.__init__(self, config)
        self.is_generator = None
        self.seen_yield = None

    def fullvisit_FunctionDef(self, node):
        if node is self.root:
            node.args = self.visit(node.args)
            node.body = self.visit_expr_list(node.body)
            return node
        else:
            return Optimizer.fullvisit_FunctionDef(self, node)

    def can_drop(self, node_list):
        if not Optimizer.can_drop(self, node_list):
            return False
        if not self.is_generator:
            return True
        # Without "yield", a function is no more a generator.
        # "return 3" is a SyntaxError in a generator.
        # http://tomlee.co/2008/04/the-internals-of-python-generator-functions-in-the-ast/
        if self.seen_yield:
            # FIXME: drop "return" but not "return 3"
            return not ast_contains(node_list, ast.Return)
        else:
            # FIXME: handle seen_yield=None case for remove_dead_code()
            return not ast_contains(node_list, (ast.Yield, ast.Return))

    def remove_dead_code(self, node_list):
        seen = self.seen_yield
        try:
            self.seen_yield = None
            Optimizer.remove_dead_code(self, node_list)
        finally:
            self.seen_yield = seen

    def visit_Yield(self, node):
        self.seen_yield = True

    def _optimize(self, tree, namespace):
        self.is_generator = ast_contains(tree, ast.Yield)
        self.seen_yield = False
        return Optimizer._optimize(self, tree, namespace)

class ReplaceVariable(Optimizer):
    def __init__(self, config, name, value):
        Optimizer.__init__(self, config)
        self.name = name
        self.value = value

    def visit_Name(self, node):
        if node.id == self.name:
            return self.value

        return Optimizer.visit_Name(self, node)

