constant_fold.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  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 Union
  6. from typing_extensions import Final
  7. from mypy.nodes import Expression, FloatExpr, IntExpr, NameExpr, OpExpr, StrExpr, UnaryExpr, Var
  8. # All possible result types of constant folding
  9. ConstantValue = Union[int, bool, float, str]
  10. CONST_TYPES: Final = (int, bool, float, str)
  11. def constant_fold_expr(expr: Expression, cur_mod_id: str) -> ConstantValue | None:
  12. """Return the constant value of an expression for supported operations.
  13. Among other things, support int arithmetic and string
  14. concatenation. For example, the expression 3 + 5 has the constant
  15. value 8.
  16. Also bind simple references to final constants defined in the
  17. current module (cur_mod_id). Binding to references is best effort
  18. -- we don't bind references to other modules. Mypyc trusts these
  19. to be correct in compiled modules, so that it can replace a
  20. constant expression (or a reference to one) with the statically
  21. computed value. We don't want to infer constant values based on
  22. stubs, in particular, as these might not match the implementation
  23. (due to version skew, for example).
  24. Return None if unsuccessful.
  25. """
  26. if isinstance(expr, IntExpr):
  27. return expr.value
  28. if isinstance(expr, StrExpr):
  29. return expr.value
  30. if isinstance(expr, FloatExpr):
  31. return expr.value
  32. elif isinstance(expr, NameExpr):
  33. if expr.name == "True":
  34. return True
  35. elif expr.name == "False":
  36. return False
  37. node = expr.node
  38. if (
  39. isinstance(node, Var)
  40. and node.is_final
  41. and node.fullname.rsplit(".", 1)[0] == cur_mod_id
  42. ):
  43. value = node.final_value
  44. if isinstance(value, (CONST_TYPES)):
  45. return value
  46. elif isinstance(expr, OpExpr):
  47. left = constant_fold_expr(expr.left, cur_mod_id)
  48. right = constant_fold_expr(expr.right, cur_mod_id)
  49. if isinstance(left, int) and isinstance(right, int):
  50. return constant_fold_binary_int_op(expr.op, left, right)
  51. elif isinstance(left, str) and isinstance(right, str):
  52. return constant_fold_binary_str_op(expr.op, left, right)
  53. elif isinstance(expr, UnaryExpr):
  54. value = constant_fold_expr(expr.expr, cur_mod_id)
  55. if isinstance(value, int):
  56. return constant_fold_unary_int_op(expr.op, value)
  57. if isinstance(value, float):
  58. return constant_fold_unary_float_op(expr.op, value)
  59. return None
  60. def constant_fold_binary_int_op(op: str, left: int, right: int) -> int | None:
  61. if op == "+":
  62. return left + right
  63. if op == "-":
  64. return left - right
  65. elif op == "*":
  66. return left * right
  67. elif op == "//":
  68. if right != 0:
  69. return left // right
  70. elif op == "%":
  71. if right != 0:
  72. return left % right
  73. elif op == "&":
  74. return left & right
  75. elif op == "|":
  76. return left | right
  77. elif op == "^":
  78. return left ^ right
  79. elif op == "<<":
  80. if right >= 0:
  81. return left << right
  82. elif op == ">>":
  83. if right >= 0:
  84. return left >> right
  85. elif op == "**":
  86. if right >= 0:
  87. ret = left**right
  88. assert isinstance(ret, int)
  89. return ret
  90. return None
  91. def constant_fold_unary_int_op(op: str, value: int) -> int | None:
  92. if op == "-":
  93. return -value
  94. elif op == "~":
  95. return ~value
  96. elif op == "+":
  97. return value
  98. return None
  99. def constant_fold_unary_float_op(op: str, value: float) -> float | None:
  100. if op == "-":
  101. return -value
  102. elif op == "+":
  103. return value
  104. return None
  105. def constant_fold_binary_str_op(op: str, left: str, right: str) -> str | None:
  106. if op == "+":
  107. return left + right
  108. return None