blob: 1af1ad611a8987b016d37d8366e0f3a47b005560 [file] [log] [blame]
"""IRBuilder AST transform helpers shared between expressions and statements.
Shared code that is tightly coupled to mypy ASTs can be put here instead of
making mypyc.irbuild.builder larger.
"""
from __future__ import annotations
from mypy.nodes import (
LDEF,
BytesExpr,
ComparisonExpr,
Expression,
FloatExpr,
IntExpr,
MemberExpr,
NameExpr,
OpExpr,
StrExpr,
UnaryExpr,
Var,
)
from mypyc.ir.ops import BasicBlock
from mypyc.ir.rtypes import is_fixed_width_rtype, is_tagged
from mypyc.irbuild.builder import IRBuilder
from mypyc.irbuild.constant_fold import constant_fold_expr
def process_conditional(
self: IRBuilder, e: Expression, true: BasicBlock, false: BasicBlock
) -> None:
if isinstance(e, OpExpr) and e.op in ["and", "or"]:
if e.op == "and":
# Short circuit 'and' in a conditional context.
new = BasicBlock()
process_conditional(self, e.left, new, false)
self.activate_block(new)
process_conditional(self, e.right, true, false)
else:
# Short circuit 'or' in a conditional context.
new = BasicBlock()
process_conditional(self, e.left, true, new)
self.activate_block(new)
process_conditional(self, e.right, true, false)
elif isinstance(e, UnaryExpr) and e.op == "not":
process_conditional(self, e.expr, false, true)
else:
res = maybe_process_conditional_comparison(self, e, true, false)
if res:
return
# Catch-all for arbitrary expressions.
reg = self.accept(e)
self.add_bool_branch(reg, true, false)
def maybe_process_conditional_comparison(
self: IRBuilder, e: Expression, true: BasicBlock, false: BasicBlock
) -> bool:
"""Transform simple tagged integer comparisons in a conditional context.
Return True if the operation is supported (and was transformed). Otherwise,
do nothing and return False.
Args:
e: Arbitrary expression
true: Branch target if comparison is true
false: Branch target if comparison is false
"""
if not isinstance(e, ComparisonExpr) or len(e.operands) != 2:
return False
ltype = self.node_type(e.operands[0])
rtype = self.node_type(e.operands[1])
if not (
(is_tagged(ltype) or is_fixed_width_rtype(ltype))
and (is_tagged(rtype) or is_fixed_width_rtype(rtype))
):
return False
op = e.operators[0]
if op not in ("==", "!=", "<", "<=", ">", ">="):
return False
left_expr = e.operands[0]
right_expr = e.operands[1]
borrow_left = is_borrow_friendly_expr(self, right_expr)
left = self.accept(left_expr, can_borrow=borrow_left)
right = self.accept(right_expr, can_borrow=True)
if is_fixed_width_rtype(ltype) or is_fixed_width_rtype(rtype):
if not is_fixed_width_rtype(ltype):
left = self.coerce(left, rtype, e.line)
elif not is_fixed_width_rtype(rtype):
right = self.coerce(right, ltype, e.line)
reg = self.binary_op(left, right, op, e.line)
self.builder.flush_keep_alives()
self.add_bool_branch(reg, true, false)
else:
# "left op right" for two tagged integers
self.builder.compare_tagged_condition(left, right, op, true, false, e.line)
return True
def is_borrow_friendly_expr(self: IRBuilder, expr: Expression) -> bool:
"""Can the result of the expression borrowed temporarily?
Borrowing means keeping a reference without incrementing the reference count.
"""
if isinstance(expr, (IntExpr, FloatExpr, StrExpr, BytesExpr)):
# Literals are immortal and can always be borrowed
return True
if (
isinstance(expr, (UnaryExpr, OpExpr, NameExpr, MemberExpr))
and constant_fold_expr(self, expr) is not None
):
# Literal expressions are similar to literals
return True
if isinstance(expr, NameExpr):
if isinstance(expr.node, Var) and expr.kind == LDEF:
# Local variable reference can be borrowed
return True
if isinstance(expr, MemberExpr) and self.is_native_attr_ref(expr):
return True
return False