Skip to content

Commit 32d585a

Browse files
committed
Correctly fold UnaryOp nodes
1 parent f73f8e3 commit 32d585a

File tree

2 files changed

+360
-25
lines changed

2 files changed

+360
-25
lines changed

src/python_minifier/transforms/constant_folding.py

Lines changed: 59 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,36 +10,35 @@
1010
from python_minifier.util import is_constant_node
1111

1212

13-
class FoldConstants(SuiteTransformer):
14-
"""
15-
Fold Constants if it would reduce the size of the source
13+
def is_foldable_constant(node):
1614
"""
15+
Check if a node is a constant expression that can participate in folding.
1716
18-
def __init__(self):
19-
super(FoldConstants, self).__init__()
17+
We can asume that children have already been folded, so foldable constants are either:
18+
- Simple literals (Num, NameConstant)
19+
- UnaryOp(USub/Invert) on a Num - these don't fold to shorter forms,
20+
so they remain after child visiting. UAdd and Not would have been
21+
folded away since they always produce shorter results.
22+
"""
23+
if is_constant_node(node, (ast.Num, ast.NameConstant)):
24+
return True
2025

21-
def visit_BinOp(self, node):
26+
if isinstance(node, ast.UnaryOp):
27+
if isinstance(node.op, (ast.USub, ast.Invert)):
28+
return is_constant_node(node.operand, ast.Num)
2229

23-
node.left = self.visit(node.left)
24-
node.right = self.visit(node.right)
30+
return False
2531

26-
# Check this is a constant expression that could be folded
27-
# We don't try to fold strings or bytes, since they have probably been arranged this way to make the source shorter and we are unlikely to beat that
28-
if not is_constant_node(node.left, (ast.Num, ast.NameConstant)):
29-
return node
30-
if not is_constant_node(node.right, (ast.Num, ast.NameConstant)):
31-
return node
3232

33-
if isinstance(node.op, ast.Div):
34-
# Folding div is subtle, since it can have different results in Python 2 and Python 3
35-
# Do this once target version options have been implemented
36-
return node
33+
class FoldConstants(SuiteTransformer):
34+
"""
35+
Fold Constants if it would reduce the size of the source
36+
"""
3737

38-
if isinstance(node.op, ast.Pow):
39-
# This can be folded, but it is unlikely to reduce the size of the source
40-
# It can also be slow to evaluate
41-
return node
38+
def __init__(self):
39+
super(FoldConstants, self).__init__()
4240

41+
def fold(self, node):
4342
# Evaluate the expression
4443
try:
4544
original_expression = unparse_expression(node)
@@ -96,6 +95,44 @@ def visit_BinOp(self, node):
9695
# New representation is shorter and has the same value, so use it
9796
return self.add_child(new_node, get_parent(node), node.namespace)
9897

98+
def visit_BinOp(self, node):
99+
100+
node.left = self.visit(node.left)
101+
node.right = self.visit(node.right)
102+
103+
# Check this is a constant expression that could be folded
104+
# We don't try to fold strings or bytes, since they have probably been arranged this way to make the source shorter and we are unlikely to beat that
105+
if not is_foldable_constant(node.left):
106+
return node
107+
if not is_foldable_constant(node.right):
108+
return node
109+
110+
if isinstance(node.op, ast.Div):
111+
# Folding div is subtle, since it can have different results in Python 2 and Python 3
112+
# Do this once target version options have been implemented
113+
return node
114+
115+
if isinstance(node.op, ast.Pow):
116+
# This can be folded, but it is unlikely to reduce the size of the source
117+
# It can also be slow to evaluate
118+
return node
119+
120+
return self.fold(node)
121+
122+
def visit_UnaryOp(self, node):
123+
124+
node.operand = self.visit(node.operand)
125+
126+
# Only fold if the operand is a foldable constant
127+
if not is_foldable_constant(node.operand):
128+
return node
129+
130+
# Only fold these unary operators
131+
if not isinstance(node.op, (ast.USub, ast.UAdd, ast.Invert, ast.Not)):
132+
return node
133+
134+
return self.fold(node)
135+
99136

100137
def equal_value_and_type(a, b):
101138
if type(a) != type(b):

0 commit comments

Comments
 (0)