From cee00306a2ded471658b71d2f1c9024bc22a62b1 Mon Sep 17 00:00:00 2001 From: Richard Si Date: Sun, 25 Jun 2023 05:43:56 -0400 Subject: [PATCH] Constant fold more unary and binary expressions (#15202) Now mypy can constant fold these additional operations: - Float arithmetic - Mixed int and float arithmetic - String multiplication - Complex plus or minus a literal real (eg. 1+j2) While this can be useful with literal types, the main goal is to improve constant folding in mypyc (mypyc/mypyc#772). mypyc can also fold bytes addition and multiplication, but mypy cannot as byte values can't be easily stored anywhere. --- mypy/constant_fold.py | 118 +++++++-- mypy/nodes.py | 2 +- mypy/semanal.py | 2 +- mypyc/irbuild/builder.py | 12 +- mypyc/irbuild/callable_class.py | 2 +- mypyc/irbuild/constant_fold.py | 64 +++-- mypyc/irbuild/expression.py | 15 +- mypyc/test-data/fixtures/ir.py | 6 + mypyc/test-data/irbuild-basic.test | 7 +- mypyc/test-data/irbuild-constant-fold.test | 281 +++++++++++++++++++-- 10 files changed, 412 insertions(+), 97 deletions(-) diff --git a/mypy/constant_fold.py b/mypy/constant_fold.py index a1011397eba8..6881ecae9e88 100644 --- a/mypy/constant_fold.py +++ b/mypy/constant_fold.py @@ -8,11 +8,21 @@ from typing import Union from typing_extensions import Final -from mypy.nodes import Expression, FloatExpr, IntExpr, NameExpr, OpExpr, StrExpr, UnaryExpr, Var +from mypy.nodes import ( + ComplexExpr, + Expression, + FloatExpr, + IntExpr, + NameExpr, + OpExpr, + StrExpr, + UnaryExpr, + Var, +) # All possible result types of constant folding -ConstantValue = Union[int, bool, float, str] -CONST_TYPES: Final = (int, bool, float, str) +ConstantValue = Union[int, bool, float, complex, str] +CONST_TYPES: Final = (int, bool, float, complex, str) def constant_fold_expr(expr: Expression, cur_mod_id: str) -> ConstantValue | None: @@ -39,6 +49,8 @@ def constant_fold_expr(expr: Expression, cur_mod_id: str) -> ConstantValue | Non return expr.value if isinstance(expr, FloatExpr): return expr.value + if isinstance(expr, ComplexExpr): + return expr.value elif isinstance(expr, NameExpr): if expr.name == "True": return True @@ -56,26 +68,60 @@ def constant_fold_expr(expr: Expression, cur_mod_id: str) -> ConstantValue | Non elif isinstance(expr, OpExpr): left = constant_fold_expr(expr.left, cur_mod_id) right = constant_fold_expr(expr.right, cur_mod_id) - if isinstance(left, int) and isinstance(right, int): - return constant_fold_binary_int_op(expr.op, left, right) - elif isinstance(left, str) and isinstance(right, str): - return constant_fold_binary_str_op(expr.op, left, right) + if left is not None and right is not None: + return constant_fold_binary_op(expr.op, left, right) elif isinstance(expr, UnaryExpr): value = constant_fold_expr(expr.expr, cur_mod_id) - if isinstance(value, int): - return constant_fold_unary_int_op(expr.op, value) - if isinstance(value, float): - return constant_fold_unary_float_op(expr.op, value) + if value is not None: + return constant_fold_unary_op(expr.op, value) return None -def constant_fold_binary_int_op(op: str, left: int, right: int) -> int | None: +def constant_fold_binary_op( + op: str, left: ConstantValue, right: ConstantValue +) -> ConstantValue | None: + if isinstance(left, int) and isinstance(right, int): + return constant_fold_binary_int_op(op, left, right) + + # Float and mixed int/float arithmetic. + if isinstance(left, float) and isinstance(right, float): + return constant_fold_binary_float_op(op, left, right) + elif isinstance(left, float) and isinstance(right, int): + return constant_fold_binary_float_op(op, left, right) + elif isinstance(left, int) and isinstance(right, float): + return constant_fold_binary_float_op(op, left, right) + + # String concatenation and multiplication. + if op == "+" and isinstance(left, str) and isinstance(right, str): + return left + right + elif op == "*" and isinstance(left, str) and isinstance(right, int): + return left * right + elif op == "*" and isinstance(left, int) and isinstance(right, str): + return left * right + + # Complex construction. + if op == "+" and isinstance(left, (int, float)) and isinstance(right, complex): + return left + right + elif op == "+" and isinstance(left, complex) and isinstance(right, (int, float)): + return left + right + elif op == "-" and isinstance(left, (int, float)) and isinstance(right, complex): + return left - right + elif op == "-" and isinstance(left, complex) and isinstance(right, (int, float)): + return left - right + + return None + + +def constant_fold_binary_int_op(op: str, left: int, right: int) -> int | float | None: if op == "+": return left + right if op == "-": return left - right elif op == "*": return left * right + elif op == "/": + if right != 0: + return left / right elif op == "//": if right != 0: return left // right @@ -102,25 +148,41 @@ def constant_fold_binary_int_op(op: str, left: int, right: int) -> int | None: return None -def constant_fold_unary_int_op(op: str, value: int) -> int | None: - if op == "-": - return -value - elif op == "~": - return ~value - elif op == "+": - return value +def constant_fold_binary_float_op(op: str, left: int | float, right: int | float) -> float | None: + assert not (isinstance(left, int) and isinstance(right, int)), (op, left, right) + if op == "+": + return left + right + elif op == "-": + return left - right + elif op == "*": + return left * right + elif op == "/": + if right != 0: + return left / right + elif op == "//": + if right != 0: + return left // right + elif op == "%": + if right != 0: + return left % right + elif op == "**": + if (left < 0 and isinstance(right, int)) or left > 0: + try: + ret = left**right + except OverflowError: + return None + else: + assert isinstance(ret, float), ret + return ret + return None -def constant_fold_unary_float_op(op: str, value: float) -> float | None: - if op == "-": +def constant_fold_unary_op(op: str, value: ConstantValue) -> int | float | None: + if op == "-" and isinstance(value, (int, float)): return -value - elif op == "+": + elif op == "~" and isinstance(value, int): + return ~value + elif op == "+" and isinstance(value, (int, float)): return value return None - - -def constant_fold_binary_str_op(op: str, left: str, right: str) -> str | None: - if op == "+": - return left + right - return None diff --git a/mypy/nodes.py b/mypy/nodes.py index 2f4f3f788385..52dd9948e0c1 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1002,7 +1002,7 @@ def __init__(self, name: str, type: mypy.types.Type | None = None) -> None: # If constant value is a simple literal, # store the literal value (unboxed) for the benefit of # tools like mypyc. - self.final_value: int | float | bool | str | None = None + self.final_value: int | float | complex | bool | str | None = None # Where the value was set (only for class attributes) self.final_unset_in_class = False self.final_set_in_init = False diff --git a/mypy/semanal.py b/mypy/semanal.py index 249a57d550b2..43960d972101 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -3394,7 +3394,7 @@ def analyze_simple_literal_type(self, rvalue: Expression, is_final: bool) -> Typ return None value = constant_fold_expr(rvalue, self.cur_mod_id) - if value is None: + if value is None or isinstance(value, complex): return None if isinstance(value, bool): diff --git a/mypyc/irbuild/builder.py b/mypyc/irbuild/builder.py index 26aa17071974..86c28882d738 100644 --- a/mypyc/irbuild/builder.py +++ b/mypyc/irbuild/builder.py @@ -535,16 +535,14 @@ def load_final_static( error_msg=f'value for final name "{error_name}" was not set', ) - def load_final_literal_value(self, val: int | str | bytes | float | bool, line: int) -> Value: - """Load value of a final name or class-level attribute.""" + def load_literal_value(self, val: int | str | bytes | float | complex | bool) -> Value: + """Load value of a final name, class-level attribute, or constant folded expression.""" if isinstance(val, bool): if val: return self.true() else: return self.false() elif isinstance(val, int): - # TODO: take care of negative integer initializers - # (probably easier to fix this in mypy itself). return self.builder.load_int(val) elif isinstance(val, float): return self.builder.load_float(val) @@ -552,8 +550,10 @@ def load_final_literal_value(self, val: int | str | bytes | float | bool, line: return self.builder.load_str(val) elif isinstance(val, bytes): return self.builder.load_bytes(val) + elif isinstance(val, complex): + return self.builder.load_complex(val) else: - assert False, "Unsupported final literal value" + assert False, "Unsupported literal value" def get_assignment_target( self, lvalue: Lvalue, line: int = -1, *, for_read: bool = False @@ -1013,7 +1013,7 @@ def emit_load_final( line: line number where loading occurs """ if final_var.final_value is not None: # this is safe even for non-native names - return self.load_final_literal_value(final_var.final_value, line) + return self.load_literal_value(final_var.final_value) elif native: return self.load_final_static(fullname, self.mapper.type_to_rtype(typ), line, name) else: diff --git a/mypyc/irbuild/callable_class.py b/mypyc/irbuild/callable_class.py index d3ee54a208cd..599dbb81f767 100644 --- a/mypyc/irbuild/callable_class.py +++ b/mypyc/irbuild/callable_class.py @@ -17,7 +17,7 @@ def setup_callable_class(builder: IRBuilder) -> None: - """Generate an (incomplete) callable class representing function. + """Generate an (incomplete) callable class representing a function. This can be a nested function or a function within a non-extension class. Also set up the 'self' variable for that class. diff --git a/mypyc/irbuild/constant_fold.py b/mypyc/irbuild/constant_fold.py index bc71052f5418..dc21be4689e2 100644 --- a/mypyc/irbuild/constant_fold.py +++ b/mypyc/irbuild/constant_fold.py @@ -13,13 +13,10 @@ from typing import Union from typing_extensions import Final -from mypy.constant_fold import ( - constant_fold_binary_int_op, - constant_fold_binary_str_op, - constant_fold_unary_float_op, - constant_fold_unary_int_op, -) +from mypy.constant_fold import constant_fold_binary_op, constant_fold_unary_op from mypy.nodes import ( + BytesExpr, + ComplexExpr, Expression, FloatExpr, IntExpr, @@ -31,10 +28,11 @@ Var, ) from mypyc.irbuild.builder import IRBuilder +from mypyc.irbuild.util import bytes_from_str # All possible result types of constant folding -ConstantValue = Union[int, str, float] -CONST_TYPES: Final = (int, str, float) +ConstantValue = Union[int, float, complex, str, bytes] +CONST_TYPES: Final = (int, float, complex, str, bytes) def constant_fold_expr(builder: IRBuilder, expr: Expression) -> ConstantValue | None: @@ -44,35 +42,55 @@ def constant_fold_expr(builder: IRBuilder, expr: Expression) -> ConstantValue | """ if isinstance(expr, IntExpr): return expr.value + if isinstance(expr, FloatExpr): + return expr.value if isinstance(expr, StrExpr): return expr.value - if isinstance(expr, FloatExpr): + if isinstance(expr, BytesExpr): + return bytes_from_str(expr.value) + if isinstance(expr, ComplexExpr): return expr.value elif isinstance(expr, NameExpr): node = expr.node if isinstance(node, Var) and node.is_final: - value = node.final_value - if isinstance(value, (CONST_TYPES)): - return value + final_value = node.final_value + if isinstance(final_value, (CONST_TYPES)): + return final_value elif isinstance(expr, MemberExpr): final = builder.get_final_ref(expr) if final is not None: fn, final_var, native = final if final_var.is_final: - value = final_var.final_value - if isinstance(value, (CONST_TYPES)): - return value + final_value = final_var.final_value + if isinstance(final_value, (CONST_TYPES)): + return final_value elif isinstance(expr, OpExpr): left = constant_fold_expr(builder, expr.left) right = constant_fold_expr(builder, expr.right) - if isinstance(left, int) and isinstance(right, int): - return constant_fold_binary_int_op(expr.op, left, right) - elif isinstance(left, str) and isinstance(right, str): - return constant_fold_binary_str_op(expr.op, left, right) + if left is not None and right is not None: + return constant_fold_binary_op_extended(expr.op, left, right) elif isinstance(expr, UnaryExpr): value = constant_fold_expr(builder, expr.expr) - if isinstance(value, int): - return constant_fold_unary_int_op(expr.op, value) - if isinstance(value, float): - return constant_fold_unary_float_op(expr.op, value) + if value is not None and not isinstance(value, bytes): + return constant_fold_unary_op(expr.op, value) + return None + + +def constant_fold_binary_op_extended( + op: str, left: ConstantValue, right: ConstantValue +) -> ConstantValue | None: + """Like mypy's constant_fold_binary_op(), but includes bytes support. + + mypy cannot use constant folded bytes easily so it's simpler to only support them in mypyc. + """ + if not isinstance(left, bytes) and not isinstance(right, bytes): + return constant_fold_binary_op(op, left, right) + + if op == "+" and isinstance(left, bytes) and isinstance(right, bytes): + return left + right + elif op == "*" and isinstance(left, bytes) and isinstance(right, int): + return left * right + elif op == "*" and isinstance(left, int) and isinstance(right, bytes): + return left * right + return None diff --git a/mypyc/irbuild/expression.py b/mypyc/irbuild/expression.py index 4ebc422ed535..ada3d47cefab 100644 --- a/mypyc/irbuild/expression.py +++ b/mypyc/irbuild/expression.py @@ -55,7 +55,6 @@ Assign, BasicBlock, ComparisonOp, - Float, Integer, LoadAddress, LoadLiteral, @@ -92,7 +91,6 @@ tokenizer_printf_style, ) from mypyc.irbuild.specialize import apply_function_specialization, apply_method_specialization -from mypyc.irbuild.util import bytes_from_str from mypyc.primitives.bytes_ops import bytes_slice_op from mypyc.primitives.dict_ops import dict_get_item_op, dict_new_op, dict_set_item_op from mypyc.primitives.generic_ops import iter_op @@ -575,12 +573,8 @@ def try_constant_fold(builder: IRBuilder, expr: Expression) -> Value | None: Return None otherwise. """ value = constant_fold_expr(builder, expr) - if isinstance(value, int): - return builder.load_int(value) - elif isinstance(value, str): - return builder.load_str(value) - elif isinstance(value, float): - return Float(value) + if value is not None: + return builder.load_literal_value(value) return None @@ -662,10 +656,6 @@ def set_literal_values(builder: IRBuilder, items: Sequence[Expression]) -> list[ values.append(True) elif item.fullname == "builtins.False": values.append(False) - elif isinstance(item, (BytesExpr, FloatExpr, ComplexExpr)): - # constant_fold_expr() doesn't handle these (yet?) - v = bytes_from_str(item.value) if isinstance(item, BytesExpr) else item.value - values.append(v) elif isinstance(item, TupleExpr): tuple_values = set_literal_values(builder, item.items) if tuple_values is not None: @@ -685,7 +675,6 @@ def precompute_set_literal(builder: IRBuilder, s: SetExpr) -> Value | None: Supported items: - Anything supported by irbuild.constant_fold.constant_fold_expr() - None, True, and False - - Float, byte, and complex literals - Tuple literals with only items listed above """ values = set_literal_values(builder, s.items) diff --git a/mypyc/test-data/fixtures/ir.py b/mypyc/test-data/fixtures/ir.py index 0b081b079bda..bf06613ad2a8 100644 --- a/mypyc/test-data/fixtures/ir.py +++ b/mypyc/test-data/fixtures/ir.py @@ -87,6 +87,8 @@ def __init__(self) -> None: pass @overload def __init__(self, x: object) -> None: pass def __add__(self, x: str) -> str: pass + def __mul__(self, x: int) -> str: pass + def __rmul__(self, x: int) -> str: pass def __eq__(self, x: object) -> bool: pass def __ne__(self, x: object) -> bool: pass def __lt__(self, x: str) -> bool: ... @@ -134,7 +136,9 @@ def __ge__(self, x: float) -> bool: ... class complex: def __init__(self, x: object, y: object = None) -> None: pass def __add__(self, n: complex) -> complex: pass + def __radd__(self, n: float) -> complex: pass def __sub__(self, n: complex) -> complex: pass + def __rsub__(self, n: float) -> complex: pass def __mul__(self, n: complex) -> complex: pass def __truediv__(self, n: complex) -> complex: pass def __neg__(self) -> complex: pass @@ -145,6 +149,8 @@ def __init__(self) -> None: ... @overload def __init__(self, x: object) -> None: ... def __add__(self, x: bytes) -> bytes: ... + def __mul__(self, x: int) -> bytes: ... + def __rmul__(self, x: int) -> bytes: ... def __eq__(self, x: object) -> bool: ... def __ne__(self, x: object) -> bool: ... @overload diff --git a/mypyc/test-data/irbuild-basic.test b/mypyc/test-data/irbuild-basic.test index 496eca77e090..556e0a4bbc50 100644 --- a/mypyc/test-data/irbuild-basic.test +++ b/mypyc/test-data/irbuild-basic.test @@ -1172,13 +1172,16 @@ L0: [case testLoadComplex] def load() -> complex: - return 5j+1.0 + real = 1 + return 5j+real [out] def load(): + real :: int r0, r1, r2 :: object L0: + real = 2 r0 = 5j - r1 = box(float, 1.0) + r1 = box(int, real) r2 = PyNumber_Add(r0, r1) return r2 diff --git a/mypyc/test-data/irbuild-constant-fold.test b/mypyc/test-data/irbuild-constant-fold.test index 866953f0c09a..97b13ab337c7 100644 --- a/mypyc/test-data/irbuild-constant-fold.test +++ b/mypyc/test-data/irbuild-constant-fold.test @@ -3,6 +3,7 @@ def bin_ops() -> None: add = 15 + 47 add_mul = (2 + 3) * 5 sub = 7 - 11 + div = 3 / 2 bit_and = 6 & 10 bit_or = 6 | 10 bit_xor = 6 ^ 10 @@ -25,11 +26,14 @@ def pow() -> None: p3 = 0**0 [out] def bin_ops(): - add, add_mul, sub, bit_and, bit_or, bit_xor, lshift, rshift, lshift0, rshift0 :: int + add, add_mul, sub :: int + div :: float + bit_and, bit_or, bit_xor, lshift, rshift, lshift0, rshift0 :: int L0: add = 124 add_mul = 50 sub = -8 + div = 1.5 bit_and = 4 bit_or = 28 bit_xor = 24 @@ -117,35 +121,28 @@ L0: [case testIntConstantFoldingUnsupportedCases] def error_cases() -> None: - div_by_zero = 5 // 0 + div_by_zero = 5 / 0 + floor_div_by_zero = 5 // 0 mod_by_zero = 5 % 0 lshift_neg = 6 << -1 rshift_neg = 7 >> -1 -def unsupported_div() -> None: - x = 4 / 6 - y = 10 / 5 def unsupported_pow() -> None: p = 3 ** (-1) [out] def error_cases(): - r0, div_by_zero, r1, mod_by_zero, r2, lshift_neg, r3, rshift_neg :: int + r0, div_by_zero :: float + r1, floor_div_by_zero, r2, mod_by_zero, r3, lshift_neg, r4, rshift_neg :: int L0: - r0 = CPyTagged_FloorDivide(10, 0) + r0 = CPyTagged_TrueDivide(10, 0) div_by_zero = r0 - r1 = CPyTagged_Remainder(10, 0) - mod_by_zero = r1 - r2 = CPyTagged_Lshift(12, -2) - lshift_neg = r2 - r3 = CPyTagged_Rshift(14, -2) - rshift_neg = r3 - return 1 -def unsupported_div(): - r0, x, r1, y :: float -L0: - r0 = CPyTagged_TrueDivide(8, 12) - x = r0 - r1 = CPyTagged_TrueDivide(20, 10) - y = r1 + r1 = CPyTagged_FloorDivide(10, 0) + floor_div_by_zero = r1 + r2 = CPyTagged_Remainder(10, 0) + mod_by_zero = r2 + r3 = CPyTagged_Lshift(12, -2) + lshift_neg = r3 + r4 = CPyTagged_Rshift(14, -2) + rshift_neg = r4 return 1 def unsupported_pow(): r0, r1, r2 :: object @@ -224,20 +221,260 @@ L0: a = 12 return 1 +[case testFloatConstantFolding] +from typing_extensions import Final + +N: Final = 1.5 +N2: Final = 1.5 * 2 + +def bin_ops() -> None: + add = 0.5 + 0.5 + add_mul = (1.5 + 3.5) * 5.0 + sub = 7.0 - 7.5 + div = 3.0 / 2.0 + floor_div = 3.0 // 2.0 +def bin_ops_neg() -> None: + add = 0.5 + -0.5 + add_mul = (-1.5 + 3.5) * -5.0 + add_mul2 = (1.5 + -3.5) * -5.0 + sub = 7.0 - -7.5 + div = 3.0 / -2.0 + floor_div = 3.0 // -2.0 +def unary_ops() -> None: + neg1 = -5.5 + neg2 = --1.5 + neg3 = -0.0 + pos = +5.5 +def pow() -> None: + p0 = 16.0**0 + p1 = 16.0**0.5 + p2 = (-5.0)**3 + p3 = 16.0**(-0) + p4 = 16.0**(-0.5) + p5 = (-2.0)**(-1) +def error_cases() -> None: + div = 2.0 / 0.0 + floor_div = 2.0 // 0.0 + power_imag = (-2.0)**0.5 + power_imag2 = (-2.0)**(-0.5) + power_overflow = 2.0**10000.0 +def final_floats() -> None: + add1 = N + 1.2 + add2 = N + N2 + add3 = -1.2 + N2 +[out] +def bin_ops(): + add, add_mul, sub, div, floor_div :: float +L0: + add = 1.0 + add_mul = 25.0 + sub = -0.5 + div = 1.5 + floor_div = 1.0 + return 1 +def bin_ops_neg(): + add, add_mul, add_mul2, sub, div, floor_div :: float +L0: + add = 0.0 + add_mul = -10.0 + add_mul2 = 10.0 + sub = 14.5 + div = -1.5 + floor_div = -2.0 + return 1 +def unary_ops(): + neg1, neg2, neg3, pos :: float +L0: + neg1 = -5.5 + neg2 = 1.5 + neg3 = -0.0 + pos = 5.5 + return 1 +def pow(): + p0, p1, p2, p3, p4, p5 :: float +L0: + p0 = 1.0 + p1 = 4.0 + p2 = -125.0 + p3 = 1.0 + p4 = 0.25 + p5 = -0.5 + return 1 +def error_cases(): + r0 :: bit + r1 :: bool + r2, div, r3, floor_div :: float + r4, r5, r6 :: object + r7, power_imag :: float + r8, r9, r10 :: object + r11, power_imag2 :: float + r12, r13, r14 :: object + r15, power_overflow :: float +L0: + r0 = 0.0 == 0.0 + if r0 goto L1 else goto L2 :: bool +L1: + r1 = raise ZeroDivisionError('float division by zero') + unreachable +L2: + r2 = 2.0 / 0.0 + div = r2 + r3 = CPyFloat_FloorDivide(2.0, 0.0) + floor_div = r3 + r4 = box(float, -2.0) + r5 = box(float, 0.5) + r6 = CPyNumber_Power(r4, r5) + r7 = unbox(float, r6) + power_imag = r7 + r8 = box(float, -2.0) + r9 = box(float, -0.5) + r10 = CPyNumber_Power(r8, r9) + r11 = unbox(float, r10) + power_imag2 = r11 + r12 = box(float, 2.0) + r13 = box(float, 10000.0) + r14 = CPyNumber_Power(r12, r13) + r15 = unbox(float, r14) + power_overflow = r15 + return 1 +def final_floats(): + add1, add2, add3 :: float +L0: + add1 = 2.7 + add2 = 4.5 + add3 = 1.8 + return 1 + +[case testMixedFloatIntConstantFolding] +def bin_ops() -> None: + add = 1 + 0.5 + sub = 1 - 0.5 + mul = 0.5 * 5 + div = 5 / 0.5 + floor_div = 9.5 // 5 +def error_cases() -> None: + div = 2.0 / 0 + floor_div = 2.0 // 0 + power_overflow = 2.0**10000 +[out] +def bin_ops(): + add, sub, mul, div, floor_div :: float +L0: + add = 1.5 + sub = 0.5 + mul = 2.5 + div = 10.0 + floor_div = 1.0 + return 1 +def error_cases(): + r0 :: bit + r1 :: bool + r2, div, r3, floor_div :: float + r4, r5, r6 :: object + r7, power_overflow :: float +L0: + r0 = 0.0 == 0.0 + if r0 goto L1 else goto L2 :: bool +L1: + r1 = raise ZeroDivisionError('float division by zero') + unreachable +L2: + r2 = 2.0 / 0.0 + div = r2 + r3 = CPyFloat_FloorDivide(2.0, 0.0) + floor_div = r3 + r4 = box(float, 2.0) + r5 = box(float, 10000.0) + r6 = CPyNumber_Power(r4, r5) + r7 = unbox(float, r6) + power_overflow = r7 + return 1 + [case testStrConstantFolding] from typing_extensions import Final S: Final = 'z' +N: Final = 2 def f() -> None: x = 'foo' + 'bar' y = 'x' + 'y' + S + mul = "foobar" * 2 + mul2 = N * "foobar" [out] def f(): - r0, x, r1, y :: str + r0, x, r1, y, r2, mul, r3, mul2 :: str L0: r0 = 'foobar' x = r0 r1 = 'xyz' y = r1 + r2 = 'foobarfoobar' + mul = r2 + r3 = 'foobarfoobar' + mul2 = r3 + return 1 + +[case testBytesConstantFolding] +from typing_extensions import Final + +N: Final = 2 + +def f() -> None: + # Unfortunately, mypy doesn't store the bytes value of final refs. + x = b'foo' + b'bar' + mul = b"foobar" * 2 + mul2 = N * b"foobar" +[out] +def f(): + r0, x, r1, mul, r2, mul2 :: bytes +L0: + r0 = b'foobar' + x = r0 + r1 = b'foobarfoobar' + mul = r1 + r2 = b'foobarfoobar' + mul2 = r2 + return 1 + +[case testComplexConstantFolding] +from typing_extensions import Final + +N: Final = 1 +FLOAT_N: Final = 1.5 + +def integral() -> None: + pos = 1+2j + pos_2 = 2j+N + neg = 1-2j + neg_2 = 2j-N +def floating() -> None: + pos = 1.5+2j + pos_2 = 2j+FLOAT_N + neg = 1.5-2j + neg_2 = 2j-FLOAT_N +[out] +def integral(): + r0, pos, r1, pos_2, r2, neg, r3, neg_2 :: object +L0: + r0 = (1+2j) + pos = r0 + r1 = (1+2j) + pos_2 = r1 + r2 = (1-2j) + neg = r2 + r3 = (-1+2j) + neg_2 = r3 + return 1 +def floating(): + r0, pos, r1, pos_2, r2, neg, r3, neg_2 :: object +L0: + r0 = (1.5+2j) + pos = r0 + r1 = (1.5+2j) + pos_2 = r1 + r2 = (1.5-2j) + neg = r2 + r3 = (-1.5+2j) + neg_2 = r3 return 1