constant_fold.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. """Constant folding of expressions.
  2. For example, 3 + 5 can be constant folded into 8.
  3. """
  4. from __future__ import annotations
  5. from typing import Final, Union
  6. from mypy.nodes import (
  7. ComplexExpr,
  8. Expression,
  9. FloatExpr,
  10. IntExpr,
  11. NameExpr,
  12. OpExpr,
  13. StrExpr,
  14. UnaryExpr,
  15. Var,
  16. )
  17. # All possible result types of constant folding
  18. ConstantValue = Union[int, bool, float, complex, str]
  19. CONST_TYPES: Final = (int, bool, float, complex, str)
  20. def constant_fold_expr(expr: Expression, cur_mod_id: str) -> ConstantValue | None:
  21. """Return the constant value of an expression for supported operations.
  22. Among other things, support int arithmetic and string
  23. concatenation. For example, the expression 3 + 5 has the constant
  24. value 8.
  25. Also bind simple references to final constants defined in the
  26. current module (cur_mod_id). Binding to references is best effort
  27. -- we don't bind references to other modules. Mypyc trusts these
  28. to be correct in compiled modules, so that it can replace a
  29. constant expression (or a reference to one) with the statically
  30. computed value. We don't want to infer constant values based on
  31. stubs, in particular, as these might not match the implementation
  32. (due to version skew, for example).
  33. Return None if unsuccessful.
  34. """
  35. if isinstance(expr, IntExpr):
  36. return expr.value
  37. if isinstance(expr, StrExpr):
  38. return expr.value
  39. if isinstance(expr, FloatExpr):
  40. return expr.value
  41. if isinstance(expr, ComplexExpr):
  42. return expr.value
  43. elif isinstance(expr, NameExpr):
  44. if expr.name == "True":
  45. return True
  46. elif expr.name == "False":
  47. return False
  48. node = expr.node
  49. if (
  50. isinstance(node, Var)
  51. and node.is_final
  52. and node.fullname.rsplit(".", 1)[0] == cur_mod_id
  53. ):
  54. value = node.final_value
  55. if isinstance(value, (CONST_TYPES)):
  56. return value
  57. elif isinstance(expr, OpExpr):
  58. left = constant_fold_expr(expr.left, cur_mod_id)
  59. right = constant_fold_expr(expr.right, cur_mod_id)
  60. if left is not None and right is not None:
  61. return constant_fold_binary_op(expr.op, left, right)
  62. elif isinstance(expr, UnaryExpr):
  63. value = constant_fold_expr(expr.expr, cur_mod_id)
  64. if value is not None:
  65. return constant_fold_unary_op(expr.op, value)
  66. return None
  67. def constant_fold_binary_op(
  68. op: str, left: ConstantValue, right: ConstantValue
  69. ) -> ConstantValue | None:
  70. if isinstance(left, int) and isinstance(right, int):
  71. return constant_fold_binary_int_op(op, left, right)
  72. # Float and mixed int/float arithmetic.
  73. if isinstance(left, float) and isinstance(right, float):
  74. return constant_fold_binary_float_op(op, left, right)
  75. elif isinstance(left, float) and isinstance(right, int):
  76. return constant_fold_binary_float_op(op, left, right)
  77. elif isinstance(left, int) and isinstance(right, float):
  78. return constant_fold_binary_float_op(op, left, right)
  79. # String concatenation and multiplication.
  80. if op == "+" and isinstance(left, str) and isinstance(right, str):
  81. return left + right
  82. elif op == "*" and isinstance(left, str) and isinstance(right, int):
  83. return left * right
  84. elif op == "*" and isinstance(left, int) and isinstance(right, str):
  85. return left * right
  86. # Complex construction.
  87. if op == "+" and isinstance(left, (int, float)) and isinstance(right, complex):
  88. return left + right
  89. elif op == "+" and isinstance(left, complex) and isinstance(right, (int, float)):
  90. return left + right
  91. elif op == "-" and isinstance(left, (int, float)) and isinstance(right, complex):
  92. return left - right
  93. elif op == "-" and isinstance(left, complex) and isinstance(right, (int, float)):
  94. return left - right
  95. return None
  96. def constant_fold_binary_int_op(op: str, left: int, right: int) -> int | float | None:
  97. if op == "+":
  98. return left + right
  99. if op == "-":
  100. return left - right
  101. elif op == "*":
  102. return left * right
  103. elif op == "/":
  104. if right != 0:
  105. return left / right
  106. elif op == "//":
  107. if right != 0:
  108. return left // right
  109. elif op == "%":
  110. if right != 0:
  111. return left % right
  112. elif op == "&":
  113. return left & right
  114. elif op == "|":
  115. return left | right
  116. elif op == "^":
  117. return left ^ right
  118. elif op == "<<":
  119. if right >= 0:
  120. return left << right
  121. elif op == ">>":
  122. if right >= 0:
  123. return left >> right
  124. elif op == "**":
  125. if right >= 0:
  126. ret = left**right
  127. assert isinstance(ret, int)
  128. return ret
  129. return None
  130. def constant_fold_binary_float_op(op: str, left: int | float, right: int | float) -> float | None:
  131. assert not (isinstance(left, int) and isinstance(right, int)), (op, left, right)
  132. if op == "+":
  133. return left + right
  134. elif op == "-":
  135. return left - right
  136. elif op == "*":
  137. return left * right
  138. elif op == "/":
  139. if right != 0:
  140. return left / right
  141. elif op == "//":
  142. if right != 0:
  143. return left // right
  144. elif op == "%":
  145. if right != 0:
  146. return left % right
  147. elif op == "**":
  148. if (left < 0 and isinstance(right, int)) or left > 0:
  149. try:
  150. ret = left**right
  151. except OverflowError:
  152. return None
  153. else:
  154. assert isinstance(ret, float), ret
  155. return ret
  156. return None
  157. def constant_fold_unary_op(op: str, value: ConstantValue) -> int | float | None:
  158. if op == "-" and isinstance(value, (int, float)):
  159. return -value
  160. elif op == "~" and isinstance(value, int):
  161. return ~value
  162. elif op == "+" and isinstance(value, (int, float)):
  163. return value
  164. return None