| """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 |