Skip to content

Commit

Permalink
[red-knot] Treat empty intersection as 'object', fix intersection sim…
Browse files Browse the repository at this point in the history
…plification (#13880)

## Summary

- Properly treat the empty intersection as being of type `object`.
- Consequently, change the simplification method to explicitly add
`Never` to the positive side of the intersection when collapsing a type
such as `int & str` to `Never`, as opposed to just clearing both the
positive and the negative side.
- Minor code improvement in `bindings_ty`: use `peekable()` to check
whether the iterator over constraints is empty, instead of handling
first and subsequent elements separately.

fixes #13870

## Test Plan

- New unit tests for `IntersectionBuilder` to make sure the empty
intersection represents `object`.
- Markdown-based regression test for the original issue in #13870
  • Loading branch information
sharkdp authored Oct 22, 2024
1 parent 5d4edd6 commit c6ce52c
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Narrowing for nested conditionals

## Multiple negative contributions

```py
def int_instance() -> int: ...

Expand All @@ -11,3 +13,14 @@ if x != 1:
if x != 3:
reveal_type(x) # revealed: int & ~Literal[1] & ~Literal[2] & ~Literal[3]
```

## Multiple negative contributions with simplification

```py
x = 1 if flag1 else 2 if flag2 else 3

if x != 1:
reveal_type(x) # revealed: Literal[2, 3]
if x != 2:
reveal_type(x) # revealed: Literal[3]
```
22 changes: 11 additions & 11 deletions crates/red_knot_python_semantic/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,18 +148,18 @@ fn bindings_ty<'db>(
binding,
constraints,
}| {
let mut constraint_tys =
constraints.filter_map(|constraint| narrowing_constraint(db, constraint, binding));
let mut constraint_tys = constraints
.filter_map(|constraint| narrowing_constraint(db, constraint, binding))
.peekable();

let binding_ty = binding_ty(db, binding);
if let Some(first_constraint_ty) = constraint_tys.next() {
let mut builder = IntersectionBuilder::new(db);
builder = builder
.add_positive(binding_ty)
.add_positive(first_constraint_ty);
for constraint_ty in constraint_tys {
builder = builder.add_positive(constraint_ty);
}
builder.build()
if constraint_tys.peek().is_some() {
constraint_tys
.fold(
IntersectionBuilder::new(db).add_positive(binding_ty),
IntersectionBuilder::add_positive,
)
.build()
} else {
binding_ty
}
Expand Down
14 changes: 13 additions & 1 deletion crates/red_knot_python_semantic/src/types/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
// A & B = Never if A and B are disjoint
if new_positive.is_disjoint_from(db, *existing_positive) {
*self = Self::new();
self.positive.insert(Type::Never);
return;
}
}
Expand All @@ -265,6 +266,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
// S & ~T = Never if S <: T
if new_positive.is_subtype_of(db, *existing_negative) {
*self = Self::new();
self.positive.insert(Type::Never);
return;
}
// A & ~B = A if A and B are disjoint
Expand Down Expand Up @@ -328,6 +330,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
// S & ~T = Never if S <: T
if existing_positive.is_subtype_of(db, new_negative) {
*self = Self::new();
self.positive.insert(Type::Never);
return;
}
// A & ~B = A if A and B are disjoint
Expand All @@ -351,7 +354,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
fn build(mut self, db: &'db dyn Db) -> Type<'db> {
self.simplify_unbound();
match (self.positive.len(), self.negative.len()) {
(0, 0) => Type::Never,
(0, 0) => KnownClass::Object.to_instance(db),
(1, 0) => self.positive[0],
_ => {
self.positive.shrink_to_fit();
Expand Down Expand Up @@ -523,6 +526,15 @@ mod tests {
assert_eq!(intersection.neg_vec(&db), &[t0]);
}

#[test]
fn build_intersection_empty_intersection_equals_object() {
let db = setup_db();

let ty = IntersectionBuilder::new(&db).build();

assert_eq!(ty, KnownClass::Object.to_instance(&db));
}

#[test]
fn build_intersection_flatten_positive() {
let db = setup_db();
Expand Down

0 comments on commit c6ce52c

Please sign in to comment.