Skip to content

Support complex statements in fix_divsion_safe #368

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 12, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions src/libfuturize/fixer_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
55 changes: 36 additions & 19 deletions src/libfuturize/fixes/fix_division_safe.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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)
32 changes: 32 additions & 0 deletions tests/test_future/test_futurize.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down