Type check a comparison expression. Comparison expressions are type checked consecutive-pair-wise That is, 'a < b > c == d' is check as 'a < b and b > c and c == d'
(self, e: ComparisonExpr)
| 3662 | raise RuntimeError(f"Unknown operator {e.op}") |
| 3663 | |
| 3664 | def visit_comparison_expr(self, e: ComparisonExpr) -> Type: |
| 3665 | """Type check a comparison expression. |
| 3666 | |
| 3667 | Comparison expressions are type checked consecutive-pair-wise |
| 3668 | That is, 'a < b > c == d' is check as 'a < b and b > c and c == d' |
| 3669 | """ |
| 3670 | result: Type | None = None |
| 3671 | sub_result: Type |
| 3672 | |
| 3673 | # Check each consecutive operand pair and their operator |
| 3674 | for left, right, operator in zip(e.operands, e.operands[1:], e.operators): |
| 3675 | left_type = self.accept(left) |
| 3676 | |
| 3677 | if operator == "in" or operator == "not in": |
| 3678 | # This case covers both iterables and containers, which have different meanings. |
| 3679 | # For a container, the in operator calls the __contains__ method. |
| 3680 | # For an iterable, the in operator iterates over the iterable, and compares each item one-by-one. |
| 3681 | # We allow `in` for a union of containers and iterables as long as at least one of them matches the |
| 3682 | # type of the left operand, as the operation will simply return False if the union's container/iterator |
| 3683 | # type doesn't match the left operand. |
| 3684 | |
| 3685 | # If the right operand has partial type, look it up without triggering |
| 3686 | # a "Need type annotation ..." message, as it would be noise. |
| 3687 | right_type = self.find_partial_type_ref_fast_path(right) |
| 3688 | if right_type is None: |
| 3689 | right_type = self.accept(right) # Validate the right operand |
| 3690 | |
| 3691 | right_type = get_proper_type(right_type) |
| 3692 | item_types: Sequence[Type] = [right_type] |
| 3693 | if isinstance(right_type, UnionType): |
| 3694 | item_types = list(right_type.relevant_items()) |
| 3695 | |
| 3696 | sub_result = self.bool_type() |
| 3697 | |
| 3698 | container_types: list[Type] = [] |
| 3699 | iterable_types: list[Type] = [] |
| 3700 | failed_out = False |
| 3701 | encountered_partial_type = False |
| 3702 | |
| 3703 | for item_type in item_types: |
| 3704 | # Keep track of whether we get type check errors (these won't be reported, they |
| 3705 | # are just to verify whether something is valid typing wise). |
| 3706 | with self.msg.filter_errors(save_filtered_errors=True) as container_errors: |
| 3707 | _, method_type = self.check_method_call_by_name( |
| 3708 | method="__contains__", |
| 3709 | base_type=item_type, |
| 3710 | args=[left], |
| 3711 | arg_kinds=[ARG_POS], |
| 3712 | context=e, |
| 3713 | original_type=right_type, |
| 3714 | ) |
| 3715 | # Container item type for strict type overlap checks. Note: we need to only |
| 3716 | # check for nominal type, because a usual "Unsupported operands for in" |
| 3717 | # will be reported for types incompatible with __contains__(). |
| 3718 | # See testCustomContainsCheckStrictEquality for an example. |
| 3719 | cont_type = self.chk.analyze_container_item_type(item_type) |
| 3720 | |
| 3721 | if isinstance(item_type, PartialType): |
nothing calls this directly
no test coverage detected