diff --git a/src/libfuturize/fixer_util.py b/src/libfuturize/fixer_util.py index 28ec76a0..48e4689d 100644 --- a/src/libfuturize/fixer_util.py +++ b/src/libfuturize/fixer_util.py @@ -507,13 +507,14 @@ def wrap_in_fn_call(fn_name, args, prefix=None): >>> wrap_in_fn_call("olddiv", (arg1, arg2)) olddiv(arg1, arg2) + + >>> wrap_in_fn_call("olddiv", [arg1, comma, arg2, comma, arg3]) + olddiv(arg1, arg2, arg3) """ assert len(args) > 0 - if len(args) == 1: - newargs = args - elif len(args) == 2: + if len(args) == 2: expr1, expr2 = args newargs = [expr1, Comma(), expr2] else: - assert NotImplementedError('write me') + newargs = args return Call(Name(fn_name), newargs, prefix=prefix) diff --git a/src/libfuturize/fixes/fix_division_safe.py b/src/libfuturize/fixes/fix_division_safe.py index a3adfa67..c057c124 100644 --- a/src/libfuturize/fixes/fix_division_safe.py +++ b/src/libfuturize/fixes/fix_division_safe.py @@ -15,7 +15,7 @@ import re import lib2to3.pytree as pytree -from lib2to3.fixer_util import Leaf, Node +from lib2to3.fixer_util import Leaf, Node, Comma from lib2to3 import fixer_base from lib2to3.fixer_util import syms, does_tree_import from libfuturize.fixer_util import (token, future_import, touch_import_top, @@ -33,8 +33,14 @@ def match_division(node): const_re = re.compile('^[0-9]*[.][0-9]*$') +def is_floaty(node, div_idx): + return _is_floaty(node.children[0:div_idx]) or _is_floaty(node.children[div_idx+1:]) + def _is_floaty(expr): + if isinstance(expr, list): + expr = expr[0] + if isinstance(expr, Leaf): # If it's a leaf, let's see if it's a numeric constant containing a '.' return const_re.match(expr.value) @@ -44,6 +50,24 @@ def _is_floaty(expr): return expr.children[0].value == u'float' return False +def find_division(node): + for i, child in enumerate(node.children): + if match_division(child): + return i + return False + +def clone_div_operands(node, div_idx): + children = [] + for i, child in enumerate(node.children): + if i == div_idx: + children.append(Comma()) + else: + children.append(child.clone()) + + # Strip any leading space for the first number: + children[0].prefix = u'' + + return children class FixDivisionSafe(fixer_base.BaseFix): # BM_compatible = True @@ -60,33 +84,26 @@ def start_tree(self, tree, name): Skip this fixer if "__future__.division" is already imported. """ super(FixDivisionSafe, self).start_tree(tree, name) - self.skip = "division" in tree.future_features + self.skip = "division" not in tree.future_features def match(self, node): u""" Since the tree needs to be fixed once and only once if and only if it matches, we can start discarding matches after the first. """ - if (node.type == self.syms.term and - len(node.children) == 3 and - match_division(node.children[1])): - expr1, expr2 = node.children[0], node.children[2] - return expr1, expr2 - else: - return False + if node.type == self.syms.term: + div_idx = find_division(node) + if div_idx is not False: + # if expr1 or expr2 are obviously floats, we don't need to wrap in + # old_div, as the behavior of division between any number and a float + # should be the same in 2 or 3 + if not is_floaty(node, div_idx): + return clone_div_operands(node, div_idx) + return False def transform(self, node, results): if self.skip: return future_import(u"division", node) - - expr1, expr2 = results[0].clone(), results[1].clone() - # Strip any leading space for the first number: - expr1.prefix = u'' - # if expr1 or expr2 are obviously floats, we don't need to wrap in - # old_div, as the behavior of division between any number and a float - # should be the same in 2 or 3 - if _is_floaty(expr1) or _is_floaty(expr2): - return touch_import_top(u'past.utils', u'old_div', node) - return wrap_in_fn_call("old_div", (expr1, expr2), prefix=node.prefix) + return wrap_in_fn_call("old_div", results, prefix=node.prefix) diff --git a/tests/test_future/test_futurize.py b/tests/test_future/test_futurize.py index 49d210f5..4bdb05ba 100644 --- a/tests/test_future/test_futurize.py +++ b/tests/test_future/test_futurize.py @@ -1181,18 +1181,50 @@ def test_safe_division(self): after futurization. """ before = """ + import random x = 3 / 2 y = 3. / 2 + foo = range(100) assert x == 1 and isinstance(x, int) assert y == 1.5 and isinstance(y, float) + a = 1 + foo[len(foo) / 2] + b = 1 + foo[len(foo) * 3 / 4] + assert a == 50 + assert b == 75 + r = random.randint(0, 1000) * 1.0 / 1000 + output = { "SUCCESS": 5, "TOTAL": 10 } + output["SUCCESS"] * 100 / output["TOTAL"] + obj = foo + val = float(obj.numer) / obj.denom * 1e-9 + mount.bytes_free * mount.free_size / bytes_in_gb + obj.total_count() * threshold / 100 + 100 * abs(obj.width - original_width) / float(max(obj.width, original_width)) + 100 * abs(obj.width - original_width) / max(obj.width, original_width) + float(target_width) * float(original_height) / float(original_width) """ after = """ from __future__ import division from past.utils import old_div + import random x = old_div(3, 2) y = 3. / 2 + foo = range(100) assert x == 1 and isinstance(x, int) assert y == 1.5 and isinstance(y, float) + a = 1 + foo[old_div(len(foo), 2)] + b = 1 + foo[old_div(len(foo) * 3, 4)] + assert a == 50 + assert b == 75 + r = old_div(random.randint(0, 1000) * 1.0, 1000) + output = { "SUCCESS": 5, "TOTAL": 10 } + output["SUCCESS"] * 100 / output["TOTAL"] + obj = foo + val = float(obj.numer) / obj.denom * 1e-9 + old_div(mount.bytes_free * mount.free_size, bytes_in_gb) + old_div(obj.total_count() * threshold, 100) + 100 * abs(obj.width - original_width) / float(max(obj.width, original_width)) + 100 * abs(obj.width - original_width), max(obj.width, original_width)) + float(target_width) * float(original_height) / float(original_width) """ self.convert_check(before, after)