mirror of https://github.com/astral-sh/ruff
Return bool
This commit is contained in:
parent
e20d1a837a
commit
8ef82e96fc
|
|
@ -352,8 +352,8 @@ def f(
|
||||||
c: tuple[str],
|
c: tuple[str],
|
||||||
):
|
):
|
||||||
# Equality comparisons are always valid
|
# Equality comparisons are always valid
|
||||||
reveal_type(a == b) # revealed: Unknown
|
reveal_type(a == b) # revealed: bool
|
||||||
reveal_type(a != b) # revealed: Unknown
|
reveal_type(a != b) # revealed: bool
|
||||||
|
|
||||||
# Ordering comparisons between incompatible types should emit errors
|
# Ordering comparisons between incompatible types should emit errors
|
||||||
# error: [unsupported-operator] "Operator `<` is not supported between objects of type `tuple[int, ...]` and `tuple[str, ...]`"
|
# error: [unsupported-operator] "Operator `<` is not supported between objects of type `tuple[int, ...]` and `tuple[str, ...]`"
|
||||||
|
|
@ -400,16 +400,16 @@ Comparisons between homogeneous tuples with compatible element types should work
|
||||||
```py
|
```py
|
||||||
def _(a: tuple[int, ...], b: tuple[int, ...], c: tuple[bool, ...]):
|
def _(a: tuple[int, ...], b: tuple[int, ...], c: tuple[bool, ...]):
|
||||||
# Same element types - always valid
|
# Same element types - always valid
|
||||||
reveal_type(a == b) # revealed: Unknown
|
reveal_type(a == b) # revealed: bool
|
||||||
reveal_type(a != b) # revealed: Unknown
|
reveal_type(a != b) # revealed: bool
|
||||||
reveal_type(a < b) # revealed: Unknown
|
reveal_type(a < b) # revealed: bool
|
||||||
reveal_type(a <= b) # revealed: Unknown
|
reveal_type(a <= b) # revealed: bool
|
||||||
reveal_type(a > b) # revealed: Unknown
|
reveal_type(a > b) # revealed: bool
|
||||||
reveal_type(a >= b) # revealed: Unknown
|
reveal_type(a >= b) # revealed: bool
|
||||||
|
|
||||||
# int and bool are compatible for comparison
|
# int and bool are compatible for comparison
|
||||||
reveal_type(a < c) # revealed: Unknown
|
reveal_type(a < c) # revealed: bool
|
||||||
reveal_type(c < a) # revealed: Unknown
|
reveal_type(c < a) # revealed: bool
|
||||||
```
|
```
|
||||||
|
|
||||||
### Tuples with Prefixes and Suffixes
|
### Tuples with Prefixes and Suffixes
|
||||||
|
|
@ -441,7 +441,7 @@ def _(
|
||||||
prefix_int_var_bool: tuple[int, *tuple[bool, ...]],
|
prefix_int_var_bool: tuple[int, *tuple[bool, ...]],
|
||||||
):
|
):
|
||||||
# Prefix `int` vs. prefix `int`, variable `int` vs. variable `bool` are all comparable.
|
# Prefix `int` vs. prefix `int`, variable `int` vs. variable `bool` are all comparable.
|
||||||
reveal_type(prefix_int_var_int < prefix_int_var_bool) # revealed: Unknown
|
reveal_type(prefix_int_var_int < prefix_int_var_bool) # revealed: bool
|
||||||
```
|
```
|
||||||
|
|
||||||
## Chained comparisons with elements that incorrectly implement `__bool__`
|
## Chained comparisons with elements that incorrectly implement `__bool__`
|
||||||
|
|
|
||||||
|
|
@ -16,15 +16,15 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/comparison/tuples.md
|
||||||
2 | prefix_int_var_str: tuple[int, *tuple[str, ...]],
|
2 | prefix_int_var_str: tuple[int, *tuple[str, ...]],
|
||||||
3 | prefix_str_var_int: tuple[str, *tuple[int, ...]],
|
3 | prefix_str_var_int: tuple[str, *tuple[int, ...]],
|
||||||
4 | ):
|
4 | ):
|
||||||
5 | # Prefix int vs prefix str - NOT ok
|
5 | # Prefix `int` vs. prefix `str` are not comparable.
|
||||||
6 | # error: [unsupported-operator]
|
6 | # error: [unsupported-operator]
|
||||||
7 | prefix_int_var_str < prefix_str_var_int
|
7 | prefix_int_var_str < prefix_str_var_int
|
||||||
8 | def _(
|
8 | def _(
|
||||||
9 | prefix_int_var_int: tuple[int, *tuple[int, ...]],
|
9 | prefix_int_var_int: tuple[int, *tuple[int, ...]],
|
||||||
10 | prefix_int_var_bool: tuple[int, *tuple[bool, ...]],
|
10 | prefix_int_var_bool: tuple[int, *tuple[bool, ...]],
|
||||||
11 | ):
|
11 | ):
|
||||||
12 | # Prefix int vs prefix int, variable int vs variable bool - all ok
|
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: Unknown
|
13 | reveal_type(prefix_int_var_int < prefix_int_var_bool) # revealed: bool
|
||||||
```
|
```
|
||||||
|
|
||||||
# Diagnostics
|
# Diagnostics
|
||||||
|
|
@ -33,7 +33,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/comparison/tuples.md
|
||||||
error[unsupported-operator]: Unsupported `<` operation
|
error[unsupported-operator]: Unsupported `<` operation
|
||||||
--> src/mdtest_snippet.py:7:5
|
--> src/mdtest_snippet.py:7:5
|
||||||
|
|
|
|
||||||
5 | # Prefix int vs prefix str - NOT ok
|
5 | # Prefix `int` vs. prefix `str` are not comparable.
|
||||||
6 | # error: [unsupported-operator]
|
6 | # error: [unsupported-operator]
|
||||||
7 | prefix_int_var_str < prefix_str_var_int
|
7 | prefix_int_var_str < prefix_str_var_int
|
||||||
| ------------------^^^------------------
|
| ------------------^^^------------------
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/comparison/tuples.md
|
||||||
4 | c: tuple[str],
|
4 | c: tuple[str],
|
||||||
5 | ):
|
5 | ):
|
||||||
6 | # Equality comparisons are always valid
|
6 | # Equality comparisons are always valid
|
||||||
7 | reveal_type(a == b) # revealed: Unknown
|
7 | reveal_type(a == b) # revealed: bool
|
||||||
8 | reveal_type(a != b) # revealed: Unknown
|
8 | reveal_type(a != b) # revealed: bool
|
||||||
9 |
|
9 |
|
||||||
10 | # Ordering comparisons between incompatible types should emit errors
|
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, ...]`"
|
11 | # error: [unsupported-operator] "Operator `<` is not supported between objects of type `tuple[int, ...]` and `tuple[str, ...]`"
|
||||||
|
|
|
||||||
|
|
@ -10478,7 +10478,6 @@ impl<'db> TypeVarBoundOrConstraints<'db> {
|
||||||
.zip(prev_elements.iter())
|
.zip(prev_elements.iter())
|
||||||
.map(|(ty, prev_ty)| ty.cycle_normalized(db, *prev_ty, cycle))
|
.map(|(ty, prev_ty)| ty.cycle_normalized(db, *prev_ty, cycle))
|
||||||
.collect::<Box<_>>(),
|
.collect::<Box<_>>(),
|
||||||
RecursivelyDefined::No,
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
// The choice of whether it's an upper bound or constraints is purely syntactic and
|
// The choice of whether it's an upper bound or constraints is purely syntactic and
|
||||||
|
|
|
||||||
|
|
@ -11060,12 +11060,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
"infer_binary_type_comparison should never return None for `CmpOp::Eq`",
|
"infer_binary_type_comparison should never return None for `CmpOp::Eq`",
|
||||||
);
|
);
|
||||||
|
|
||||||
match pairwise_eq_result.try_bool(self.db()).unwrap_or_else(|err| {
|
match pairwise_eq_result
|
||||||
// TODO: We should, whenever possible, pass the range of the left and right elements
|
.try_bool(self.db())
|
||||||
// instead of the range of the whole tuple.
|
.unwrap_or_else(|err| {
|
||||||
err.report_diagnostic(&self.context, range);
|
// TODO: We should, whenever possible, pass the range of the left and right elements
|
||||||
err.fallback_truthiness()
|
// 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
|
// - AlwaysTrue : Continue to the next pair for lexicographic comparison
|
||||||
Truthiness::AlwaysTrue => continue,
|
Truthiness::AlwaysTrue => continue,
|
||||||
// - AlwaysFalse:
|
// - AlwaysFalse:
|
||||||
|
|
@ -11125,71 +11127,88 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
// compare lexicographically. However, we still need to verify that the
|
// compare lexicographically. However, we still need to verify that the
|
||||||
// element types are comparable for ordering comparisons.
|
// element types are comparable for ordering comparisons.
|
||||||
|
|
||||||
// For equality comparisons (==, !=), any two objects can be compared.
|
// 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(_))
|
(TupleSpec::Variable(_), _) | (_, TupleSpec::Variable(_))
|
||||||
if matches!(op, RichCompareOperator::Eq | RichCompareOperator::Ne) =>
|
if matches!(op, RichCompareOperator::Eq | RichCompareOperator::Ne) =>
|
||||||
{
|
{
|
||||||
Ok(Type::unknown())
|
Ok(KnownClass::Bool.to_instance(self.db()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Both variable: check all elements that could potentially be compared.
|
// Both variable: check all elements that could potentially be compared.
|
||||||
(TupleSpec::Variable(left_var), TupleSpec::Variable(right_var)) => {
|
(TupleSpec::Variable(left_var), TupleSpec::Variable(right_var)) => {
|
||||||
|
let mut builder = UnionBuilder::new(self.db());
|
||||||
|
|
||||||
// 1. Compare prefix elements at matching positions.
|
// 1. Compare prefix elements at matching positions.
|
||||||
for (l_el, r_el) in left_var.prefix_elements().zip(right_var.prefix_elements()) {
|
for (l_el, r_el) in left_var.prefix_elements().zip(right_var.prefix_elements()) {
|
||||||
self.infer_binary_type_comparison(*l_el, op.into(), *r_el, range, visitor)?;
|
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.
|
// 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()) {
|
for l_el in left_var.prefix_elements().skip(right_var.prefix.len()) {
|
||||||
self.infer_binary_type_comparison(
|
builder = builder.add(self.infer_binary_type_comparison(
|
||||||
*l_el,
|
*l_el,
|
||||||
op.into(),
|
op.into(),
|
||||||
right_var.variable,
|
right_var.variable,
|
||||||
range,
|
range,
|
||||||
visitor,
|
visitor,
|
||||||
)?;
|
)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Right's extra prefix elements are compared with left's variable.
|
// 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()) {
|
for r_el in right_var.prefix_elements().skip(left_var.prefix.len()) {
|
||||||
self.infer_binary_type_comparison(
|
builder = builder.add(self.infer_binary_type_comparison(
|
||||||
left_var.variable,
|
left_var.variable,
|
||||||
op.into(),
|
op.into(),
|
||||||
*r_el,
|
*r_el,
|
||||||
range,
|
range,
|
||||||
visitor,
|
visitor,
|
||||||
)?;
|
)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Variable elements can be compared at any overlapping position.
|
// 4. Variable elements can be compared at any overlapping position.
|
||||||
self.infer_binary_type_comparison(
|
builder = builder.add(self.infer_binary_type_comparison(
|
||||||
left_var.variable,
|
left_var.variable,
|
||||||
op.into(),
|
op.into(),
|
||||||
right_var.variable,
|
right_var.variable,
|
||||||
range,
|
range,
|
||||||
visitor,
|
visitor,
|
||||||
)?;
|
)?);
|
||||||
|
|
||||||
// 5. Left's extra suffix elements are compared with right's variable.
|
// 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()) {
|
for l_el in left_var
|
||||||
self.infer_binary_type_comparison(
|
.suffix_elements()
|
||||||
|
.rev()
|
||||||
|
.skip(right_var.suffix.len())
|
||||||
|
{
|
||||||
|
builder = builder.add(self.infer_binary_type_comparison(
|
||||||
*l_el,
|
*l_el,
|
||||||
op.into(),
|
op.into(),
|
||||||
right_var.variable,
|
right_var.variable,
|
||||||
range,
|
range,
|
||||||
visitor,
|
visitor,
|
||||||
)?;
|
)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. Right's extra suffix elements are compared with left's variable.
|
// 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()) {
|
for r_el in right_var
|
||||||
self.infer_binary_type_comparison(
|
.suffix_elements()
|
||||||
|
.rev()
|
||||||
|
.skip(left_var.suffix.len())
|
||||||
|
{
|
||||||
|
builder = builder.add(self.infer_binary_type_comparison(
|
||||||
left_var.variable,
|
left_var.variable,
|
||||||
op.into(),
|
op.into(),
|
||||||
*r_el,
|
*r_el,
|
||||||
range,
|
range,
|
||||||
visitor,
|
visitor,
|
||||||
)?;
|
)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 7. Compare suffix elements at matching positions (from the end).
|
// 7. Compare suffix elements at matching positions (from the end).
|
||||||
|
|
@ -11198,17 +11217,34 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
.rev()
|
.rev()
|
||||||
.zip(right_var.suffix_elements().rev())
|
.zip(right_var.suffix_elements().rev())
|
||||||
{
|
{
|
||||||
self.infer_binary_type_comparison(*l_el, op.into(), *r_el, range, visitor)?;
|
builder = builder.add(self.infer_binary_type_comparison(
|
||||||
|
*l_el,
|
||||||
|
op.into(),
|
||||||
|
*r_el,
|
||||||
|
range,
|
||||||
|
visitor,
|
||||||
|
)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Type::unknown())
|
// 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.
|
// Left variable, right fixed: check which elements could be compared.
|
||||||
(TupleSpec::Variable(left_var), TupleSpec::Fixed(right_fixed)) => {
|
(TupleSpec::Variable(left_var), TupleSpec::Fixed(right_fixed)) => {
|
||||||
|
let mut builder = UnionBuilder::new(self.db());
|
||||||
|
|
||||||
// Compare left's prefix with right's corresponding elements.
|
// Compare left's prefix with right's corresponding elements.
|
||||||
for (l_el, r_el) in left_var.prefix_elements().zip(right_fixed.elements()) {
|
for (l_el, r_el) in left_var.prefix_elements().zip(right_fixed.elements()) {
|
||||||
self.infer_binary_type_comparison(*l_el, op.into(), *r_el, range, visitor)?;
|
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).
|
// Compare left's suffix with right's corresponding elements (from end).
|
||||||
|
|
@ -11217,7 +11253,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
.rev()
|
.rev()
|
||||||
.zip(right_fixed.elements().rev())
|
.zip(right_fixed.elements().rev())
|
||||||
{
|
{
|
||||||
self.infer_binary_type_comparison(*l_el, op.into(), *r_el, range, visitor)?;
|
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
|
// Compare left's variable with right's "middle" elements
|
||||||
|
|
@ -11229,23 +11271,34 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
.skip(middle_start)
|
.skip(middle_start)
|
||||||
.take(middle_end.saturating_sub(middle_start))
|
.take(middle_end.saturating_sub(middle_start))
|
||||||
{
|
{
|
||||||
self.infer_binary_type_comparison(
|
builder = builder.add(self.infer_binary_type_comparison(
|
||||||
left_var.variable,
|
left_var.variable,
|
||||||
op.into(),
|
op.into(),
|
||||||
*r_el,
|
*r_el,
|
||||||
range,
|
range,
|
||||||
visitor,
|
visitor,
|
||||||
)?;
|
)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Type::unknown())
|
// 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.
|
// Left fixed, right variable: check which elements could be compared.
|
||||||
(TupleSpec::Fixed(left_fixed), TupleSpec::Variable(right_var)) => {
|
(TupleSpec::Fixed(left_fixed), TupleSpec::Variable(right_var)) => {
|
||||||
|
let mut builder = UnionBuilder::new(self.db());
|
||||||
|
|
||||||
// Compare left's elements with right's prefix.
|
// Compare left's elements with right's prefix.
|
||||||
for (l_el, r_el) in left_fixed.elements().zip(right_var.prefix_elements()) {
|
for (l_el, r_el) in left_fixed.elements().zip(right_var.prefix_elements()) {
|
||||||
self.infer_binary_type_comparison(*l_el, op.into(), *r_el, range, visitor)?;
|
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.
|
// Compare left's elements (from end) with right's suffix.
|
||||||
|
|
@ -11254,7 +11307,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
.rev()
|
.rev()
|
||||||
.zip(right_var.suffix_elements().rev())
|
.zip(right_var.suffix_elements().rev())
|
||||||
{
|
{
|
||||||
self.infer_binary_type_comparison(*l_el, op.into(), *r_el, range, visitor)?;
|
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.
|
// Compare left's "middle" elements with right's variable.
|
||||||
|
|
@ -11265,16 +11324,19 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
.skip(middle_start)
|
.skip(middle_start)
|
||||||
.take(middle_end.saturating_sub(middle_start))
|
.take(middle_end.saturating_sub(middle_start))
|
||||||
{
|
{
|
||||||
self.infer_binary_type_comparison(
|
builder = builder.add(self.infer_binary_type_comparison(
|
||||||
*l_el,
|
*l_el,
|
||||||
op.into(),
|
op.into(),
|
||||||
right_var.variable,
|
right_var.variable,
|
||||||
range,
|
range,
|
||||||
visitor,
|
visitor,
|
||||||
)?;
|
)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Type::unknown())
|
// Length comparison (when all elements are equal) returns bool.
|
||||||
|
builder = builder.add(KnownClass::Bool.to_instance(self.db()));
|
||||||
|
|
||||||
|
Ok(builder.build())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue