(
self,
op_name: str,
left_type: Type,
left_expr: Expression,
right_type: Type,
right_expr: Expression,
context: Context,
)
| 4111 | return None |
| 4112 | |
| 4113 | def check_op_reversible( |
| 4114 | self, |
| 4115 | op_name: str, |
| 4116 | left_type: Type, |
| 4117 | left_expr: Expression, |
| 4118 | right_type: Type, |
| 4119 | right_expr: Expression, |
| 4120 | context: Context, |
| 4121 | ) -> tuple[Type, Type]: |
| 4122 | left_type = get_proper_type(left_type) |
| 4123 | right_type = get_proper_type(right_type) |
| 4124 | |
| 4125 | # If either the LHS or the RHS are Any, we can't really conclude anything |
| 4126 | # about the operation since the Any type may or may not define an |
| 4127 | # __op__ or __rop__ method. So, we punt and return Any instead. |
| 4128 | |
| 4129 | if isinstance(left_type, AnyType): |
| 4130 | any_type = AnyType(TypeOfAny.from_another_any, source_any=left_type) |
| 4131 | return any_type, any_type |
| 4132 | if isinstance(right_type, AnyType): |
| 4133 | any_type = AnyType(TypeOfAny.from_another_any, source_any=right_type) |
| 4134 | return any_type, any_type |
| 4135 | |
| 4136 | # STEP 1: |
| 4137 | # We start by getting the __op__ and __rop__ methods, if they exist. |
| 4138 | |
| 4139 | rev_op_name = operators.reverse_op_methods[op_name] |
| 4140 | |
| 4141 | left_op = self.lookup_operator(op_name, left_type, context) |
| 4142 | right_op = self.lookup_operator(rev_op_name, right_type, context) |
| 4143 | |
| 4144 | # STEP 2a: |
| 4145 | # We figure out in which order Python will call the operator methods. As it |
| 4146 | # turns out, it's not as simple as just trying to call __op__ first and |
| 4147 | # __rop__ second. |
| 4148 | # |
| 4149 | # We store the determined order inside the 'variants_raw' variable, |
| 4150 | # which records tuples containing the method, base type, and the argument. |
| 4151 | |
| 4152 | if op_name in operators.op_methods_that_shortcut and is_same_type(left_type, right_type): |
| 4153 | # When we do "A() + A()", for example, Python will only call the __add__ method, |
| 4154 | # never the __radd__ method. |
| 4155 | # |
| 4156 | # This is the case even if the __add__ method is completely missing and the __radd__ |
| 4157 | # method is defined. |
| 4158 | |
| 4159 | variants_raw = [(op_name, left_op, left_type, right_expr)] |
| 4160 | elif ( |
| 4161 | ( |
| 4162 | # Checking (A implies B) using the logically equivalent (not A or B), where |
| 4163 | # A: left and right are both `Instance` objects |
| 4164 | # B: right's __rop__ method is different from left's __op__ method |
| 4165 | not (isinstance(left_type, Instance) and isinstance(right_type, Instance)) |
| 4166 | or ( |
| 4167 | self.lookup_definer(left_type, op_name) |
| 4168 | != self.lookup_definer(right_type, rev_op_name) |
| 4169 | and ( |
| 4170 | left_type.type.alt_promote is None |
no test coverage detected