diff --git a/crates/ty_python_semantic/resources/mdtest/comparison/tuples.md b/crates/ty_python_semantic/resources/mdtest/comparison/tuples.md index 832029c881..97c712735a 100644 --- a/crates/ty_python_semantic/resources/mdtest/comparison/tuples.md +++ b/crates/ty_python_semantic/resources/mdtest/comparison/tuples.md @@ -338,7 +338,111 @@ reveal_type(a is not c) # revealed: Literal[True] For tuples like `tuple[int, ...]`, `tuple[Any, ...]` -// TODO +### Unsupported Comparisons + + + +Comparisons between homogeneous tuples with incompatible element types should emit diagnostics for +ordering operators (`<`, `<=`, `>`, `>=`), but not for equality operators (`==`, `!=`). + +```py +def f( + a: tuple[int, ...], + b: tuple[str, ...], + c: tuple[str], +): + # Equality comparisons are always valid + reveal_type(a == b) # revealed: bool + reveal_type(a != b) # revealed: bool + + # Ordering comparisons between incompatible types should emit errors + # error: [unsupported-operator] "Operator `<` is not supported between objects of type `tuple[int, ...]` and `tuple[str, ...]`" + a < b + # error: [unsupported-operator] "Operator `<` is not supported between objects of type `tuple[str, ...]` and `tuple[int, ...]`" + b < a + # error: [unsupported-operator] "Operator `<` is not supported between objects of type `tuple[int, ...]` and `tuple[str]`" + a < c + # error: [unsupported-operator] "Operator `<` is not supported between objects of type `tuple[str]` and `tuple[int, ...]`" + c < a +``` + +When comparing fixed-length tuples with variable-length tuples, all element types that could +potentially be compared must be compatible. + +```py +def _( + var_int: tuple[int, ...], + var_str: tuple[str, ...], + fixed_int_str: tuple[int, str], +): + # Fixed `tuple[int, str]` vs. variable `tuple[int, ...]`: + # Position 0: `int` vs. `int` are comparable. + # Position 1 (if `var_int` has 2+ elements): `str` vs. `int` are not comparable. + # error: [unsupported-operator] + fixed_int_str < var_int + + # Variable `tuple[int, ...]` vs. fixed `tuple[int, str]`: + # Position 0: `int` vs. `int` are comparable. + # Position 1 (if `var_int` has 2+ elements): `int` vs. `str` are not comparable. + # error: [unsupported-operator] + var_int < fixed_int_str + + # Variable `tuple[str, ...]` vs. fixed `tuple[int, str]`: + # Position 0: `str` vs. `int` are not comparable. + # error: [unsupported-operator] + var_str < fixed_int_str +``` + +### Supported Comparisons + +Comparisons between homogeneous tuples with compatible element types should work. + +```py +def _(a: tuple[int, ...], b: tuple[int, ...], c: tuple[bool, ...]): + # Same element types - always valid + reveal_type(a == b) # revealed: bool + reveal_type(a != b) # revealed: bool + reveal_type(a < b) # revealed: bool + reveal_type(a <= b) # revealed: bool + reveal_type(a > b) # revealed: bool + reveal_type(a >= b) # revealed: bool + + # int and bool are compatible for comparison + reveal_type(a < c) # revealed: bool + reveal_type(c < a) # revealed: bool +``` + +### Tuples with Prefixes and Suffixes + + + +Variable-length tuples with prefixes and suffixes are also checked. + +```toml +[environment] +python-version = "3.11" +``` + +```py +def _( + prefix_int_var_str: tuple[int, *tuple[str, ...]], + prefix_str_var_int: tuple[str, *tuple[int, ...]], +): + # Prefix `int` vs. prefix `str` are not comparable. + # error: [unsupported-operator] + prefix_int_var_str < prefix_str_var_int +``` + +Tuples with compatible prefixes/suffixes are allowed. + +```py +def _( + prefix_int_var_int: tuple[int, *tuple[int, ...]], + prefix_int_var_bool: tuple[int, *tuple[bool, ...]], +): + # Prefix `int` vs. prefix `int`, variable `int` vs. variable `bool` are all comparable. + reveal_type(prefix_int_var_int < prefix_int_var_bool) # revealed: bool +``` ## Chained comparisons with elements that incorrectly implement `__bool__` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Homogeneous_-_Tuples_with_Prefixes…_(c25079c01f6d8eb3).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Homogeneous_-_Tuples_with_Prefixes…_(c25079c01f6d8eb3).snap new file mode 100644 index 0000000000..6b5881dde5 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Homogeneous_-_Tuples_with_Prefixes…_(c25079c01f6d8eb3).snap @@ -0,0 +1,49 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: tuples.md - Comparison: Tuples - Homogeneous - Tuples with Prefixes and Suffixes +mdtest path: crates/ty_python_semantic/resources/mdtest/comparison/tuples.md +--- + +# Python source files + +## mdtest_snippet.py + +``` + 1 | def _( + 2 | prefix_int_var_str: tuple[int, *tuple[str, ...]], + 3 | prefix_str_var_int: tuple[str, *tuple[int, ...]], + 4 | ): + 5 | # Prefix `int` vs. prefix `str` are not comparable. + 6 | # error: [unsupported-operator] + 7 | prefix_int_var_str < prefix_str_var_int + 8 | def _( + 9 | prefix_int_var_int: tuple[int, *tuple[int, ...]], +10 | prefix_int_var_bool: tuple[int, *tuple[bool, ...]], +11 | ): +12 | # Prefix `int` vs. prefix `int`, variable `int` vs. variable `bool` are all comparable. +13 | reveal_type(prefix_int_var_int < prefix_int_var_bool) # revealed: bool +``` + +# Diagnostics + +``` +error[unsupported-operator]: Unsupported `<` operation + --> src/mdtest_snippet.py:7:5 + | +5 | # Prefix `int` vs. prefix `str` are not comparable. +6 | # error: [unsupported-operator] +7 | prefix_int_var_str < prefix_str_var_int + | ------------------^^^------------------ + | | | + | | Has type `tuple[str, *tuple[int, ...]]` + | Has type `tuple[int, *tuple[str, ...]]` +8 | def _( +9 | prefix_int_var_int: tuple[int, *tuple[int, ...]], + | +info: Operation fails because operator `<` is not supported between objects of type `int` and `str` +info: rule `unsupported-operator` is enabled by default + +``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Homogeneous_-_Unsupported_Comparis…_(400a427b33d53e00).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Homogeneous_-_Unsupported_Comparis…_(400a427b33d53e00).snap new file mode 100644 index 0000000000..47d2dc93f9 --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/tuples.md_-_Comparison___Tuples_-_Homogeneous_-_Unsupported_Comparis…_(400a427b33d53e00).snap @@ -0,0 +1,187 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: tuples.md - Comparison: Tuples - Homogeneous - Unsupported Comparisons +mdtest path: crates/ty_python_semantic/resources/mdtest/comparison/tuples.md +--- + +# Python source files + +## mdtest_snippet.py + +``` + 1 | def f( + 2 | a: tuple[int, ...], + 3 | b: tuple[str, ...], + 4 | c: tuple[str], + 5 | ): + 6 | # Equality comparisons are always valid + 7 | reveal_type(a == b) # revealed: bool + 8 | reveal_type(a != b) # revealed: bool + 9 | +10 | # Ordering comparisons between incompatible types should emit errors +11 | # error: [unsupported-operator] "Operator `<` is not supported between objects of type `tuple[int, ...]` and `tuple[str, ...]`" +12 | a < b +13 | # error: [unsupported-operator] "Operator `<` is not supported between objects of type `tuple[str, ...]` and `tuple[int, ...]`" +14 | b < a +15 | # error: [unsupported-operator] "Operator `<` is not supported between objects of type `tuple[int, ...]` and `tuple[str]`" +16 | a < c +17 | # error: [unsupported-operator] "Operator `<` is not supported between objects of type `tuple[str]` and `tuple[int, ...]`" +18 | c < a +19 | def _( +20 | var_int: tuple[int, ...], +21 | var_str: tuple[str, ...], +22 | fixed_int_str: tuple[int, str], +23 | ): +24 | # Fixed `tuple[int, str]` vs. variable `tuple[int, ...]`: +25 | # Position 0: `int` vs. `int` are comparable. +26 | # Position 1 (if `var_int` has 2+ elements): `str` vs. `int` are not comparable. +27 | # error: [unsupported-operator] +28 | fixed_int_str < var_int +29 | +30 | # Variable `tuple[int, ...]` vs. fixed `tuple[int, str]`: +31 | # Position 0: `int` vs. `int` are comparable. +32 | # Position 1 (if `var_int` has 2+ elements): `int` vs. `str` are not comparable. +33 | # error: [unsupported-operator] +34 | var_int < fixed_int_str +35 | +36 | # Variable `tuple[str, ...]` vs. fixed `tuple[int, str]`: +37 | # Position 0: `str` vs. `int` are not comparable. +38 | # error: [unsupported-operator] +39 | var_str < fixed_int_str +``` + +# Diagnostics + +``` +error[unsupported-operator]: Unsupported `<` operation + --> src/mdtest_snippet.py:12:5 + | +10 | # Ordering comparisons between incompatible types should emit errors +11 | # error: [unsupported-operator] "Operator `<` is not supported between objects of type `tuple[int, ...]` and `tuple[str, ...]`" +12 | a < b + | -^^^- + | | | + | | Has type `tuple[str, ...]` + | Has type `tuple[int, ...]` +13 | # error: [unsupported-operator] "Operator `<` is not supported between objects of type `tuple[str, ...]` and `tuple[int, ...]`" +14 | b < a + | +info: Operation fails because operator `<` is not supported between objects of type `int` and `str` +info: rule `unsupported-operator` is enabled by default + +``` + +``` +error[unsupported-operator]: Unsupported `<` operation + --> src/mdtest_snippet.py:14:5 + | +12 | a < b +13 | # error: [unsupported-operator] "Operator `<` is not supported between objects of type `tuple[str, ...]` and `tuple[int, ...]`" +14 | b < a + | -^^^- + | | | + | | Has type `tuple[int, ...]` + | Has type `tuple[str, ...]` +15 | # error: [unsupported-operator] "Operator `<` is not supported between objects of type `tuple[int, ...]` and `tuple[str]`" +16 | a < c + | +info: Operation fails because operator `<` is not supported between objects of type `str` and `int` +info: rule `unsupported-operator` is enabled by default + +``` + +``` +error[unsupported-operator]: Unsupported `<` operation + --> src/mdtest_snippet.py:16:5 + | +14 | b < a +15 | # error: [unsupported-operator] "Operator `<` is not supported between objects of type `tuple[int, ...]` and `tuple[str]`" +16 | a < c + | -^^^- + | | | + | | Has type `tuple[str]` + | Has type `tuple[int, ...]` +17 | # error: [unsupported-operator] "Operator `<` is not supported between objects of type `tuple[str]` and `tuple[int, ...]`" +18 | c < a + | +info: Operation fails because operator `<` is not supported between objects of type `int` and `str` +info: rule `unsupported-operator` is enabled by default + +``` + +``` +error[unsupported-operator]: Unsupported `<` operation + --> src/mdtest_snippet.py:18:5 + | +16 | a < c +17 | # error: [unsupported-operator] "Operator `<` is not supported between objects of type `tuple[str]` and `tuple[int, ...]`" +18 | c < a + | -^^^- + | | | + | | Has type `tuple[int, ...]` + | Has type `tuple[str]` +19 | def _( +20 | var_int: tuple[int, ...], + | +info: Operation fails because operator `<` is not supported between objects of type `str` and `int` +info: rule `unsupported-operator` is enabled by default + +``` + +``` +error[unsupported-operator]: Unsupported `<` operation + --> src/mdtest_snippet.py:28:5 + | +26 | # Position 1 (if `var_int` has 2+ elements): `str` vs. `int` are not comparable. +27 | # error: [unsupported-operator] +28 | fixed_int_str < var_int + | -------------^^^------- + | | | + | | Has type `tuple[int, ...]` + | Has type `tuple[int, str]` +29 | +30 | # Variable `tuple[int, ...]` vs. fixed `tuple[int, str]`: + | +info: Operation fails because operator `<` is not supported between objects of type `str` and `int` +info: rule `unsupported-operator` is enabled by default + +``` + +``` +error[unsupported-operator]: Unsupported `<` operation + --> src/mdtest_snippet.py:34:5 + | +32 | # Position 1 (if `var_int` has 2+ elements): `int` vs. `str` are not comparable. +33 | # error: [unsupported-operator] +34 | var_int < fixed_int_str + | -------^^^------------- + | | | + | | Has type `tuple[int, str]` + | Has type `tuple[int, ...]` +35 | +36 | # Variable `tuple[str, ...]` vs. fixed `tuple[int, str]`: + | +info: Operation fails because operator `<` is not supported between objects of type `int` and `str` +info: rule `unsupported-operator` is enabled by default + +``` + +``` +error[unsupported-operator]: Unsupported `<` operation + --> src/mdtest_snippet.py:39:5 + | +37 | # Position 0: `str` vs. `int` are not comparable. +38 | # error: [unsupported-operator] +39 | var_str < fixed_int_str + | -------^^^------------- + | | | + | | Has type `tuple[int, str]` + | Has type `tuple[str, ...]` + | +info: Operation fails because operator `<` is not supported between objects of type `str` and `int` +info: rule `unsupported-operator` is enabled by default + +``` diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index c93269ade2..ab4d7240e3 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -11191,84 +11191,300 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { range: TextRange, visitor: &BinaryComparisonVisitor<'db>, ) -> Result, UnsupportedComparisonError<'db>> { - // If either tuple is variable length, we can make no assumptions about the relative - // lengths of the tuples, and therefore neither about how they compare lexicographically. - // TODO: Consider comparing the prefixes of the tuples, since that could give a comparison - // result regardless of how long the variable-length tuple is. - let (TupleSpec::Fixed(left), TupleSpec::Fixed(right)) = (left, right) else { - return Ok(Type::unknown()); - }; + match (left, right) { + // Both fixed-length: perform full lexicographic comparison. + (TupleSpec::Fixed(left), TupleSpec::Fixed(right)) => { + let left_iter = left.elements().copied(); + let right_iter = right.elements().copied(); - let left_iter = left.elements().copied(); - let right_iter = right.elements().copied(); + let mut builder = UnionBuilder::new(self.db()); - let mut builder = UnionBuilder::new(self.db()); + for (l_ty, r_ty) in left_iter.zip(right_iter) { + let pairwise_eq_result = self + .infer_binary_type_comparison(l_ty, ast::CmpOp::Eq, r_ty, range, visitor) + .expect( + "infer_binary_type_comparison should never return None for `CmpOp::Eq`", + ); - for (l_ty, r_ty) in left_iter.zip(right_iter) { - let pairwise_eq_result = self - .infer_binary_type_comparison(l_ty, ast::CmpOp::Eq, r_ty, range, visitor) - .expect("infer_binary_type_comparison should never return None for `CmpOp::Eq`"); + match pairwise_eq_result + .try_bool(self.db()) + .unwrap_or_else(|err| { + // TODO: We should, whenever possible, pass the range of the left and right elements + // instead of the range of the whole tuple. + err.report_diagnostic(&self.context, range); + err.fallback_truthiness() + }) { + // - AlwaysTrue : Continue to the next pair for lexicographic comparison + Truthiness::AlwaysTrue => continue, + // - AlwaysFalse: + // Lexicographic comparisons will always terminate with this pair. + // Complete the comparison and return the result. + // - Ambiguous: + // Lexicographic comparisons might continue to the next pair (if eq_result is true), + // or terminate here (if eq_result is false). + // To account for cases where the comparison terminates here, add the pairwise comparison result to the union builder. + eq_truthiness @ (Truthiness::AlwaysFalse | Truthiness::Ambiguous) => { + let pairwise_compare_result = match op { + RichCompareOperator::Lt + | RichCompareOperator::Le + | RichCompareOperator::Gt + | RichCompareOperator::Ge => self.infer_binary_type_comparison( + l_ty, + op.into(), + r_ty, + range, + visitor, + )?, + // For `==` and `!=`, we already figure out the result from `pairwise_eq_result` + // NOTE: The CPython implementation does not account for non-boolean return types + // or cases where `!=` is not the negation of `==`, we also do not consider these cases. + RichCompareOperator::Eq => Type::BooleanLiteral(false), + RichCompareOperator::Ne => Type::BooleanLiteral(true), + }; - match pairwise_eq_result - .try_bool(self.db()) - .unwrap_or_else(|err| { - // TODO: We should, whenever possible, pass the range of the left and right elements - // instead of the range of the whole tuple. - err.report_diagnostic(&self.context, range); - err.fallback_truthiness() - }) { - // - AlwaysTrue : Continue to the next pair for lexicographic comparison - Truthiness::AlwaysTrue => continue, - // - AlwaysFalse: - // Lexicographic comparisons will always terminate with this pair. - // Complete the comparison and return the result. - // - Ambiguous: - // Lexicographic comparisons might continue to the next pair (if eq_result is true), - // or terminate here (if eq_result is false). - // To account for cases where the comparison terminates here, add the pairwise comparison result to the union builder. - eq_truthiness @ (Truthiness::AlwaysFalse | Truthiness::Ambiguous) => { - let pairwise_compare_result = match op { - RichCompareOperator::Lt - | RichCompareOperator::Le - | RichCompareOperator::Gt - | RichCompareOperator::Ge => self.infer_binary_type_comparison( - l_ty, - op.into(), - r_ty, - range, - visitor, - )?, - // For `==` and `!=`, we already figure out the result from `pairwise_eq_result` - // NOTE: The CPython implementation does not account for non-boolean return types - // or cases where `!=` is not the negation of `==`, we also do not consider these cases. - RichCompareOperator::Eq => Type::BooleanLiteral(false), - RichCompareOperator::Ne => Type::BooleanLiteral(true), - }; + builder = builder.add(pairwise_compare_result); - builder = builder.add(pairwise_compare_result); + if eq_truthiness.is_ambiguous() { + continue; + } - if eq_truthiness.is_ambiguous() { - continue; + return Ok(builder.build()); + } } - - return Ok(builder.build()); } + + // if no more items to compare, we just compare sizes + let (left_len, right_len) = (left.len(), right.len()); + + builder = builder.add(Type::BooleanLiteral(match op { + RichCompareOperator::Eq => left_len == right_len, + RichCompareOperator::Ne => left_len != right_len, + RichCompareOperator::Lt => left_len < right_len, + RichCompareOperator::Le => left_len <= right_len, + RichCompareOperator::Gt => left_len > right_len, + RichCompareOperator::Ge => left_len >= right_len, + })); + + Ok(builder.build()) + } + + // At least one tuple is variable-length. We can make no assumptions about + // the relative lengths of the tuples, and therefore neither about how they + // compare lexicographically. However, we still need to verify that the + // element types are comparable for ordering comparisons. + + // For equality comparisons (==, !=), any two objects can be compared, + // and tuple equality always returns bool regardless of element __eq__ return types. + (TupleSpec::Variable(_), _) | (_, TupleSpec::Variable(_)) + if matches!(op, RichCompareOperator::Eq | RichCompareOperator::Ne) => + { + Ok(KnownClass::Bool.to_instance(self.db())) + } + + // Both variable: check all elements that could potentially be compared. + (TupleSpec::Variable(left_var), TupleSpec::Variable(right_var)) => { + let mut builder = UnionBuilder::new(self.db()); + + // 1. Compare prefix elements at matching positions. + for (l_el, r_el) in left_var.prefix_elements().zip(right_var.prefix_elements()) { + builder = builder.add(self.infer_binary_type_comparison( + *l_el, + op.into(), + *r_el, + range, + visitor, + )?); + } + + // 2. Left's extra prefix elements are compared with right's variable. + for l_el in left_var.prefix_elements().skip(right_var.prefix.len()) { + builder = builder.add(self.infer_binary_type_comparison( + *l_el, + op.into(), + right_var.variable, + range, + visitor, + )?); + } + + // 3. Right's extra prefix elements are compared with left's variable. + for r_el in right_var.prefix_elements().skip(left_var.prefix.len()) { + builder = builder.add(self.infer_binary_type_comparison( + left_var.variable, + op.into(), + *r_el, + range, + visitor, + )?); + } + + // 4. Variable elements can be compared at any overlapping position. + builder = builder.add(self.infer_binary_type_comparison( + left_var.variable, + op.into(), + right_var.variable, + range, + visitor, + )?); + + // 5. Left's extra suffix elements are compared with right's variable. + for l_el in left_var + .suffix_elements() + .rev() + .skip(right_var.suffix.len()) + { + builder = builder.add(self.infer_binary_type_comparison( + *l_el, + op.into(), + right_var.variable, + range, + visitor, + )?); + } + + // 6. Right's extra suffix elements are compared with left's variable. + for r_el in right_var + .suffix_elements() + .rev() + .skip(left_var.suffix.len()) + { + builder = builder.add(self.infer_binary_type_comparison( + left_var.variable, + op.into(), + *r_el, + range, + visitor, + )?); + } + + // 7. Compare suffix elements at matching positions (from the end). + for (l_el, r_el) in left_var + .suffix_elements() + .rev() + .zip(right_var.suffix_elements().rev()) + { + builder = builder.add(self.infer_binary_type_comparison( + *l_el, + op.into(), + *r_el, + range, + visitor, + )?); + } + + // Length comparison (when all elements are equal) returns bool. + builder = builder.add(KnownClass::Bool.to_instance(self.db())); + + Ok(builder.build()) + } + + // Left variable, right fixed: check which elements could be compared. + (TupleSpec::Variable(left_var), TupleSpec::Fixed(right_fixed)) => { + let mut builder = UnionBuilder::new(self.db()); + + // Compare left's prefix with right's corresponding elements. + for (l_el, r_el) in left_var.prefix_elements().zip(right_fixed.elements()) { + builder = builder.add(self.infer_binary_type_comparison( + *l_el, + op.into(), + *r_el, + range, + visitor, + )?); + } + + // Compare left's suffix with right's corresponding elements (from end). + for (l_el, r_el) in left_var + .suffix_elements() + .rev() + .zip(right_fixed.elements().rev()) + { + builder = builder.add(self.infer_binary_type_comparison( + *l_el, + op.into(), + *r_el, + range, + visitor, + )?); + } + + // Compare left's variable with right's "middle" elements + // (those not covered by prefix or suffix). + let middle_start = left_var.prefix.len(); + let middle_end = right_fixed.len().saturating_sub(left_var.suffix.len()); + for r_el in right_fixed + .elements() + .skip(middle_start) + .take(middle_end.saturating_sub(middle_start)) + { + builder = builder.add(self.infer_binary_type_comparison( + left_var.variable, + op.into(), + *r_el, + range, + visitor, + )?); + } + + // Length comparison (when all elements are equal) returns bool. + builder = builder.add(KnownClass::Bool.to_instance(self.db())); + + Ok(builder.build()) + } + + // Left fixed, right variable: check which elements could be compared. + (TupleSpec::Fixed(left_fixed), TupleSpec::Variable(right_var)) => { + let mut builder = UnionBuilder::new(self.db()); + + // Compare left's elements with right's prefix. + for (l_el, r_el) in left_fixed.elements().zip(right_var.prefix_elements()) { + builder = builder.add(self.infer_binary_type_comparison( + *l_el, + op.into(), + *r_el, + range, + visitor, + )?); + } + + // Compare left's elements (from end) with right's suffix. + for (l_el, r_el) in left_fixed + .elements() + .rev() + .zip(right_var.suffix_elements().rev()) + { + builder = builder.add(self.infer_binary_type_comparison( + *l_el, + op.into(), + *r_el, + range, + visitor, + )?); + } + + // Compare left's "middle" elements with right's variable. + let middle_start = right_var.prefix.len(); + let middle_end = left_fixed.len().saturating_sub(right_var.suffix.len()); + for l_el in left_fixed + .elements() + .skip(middle_start) + .take(middle_end.saturating_sub(middle_start)) + { + builder = builder.add(self.infer_binary_type_comparison( + *l_el, + op.into(), + right_var.variable, + range, + visitor, + )?); + } + + // Length comparison (when all elements are equal) returns bool. + builder = builder.add(KnownClass::Bool.to_instance(self.db())); + + Ok(builder.build()) } } - - // if no more items to compare, we just compare sizes - let (left_len, right_len) = (left.len(), right.len()); - - builder = builder.add(Type::BooleanLiteral(match op { - RichCompareOperator::Eq => left_len == right_len, - RichCompareOperator::Ne => left_len != right_len, - RichCompareOperator::Lt => left_len < right_len, - RichCompareOperator::Le => left_len <= right_len, - RichCompareOperator::Gt => left_len > right_len, - RichCompareOperator::Ge => left_len >= right_len, - })); - - Ok(builder.build()) } fn infer_subscript_expression(&mut self, subscript: &ast::ExprSubscript) -> Type<'db> {