Skip to content

Commit 0a478a2

Browse files
committed
take 7?
1 parent 2851098 commit 0a478a2

File tree

6 files changed

+68
-90
lines changed

6 files changed

+68
-90
lines changed

crates/red_knot_python_semantic/resources/mdtest/comparison/tuples.md

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -58,22 +58,7 @@ reveal_type(c >= d) # revealed: Literal[True]
5858
#### Results with Ambiguity
5959

6060
```py
61-
class P:
62-
def __lt__(self, other: "P") -> bool:
63-
return True
64-
65-
def __le__(self, other: "P") -> bool:
66-
return True
67-
68-
def __gt__(self, other: "P") -> bool:
69-
return True
70-
71-
def __ge__(self, other: "P") -> bool:
72-
return True
73-
74-
class Q(P): ...
75-
76-
def _(x: P, y: Q):
61+
def _(x: bool, y: int):
7762
a = (x,)
7863
b = (y,)
7964

crates/red_knot_python_semantic/resources/mdtest/exception/control_flow.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -455,9 +455,9 @@ else:
455455
reveal_type(x) # revealed: slice
456456
finally:
457457
# TODO: should be `Literal[1] | str | bytes | bool | memoryview | float | range | slice`
458-
reveal_type(x) # revealed: bool | slice | float
458+
reveal_type(x) # revealed: bool | float | slice
459459

460-
reveal_type(x) # revealed: bool | slice | float
460+
reveal_type(x) # revealed: bool | float | slice
461461
```
462462

463463
## Nested `try`/`except` blocks
@@ -534,7 +534,7 @@ try:
534534
reveal_type(x) # revealed: slice
535535
finally:
536536
# TODO: should be `Literal[1] | str | bytes | bool | memoryview | float | range | slice`
537-
reveal_type(x) # revealed: bool | slice | float
537+
reveal_type(x) # revealed: bool | float | slice
538538
x = 2
539539
reveal_type(x) # revealed: Literal[2]
540540
reveal_type(x) # revealed: Literal[2]

crates/red_knot_python_semantic/resources/mdtest/narrow/truthiness.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,22 @@ else:
2121
if x and not x:
2222
reveal_type(x) # revealed: Never
2323
else:
24-
reveal_type(x) # revealed: Literal[0, -1, "", "foo", b"", b"bar"] | bool | tuple[()] | None
24+
reveal_type(x) # revealed: Literal[0, -1, "", "foo", b"", b"bar"] | bool | None | tuple[()]
2525

2626
if not (x and not x):
27-
reveal_type(x) # revealed: Literal[0, -1, "", "foo", b"", b"bar"] | bool | tuple[()] | None
27+
reveal_type(x) # revealed: Literal[0, -1, "", "foo", b"", b"bar"] | bool | None | tuple[()]
2828
else:
2929
reveal_type(x) # revealed: Never
3030

3131
if x or not x:
32-
reveal_type(x) # revealed: Literal[0, -1, "", "foo", b"", b"bar"] | bool | tuple[()] | None
32+
reveal_type(x) # revealed: Literal[0, -1, "", "foo", b"", b"bar"] | bool | None | tuple[()]
3333
else:
3434
reveal_type(x) # revealed: Never
3535

3636
if not (x or not x):
3737
reveal_type(x) # revealed: Never
3838
else:
39-
reveal_type(x) # revealed: Literal[0, -1, "", "foo", b"", b"bar"] | bool | tuple[()] | None
39+
reveal_type(x) # revealed: Literal[0, -1, "", "foo", b"", b"bar"] | bool | None | tuple[()]
4040

4141
if (isinstance(x, int) or isinstance(x, str)) and x:
4242
reveal_type(x) # revealed: Literal[-1, True, "foo"]

crates/red_knot_python_semantic/src/types.rs

Lines changed: 29 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -824,11 +824,9 @@ impl<'db> Type<'db> {
824824
/// - Literal[True, False] | T <: bool | T
825825
#[must_use]
826826
pub fn with_normalized_bools(self, db: &'db dyn Db) -> Self {
827-
const LITERAL_BOOLS: [Type; 2] = [Type::BooleanLiteral(false), Type::BooleanLiteral(true)];
828-
829827
match self {
830828
Type::Instance(InstanceType { class }) if class.is_known(db, KnownClass::Bool) => {
831-
Type::Union(UnionType::new(db, Box::from(LITERAL_BOOLS)))
829+
Type::normalized_bool(db)
832830
}
833831
// TODO: decompose `LiteralString` into `Literal[""] | TruthyLiteralString`?
834832
// We'd need to rename this method... --Alex
@@ -884,12 +882,6 @@ impl<'db> Type<'db> {
884882
return true;
885883
}
886884

887-
let normalized_self = self.with_normalized_bools(db);
888-
let normalized_target = target.with_normalized_bools(db);
889-
if normalized_self != self || normalized_target != target {
890-
return normalized_self.is_subtype_of(db, normalized_target);
891-
}
892-
893885
// Non-fully-static types do not participate in subtyping.
894886
//
895887
// Type `A` can only be a subtype of type `B` if the set of possible runtime objects
@@ -912,6 +904,13 @@ impl<'db> Type<'db> {
912904
(Type::Never, _) => true,
913905
(_, Type::Never) => false,
914906

907+
(Type::Instance(InstanceType { class }), _) if class.is_known(db, KnownClass::Bool) => {
908+
Type::normalized_bool(db).is_subtype_of(db, target)
909+
}
910+
(_, Type::Instance(InstanceType { class })) if class.is_known(db, KnownClass::Bool) => {
911+
self.is_subtype_of(db, Type::normalized_bool(db))
912+
}
913+
915914
(Type::Union(union), _) => union
916915
.elements(db)
917916
.iter()
@@ -1108,11 +1107,7 @@ impl<'db> Type<'db> {
11081107
if self.is_gradual_equivalent_to(db, target) {
11091108
return true;
11101109
}
1111-
let normalized_self = self.with_normalized_bools(db);
1112-
let normalized_target = target.with_normalized_bools(db);
1113-
if normalized_self != self || normalized_target != target {
1114-
return normalized_self.is_assignable_to(db, normalized_target);
1115-
}
1110+
11161111
match (self, target) {
11171112
// Never can be assigned to any type.
11181113
(Type::Never, _) => true,
@@ -1129,6 +1124,13 @@ impl<'db> Type<'db> {
11291124
true
11301125
}
11311126

1127+
(Type::Instance(InstanceType { class }), _) if class.is_known(db, KnownClass::Bool) => {
1128+
Type::normalized_bool(db).is_assignable_to(db, target)
1129+
}
1130+
(_, Type::Instance(InstanceType { class })) if class.is_known(db, KnownClass::Bool) => {
1131+
self.is_assignable_to(db, Type::normalized_bool(db))
1132+
}
1133+
11321134
// A union is assignable to a type T iff every element of the union is assignable to T.
11331135
(Type::Union(union), ty) => union
11341136
.elements(db)
@@ -1213,13 +1215,6 @@ impl<'db> Type<'db> {
12131215
pub(crate) fn is_equivalent_to(self, db: &'db dyn Db, other: Type<'db>) -> bool {
12141216
// TODO equivalent but not identical types: TypedDicts, Protocols, type aliases, etc.
12151217

1216-
let normalized_self = self.with_normalized_bools(db);
1217-
let normalized_other = other.with_normalized_bools(db);
1218-
1219-
if normalized_self != self || normalized_other != other {
1220-
return normalized_self.is_equivalent_to(db, normalized_other);
1221-
}
1222-
12231218
match (self, other) {
12241219
(Type::Union(left), Type::Union(right)) => left.is_equivalent_to(db, right),
12251220
(Type::Intersection(left), Type::Intersection(right)) => {
@@ -1261,13 +1256,6 @@ impl<'db> Type<'db> {
12611256
///
12621257
/// [Summary of type relations]: https://typing.readthedocs.io/en/latest/spec/concepts.html#summary-of-type-relations
12631258
pub(crate) fn is_gradual_equivalent_to(self, db: &'db dyn Db, other: Type<'db>) -> bool {
1264-
let normalized_self = self.with_normalized_bools(db);
1265-
let normalized_other = other.with_normalized_bools(db);
1266-
1267-
if normalized_self != self || normalized_other != other {
1268-
return normalized_self.is_gradual_equivalent_to(db, normalized_other);
1269-
}
1270-
12711259
if self == other {
12721260
return true;
12731261
}
@@ -1300,12 +1288,6 @@ impl<'db> Type<'db> {
13001288
/// Note: This function aims to have no false positives, but might return
13011289
/// wrong `false` answers in some cases.
13021290
pub(crate) fn is_disjoint_from(self, db: &'db dyn Db, other: Type<'db>) -> bool {
1303-
let normalized_self = self.with_normalized_bools(db);
1304-
let normalized_other = other.with_normalized_bools(db);
1305-
if normalized_self != self || normalized_other != other {
1306-
return normalized_self.is_disjoint_from(db, normalized_other);
1307-
}
1308-
13091291
match (self, other) {
13101292
(Type::Never, _) | (_, Type::Never) => true,
13111293

@@ -2427,6 +2409,13 @@ impl<'db> Type<'db> {
24272409
KnownClass::NoneType.to_instance(db)
24282410
}
24292411

2412+
/// The type `Literal[True, False]`, which is exactly equivalent to `bool`
2413+
/// (and which `bool` is eagerly normalized to in several situations)
2414+
pub fn normalized_bool(db: &'db dyn Db) -> Type<'db> {
2415+
const LITERAL_BOOLS: [Type; 2] = [Type::BooleanLiteral(false), Type::BooleanLiteral(true)];
2416+
Type::Union(UnionType::new(db, Box::from(LITERAL_BOOLS)))
2417+
}
2418+
24302419
/// Return the type of `tuple(sys.version_info)`.
24312420
///
24322421
/// This is not exactly the type that `sys.version_info` has at runtime,
@@ -4698,19 +4687,18 @@ pub struct TupleType<'db> {
46984687
}
46994688

47004689
impl<'db> TupleType<'db> {
4701-
pub fn from_elements<I, T>(db: &'db dyn Db, types: I) -> Type<'db>
4702-
where
4703-
I: IntoIterator<Item = T>,
4704-
T: Into<Type<'db>>,
4705-
{
4690+
pub fn from_elements<T: Into<Type<'db>>>(
4691+
db: &'db dyn Db,
4692+
types: impl IntoIterator<Item = T>,
4693+
) -> Type<'db> {
47064694
let mut elements = vec![];
47074695

47084696
for ty in types {
4709-
let ty: Type<'db> = ty.into();
4697+
let ty = ty.into();
47104698
if ty.is_never() {
47114699
return Type::Never;
47124700
}
4713-
elements.push(ty.with_normalized_bools(db));
4701+
elements.push(ty);
47144702
}
47154703

47164704
Type::Tuple(Self::new(db, elements.into_boxed_slice()))

crates/red_knot_python_semantic/src/types/builder.rs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,35 @@ impl<'db> UnionBuilder<'db> {
103103
}
104104

105105
pub(crate) fn build(self) -> Type<'db> {
106-
match self.elements.len() {
106+
let UnionBuilder { elements, db } = self;
107+
108+
match elements.len() {
107109
0 => Type::Never,
108-
1 => self.elements[0],
109-
_ => Type::Union(UnionType::new(self.db, self.elements.into_boxed_slice())),
110+
1 => elements[0],
111+
_ => {
112+
let mut normalized_elements = Vec::with_capacity(elements.len());
113+
let mut first_bool_literal_pos = None;
114+
let mut seen_two_bool_literals = false;
115+
for (i, element) in elements.into_iter().enumerate() {
116+
if element.is_boolean_literal() {
117+
if first_bool_literal_pos.is_none() {
118+
first_bool_literal_pos = Some(i);
119+
} else {
120+
seen_two_bool_literals = true;
121+
continue;
122+
}
123+
}
124+
normalized_elements.push(element);
125+
}
126+
if let (Some(pos), true) = (first_bool_literal_pos, seen_two_bool_literals) {
127+
// If we have two boolean literals, we can merge them to `bool`.
128+
if normalized_elements.len() == 1 {
129+
return KnownClass::Bool.to_instance(db);
130+
}
131+
normalized_elements[pos] = KnownClass::Bool.to_instance(db);
132+
}
133+
Type::Union(UnionType::new(db, normalized_elements.into_boxed_slice()))
134+
}
110135
}
111136
}
112137
}

crates/red_knot_python_semantic/src/types/display.rs

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
//! Display implementations for types.
22
3-
use std::borrow::Cow;
43
use std::fmt::{self, Display, Formatter, Write};
54

65
use ruff_db::display::FormatterJoinExtension;
@@ -152,31 +151,12 @@ struct DisplayUnionType<'db> {
152151

153152
impl Display for DisplayUnionType<'_> {
154153
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
155-
let mut elements = Cow::Borrowed(self.ty.elements(self.db));
156-
157-
if let Some(literal_false_pos) = elements
158-
.iter()
159-
.position(|ty| matches!(ty, Type::BooleanLiteral(false)))
160-
{
161-
if let Some(literal_true_pos) = elements
162-
.iter()
163-
.position(|ty| matches!(ty, Type::BooleanLiteral(true)))
164-
{
165-
let (min, max) = if literal_false_pos < literal_true_pos {
166-
(literal_false_pos, literal_true_pos)
167-
} else {
168-
(literal_true_pos, literal_false_pos)
169-
};
170-
let mutable_elements = elements.to_mut();
171-
mutable_elements.swap_remove(max);
172-
mutable_elements[min] = KnownClass::Bool.to_instance(self.db);
173-
}
174-
}
154+
let elements = self.ty.elements(self.db);
175155

176156
// Group condensed-display types by kind.
177157
let mut grouped_condensed_kinds = FxHashMap::default();
178158

179-
for element in &*elements {
159+
for element in elements {
180160
if let Ok(kind) = CondensedDisplayTypeKind::try_from(*element) {
181161
grouped_condensed_kinds
182162
.entry(kind)
@@ -187,7 +167,7 @@ impl Display for DisplayUnionType<'_> {
187167

188168
let mut join = f.join(" | ");
189169

190-
for element in &*elements {
170+
for element in elements {
191171
if let Ok(kind) = CondensedDisplayTypeKind::try_from(*element) {
192172
let Some(condensed_kind) = grouped_condensed_kinds.remove(&kind) else {
193173
continue;

0 commit comments

Comments
 (0)