Skip to content
Open
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
50 changes: 32 additions & 18 deletions mypy/constant_fold.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ def constant_fold_binary_op(


def constant_fold_binary_int_op(op: str, left: int, right: int) -> int | float | None:
# Operands are unbounded ints, so some results do not fit into a float (`/`) or
# cannot be built at all (`<<` with a huge count). Folding is an optimization:
# when it cannot produce a value, return None and let the expression stand.
if op == "+":
return left + right
if op == "-":
Expand All @@ -120,7 +123,10 @@ def constant_fold_binary_int_op(op: str, left: int, right: int) -> int | float |
return left * right
elif op == "/":
if right != 0:
return left / right
try:
return left / right
except OverflowError:
return None
elif op == "//":
if right != 0:
return left // right
Expand All @@ -135,7 +141,10 @@ def constant_fold_binary_int_op(op: str, left: int, right: int) -> int | float |
return left ^ right
elif op == "<<":
if right >= 0:
return left << right
try:
return left << right
except (OverflowError, ValueError):
return None
elif op == ">>":
if right >= 0:
return left >> right
Expand All @@ -149,22 +158,27 @@ def constant_fold_binary_int_op(op: str, left: int, right: int) -> int | float |

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 == "**":
# An int operand here is unbounded, so converting it to a float can overflow.
# `**` already guards against this; the other operations get the same treatment.
try:
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
except OverflowError:
return None
if op == "**":
if (left < 0 and isinstance(right, int)) or left > 0:
try:
ret = left**right
Expand Down
28 changes: 28 additions & 0 deletions mypy/test/testconstantfold.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""Tests for constant folding of huge operands."""

from __future__ import annotations

from mypy.constant_fold import constant_fold_binary_float_op, constant_fold_binary_int_op
from mypy.test.helpers import Suite

BIG = 2**2000


class ConstantFoldOverflowSuite(Suite):
"""Folding is an optimization: when a result cannot be built, it must yield None."""

def test_int_div_overflow(self) -> None:
assert constant_fold_binary_int_op("/", BIG, 3) is None

def test_int_lshift_huge_count(self) -> None:
assert constant_fold_binary_int_op("<<", 1, 2**70) is None

def test_float_ops_with_huge_int(self) -> None:
for op in ("+", "-", "*", "/", "//", "%"):
assert constant_fold_binary_float_op(op, BIG, 1.0) is None, op

def test_small_operands_still_fold(self) -> None:
assert constant_fold_binary_int_op("/", 6, 3) == 2.0
assert constant_fold_binary_int_op("<<", 1, 4) == 16
assert constant_fold_binary_float_op("+", 1, 2.5) == 3.5
assert constant_fold_binary_float_op("**", 2.0, 3) == 8.0
13 changes: 13 additions & 0 deletions mypy/test/testtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,19 @@
import mypy.expandtype # ruff: isort: skip


class LiteralTypeReprSuite(Suite):
def setUp(self) -> None:
self.fx = TypeFixture()

def test_value_repr_of_huge_int(self) -> None:
# repr() of an int is limited by sys.set_int_max_str_digits(); a literal built
# from a folded power can exceed it and used to raise ValueError.
huge = LiteralType(2**100000, self.fx.a)
rendered = huge.value_repr()
assert rendered.startswith("0x")
assert int(rendered, 16) == 2**100000


class TypesSuite(Suite):
def setUp(self) -> None:
self.x = UnboundType("X") # Helpers
Expand Down
8 changes: 7 additions & 1 deletion mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -3396,7 +3396,13 @@ def value_repr(self) -> str:
if isinstance(self.value, SentinelValue):
return self.value.name

raw = repr(self.value)
try:
raw = repr(self.value)
except ValueError:
# int -> str conversion is limited by sys.set_int_max_str_digits(); a literal
# type built from a folded power can exceed it. Fall back to a lossless form.
assert isinstance(self.value, int)
raw = hex(self.value)
fallback_name = self.fallback.type.fullname

# If this is backed by an enum,
Expand Down
Loading