[red-knot] Move intersection type tests to Markdown (#15396)

## Summary

[**Rendered version of the new test
suite**](https://github.com/astral-sh/ruff/blob/david/intersection-type-tests/crates/red_knot_python_semantic/resources/mdtest/intersection_types.md)

Moves most of our existing intersection-types tests to a dedicated
Markdown test suite, extends the test coverage, unifies the notation for
these tests, groups tests into a proper structure, and adds some
explanations for various simplification strategies.

This changeset also:
- Adds a new simplification where `~Never` is removed from
intersections.
- Adds a new simplification where adding `~object` simplifies the whole
intersection to `Never`
- Avoids unnecessary assignment-checks between inferred and declared
type. This was added to this changeset to avoid many false positive
errors in this test suite.

Resolves the task described in this old comment
[here](e01da82a5a..e7e432bca2 (r1819924085)).

## Test Plan

Running the new Markdown tests

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
David Peter 2025-01-10 14:04:03 +01:00 committed by GitHub
parent b861551b6a
commit f2c3ddc5ea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 888 additions and 620 deletions

View file

@ -0,0 +1,747 @@
# Intersection types
## Introduction
This test suite covers certain properties of intersection types and makes sure that we can apply
various simplification strategies. We use `Intersection` (`&`) and `Not` (`~`) to construct
intersection types (note that we display negative contributions at the end; the order does not
matter):
```py
from knot_extensions import Intersection, Not
class P: ...
class Q: ...
def _(
i1: Intersection[P, Q],
i2: Intersection[P, Not[Q]],
i3: Intersection[Not[P], Q],
i4: Intersection[Not[P], Not[Q]],
) -> None:
reveal_type(i1) # revealed: P & Q
reveal_type(i2) # revealed: P & ~Q
reveal_type(i3) # revealed: Q & ~P
reveal_type(i4) # revealed: ~P & ~Q
```
## Notation
Throughout this document, we use the following types as representatives for certain equivalence
classes.
### Non-disjoint types
We use `P`, `Q`, `R`, … to denote types that are non-disjoint:
```py
from knot_extensions import static_assert, is_disjoint_from
class P: ...
class Q: ...
class R: ...
static_assert(not is_disjoint_from(P, Q))
static_assert(not is_disjoint_from(P, R))
static_assert(not is_disjoint_from(Q, R))
```
Although `P` is not a subtype of `Q` and `Q` is not a subtype of `P`, the two types are not disjoint
because it would be possible to create a class `S` that inherits from both `P` and `Q` using
multiple inheritance. An instance of `S` would be a member of the `P` type _and_ the `Q` type.
### Disjoint types
We use `Literal[1]`, `Literal[2]`, … as examples of pairwise-disjoint types, and `int` as a joint
supertype of these:
```py
from knot_extensions import static_assert, is_disjoint_from, is_subtype_of
from typing import Literal
static_assert(is_disjoint_from(Literal[1], Literal[2]))
static_assert(is_disjoint_from(Literal[1], Literal[3]))
static_assert(is_disjoint_from(Literal[2], Literal[3]))
static_assert(is_subtype_of(Literal[1], int))
static_assert(is_subtype_of(Literal[2], int))
static_assert(is_subtype_of(Literal[3], int))
```
### Subtypes
Finally, we use `A <: B <: C` and `A <: B1`, `A <: B2` to denote hierarchies of (proper) subtypes:
```py
from knot_extensions import static_assert, is_subtype_of, is_disjoint_from
class A: ...
class B(A): ...
class C(B): ...
static_assert(is_subtype_of(B, A))
static_assert(is_subtype_of(C, B))
static_assert(is_subtype_of(C, A))
static_assert(not is_subtype_of(A, B))
static_assert(not is_subtype_of(B, C))
static_assert(not is_subtype_of(A, C))
class B1(A): ...
class B2(A): ...
static_assert(is_subtype_of(B1, A))
static_assert(is_subtype_of(B2, A))
static_assert(not is_subtype_of(A, B1))
static_assert(not is_subtype_of(A, B2))
static_assert(not is_subtype_of(B1, B2))
static_assert(not is_subtype_of(B2, B1))
```
## Structural properties
This section covers structural properties of intersection types and documents some decisions on how
to represent mixtures of intersections and unions.
### Single-element unions
If we have a union of a single element, we can simplify to that element. Similarly, we show an
intersection with a single negative contribution as just the negation of that element.
```py
from knot_extensions import Intersection, Not
class P: ...
def _(
i1: Intersection[P],
i2: Intersection[Not[P]],
) -> None:
reveal_type(i1) # revealed: P
reveal_type(i2) # revealed: ~P
```
### Flattening of nested intersections
We eagerly flatten nested intersections types.
```py
from knot_extensions import Intersection, Not
class P: ...
class Q: ...
class R: ...
class S: ...
def positive_contributions(
i1: Intersection[P, Intersection[Q, R]],
i2: Intersection[Intersection[P, Q], R],
) -> None:
reveal_type(i1) # revealed: P & Q & R
reveal_type(i2) # revealed: P & Q & R
def negative_contributions(
i1: Intersection[Not[P], Intersection[Not[Q], Not[R]]],
i2: Intersection[Intersection[Not[P], Not[Q]], Not[R]],
) -> None:
reveal_type(i1) # revealed: ~P & ~Q & ~R
reveal_type(i2) # revealed: ~P & ~Q & ~R
def mixed(
i1: Intersection[P, Intersection[Not[Q], R]],
i2: Intersection[Intersection[P, Not[Q]], R],
i3: Intersection[Not[P], Intersection[Q, Not[R]]],
i4: Intersection[Intersection[Q, Not[R]], Not[P]],
) -> None:
reveal_type(i1) # revealed: P & R & ~Q
reveal_type(i2) # revealed: P & R & ~Q
reveal_type(i3) # revealed: Q & ~P & ~R
reveal_type(i4) # revealed: Q & ~R & ~P
def multiple(
i1: Intersection[Intersection[P, Q], Intersection[R, S]],
):
reveal_type(i1) # revealed: P & Q & R & S
def nested(
i1: Intersection[Intersection[Intersection[P, Q], R], S],
i2: Intersection[P, Intersection[Q, Intersection[R, S]]],
):
reveal_type(i1) # revealed: P & Q & R & S
reveal_type(i2) # revealed: P & Q & R & S
```
### Union of intersections
We always normalize our representation to a _union of intersections_, so when we add a _union to an
intersection_, we distribute the union over the respective elements:
```py
from knot_extensions import Intersection, Not
class P: ...
class Q: ...
class R: ...
class S: ...
def _(
i1: Intersection[P, Q | R | S],
i2: Intersection[P | Q | R, S],
i3: Intersection[P | Q, R | S],
) -> None:
reveal_type(i1) # revealed: P & Q | P & R | P & S
reveal_type(i2) # revealed: P & S | Q & S | R & S
reveal_type(i3) # revealed: P & R | Q & R | P & S | Q & S
def simplifications_for_same_elements(
i1: Intersection[P, Q | P],
i2: Intersection[Q, P | Q],
i3: Intersection[P | Q, Q | R],
i4: Intersection[P | Q, P | Q],
i5: Intersection[P | Q, Q | P],
) -> None:
# P & (Q | P)
# = P & Q | P & P
# = P & Q | P
# = P
# (because P is a supertype of P & Q)
reveal_type(i1) # revealed: P
# similar here:
reveal_type(i2) # revealed: Q
# (P | Q) & (Q | R)
# = P & Q | P & R | Q & Q | Q & R
# = P & Q | P & R | Q | Q & R
# = Q | P & R
# (again, because Q is a supertype of P & Q and of Q & R)
reveal_type(i3) # revealed: Q | P & R
# (P | Q) & (P | Q)
# = P & P | P & Q | Q & P | Q & Q
# = P | P & Q | Q
# = P | Q
reveal_type(i4) # revealed: P | Q
```
### Negation distributes over union
Distribution also applies to a negation operation. This is a manifestation of one of
[De Morgan's laws], namely `~(P | Q) = ~P & ~Q`:
```py
from knot_extensions import Not
from typing import Literal
class P: ...
class Q: ...
class R: ...
def _(i1: Not[P | Q], i2: Not[P | Q | R]) -> None:
reveal_type(i1) # revealed: ~P & ~Q
reveal_type(i2) # revealed: ~P & ~Q & ~R
def example_literals(i: Not[Literal[1, 2]]) -> None:
reveal_type(i) # revealed: ~Literal[1] & ~Literal[2]
```
### Negation of intersections
The other of [De Morgan's laws], `~(P & Q) = ~P | ~Q`, also holds:
```py
from knot_extensions import Intersection, Not
class P: ...
class Q: ...
class R: ...
def _(
i1: Not[Intersection[P, Q]],
i2: Not[Intersection[P, Q, R]],
) -> None:
reveal_type(i1) # revealed: ~P | ~Q
reveal_type(i2) # revealed: ~P | ~Q | ~R
```
### `Never` is dual to `object`
`Never` represents the empty set of values, while `object` represents the set of all values, so
`~Never` is equivalent to `object`, and `~object` is equivalent to `Never`. This is a manifestation
of the [complement laws] of set theory.
```py
from knot_extensions import Intersection, Not
from typing_extensions import Never
def _(
not_never: Not[Never],
not_object: Not[object],
) -> None:
reveal_type(not_never) # revealed: object
reveal_type(not_object) # revealed: Never
```
### Intersection of a type and its negation
Continuing with more [complement laws], if we see both `P` and `~P` in an intersection, we can
simplify to `Never`, even in the presence of other types:
```py
from knot_extensions import Intersection, Not
from typing import Any
class P: ...
class Q: ...
def _(
i1: Intersection[P, Not[P]],
i2: Intersection[Not[P], P],
i3: Intersection[P, Q, Not[P]],
i4: Intersection[Not[P], Q, P],
i5: Intersection[P, Any, Not[P]],
i6: Intersection[Not[P], Any, P],
) -> None:
reveal_type(i1) # revealed: Never
reveal_type(i2) # revealed: Never
reveal_type(i3) # revealed: Never
reveal_type(i4) # revealed: Never
reveal_type(i5) # revealed: Never
reveal_type(i6) # revealed: Never
```
### Union of a type and its negation
Similarly, if we have both `P` and `~P` in a _union_, we could simplify that to `object`. However,
this is a rather costly operation which would require us to build the negation of each type that we
add to a union, so this is not implemented at the moment.
```py
from knot_extensions import Intersection, Not
class P: ...
def _(
i1: P | Not[P],
i2: Not[P] | P,
) -> None:
# These could be simplified to `object`
reveal_type(i1) # revealed: P | ~P
reveal_type(i2) # revealed: ~P | P
```
### Negation is an involution
The final of the [complement laws] states that negating twice is equivalent to not negating at all:
```py
from knot_extensions import Not
class P: ...
def _(
i1: Not[P],
i2: Not[Not[P]],
i3: Not[Not[Not[P]]],
i4: Not[Not[Not[Not[P]]]],
) -> None:
reveal_type(i1) # revealed: ~P
reveal_type(i2) # revealed: P
reveal_type(i3) # revealed: ~P
reveal_type(i4) # revealed: P
```
## Simplification strategies
In this section, we present various simplification strategies that go beyond the structure of the
representation.
### `Never` in intersections
If we intersect with `Never`, we can simplify the whole intersection to `Never`, even if there are
dynamic types involved:
```py
from knot_extensions import Intersection, Not
from typing_extensions import Never, Any
class P: ...
class Q: ...
def _(
i1: Intersection[P, Never],
i2: Intersection[Never, P],
i3: Intersection[Any, Never],
i4: Intersection[Never, Not[Any]],
) -> None:
reveal_type(i1) # revealed: Never
reveal_type(i2) # revealed: Never
reveal_type(i3) # revealed: Never
reveal_type(i4) # revealed: Never
```
### Simplifications using disjointness
#### Positive contributions
If we intersect disjoint types, we can simplify to `Never`, even in the presence of other types:
```py
from knot_extensions import Intersection, Not
from typing import Literal, Any
class P: ...
def _(
i01: Intersection[Literal[1], Literal[2]],
i02: Intersection[Literal[2], Literal[1]],
i03: Intersection[Literal[1], Literal[2], P],
i04: Intersection[Literal[1], P, Literal[2]],
i05: Intersection[P, Literal[1], Literal[2]],
i06: Intersection[Literal[1], Literal[2], Any],
i07: Intersection[Literal[1], Any, Literal[2]],
i08: Intersection[Any, Literal[1], Literal[2]],
) -> None:
reveal_type(i01) # revealed: Never
reveal_type(i02) # revealed: Never
reveal_type(i03) # revealed: Never
reveal_type(i04) # revealed: Never
reveal_type(i05) # revealed: Never
reveal_type(i06) # revealed: Never
reveal_type(i07) # revealed: Never
reveal_type(i08) # revealed: Never
# `bool` is final and can not be subclassed, so `type[bool]` is equivalent to `Literal[bool]`, which
# is disjoint from `type[str]`:
def example_type_bool_type_str(
i: Intersection[type[bool], type[str]],
) -> None:
reveal_type(i) # revealed: Never
```
#### Positive and negative contributions
If we intersect a type `X` with the negation of a disjoint type `Y`, we can remove the negative
contribution `~Y`, as it necessarily overlaps with the positive contribution `X`:
```py
from knot_extensions import Intersection, Not
from typing import Literal
def _(
i1: Intersection[Literal[1], Not[Literal[2]]],
i2: Intersection[Not[Literal[2]], Literal[1]],
i3: Intersection[Literal[1], Not[Literal[2]], int],
i4: Intersection[Literal[1], int, Not[Literal[2]]],
i5: Intersection[int, Literal[1], Not[Literal[2]]],
) -> None:
reveal_type(i1) # revealed: Literal[1]
reveal_type(i2) # revealed: Literal[1]
reveal_type(i3) # revealed: Literal[1]
reveal_type(i4) # revealed: Literal[1]
reveal_type(i5) # revealed: Literal[1]
# None is disjoint from int, so this simplification applies here
def example_none(
i1: Intersection[int, Not[None]],
i2: Intersection[Not[None], int],
) -> None:
reveal_type(i1) # revealed: int
reveal_type(i2) # revealed: int
```
### Simplifications using subtype relationships
#### Positive type and positive subtype
Subtypes are contained within their supertypes, so we can simplify intersections by removing
superfluous supertypes:
```py
from knot_extensions import Intersection, Not
from typing import Any
class A: ...
class B(A): ...
class C(B): ...
class Unrelated: ...
def _(
i01: Intersection[A, B],
i02: Intersection[B, A],
i03: Intersection[A, C],
i04: Intersection[C, A],
i05: Intersection[B, C],
i06: Intersection[C, B],
i07: Intersection[A, B, C],
i08: Intersection[C, B, A],
i09: Intersection[B, C, A],
i10: Intersection[A, B, Unrelated],
i11: Intersection[B, A, Unrelated],
i12: Intersection[B, Unrelated, A],
i13: Intersection[A, Unrelated, B],
i14: Intersection[Unrelated, A, B],
i15: Intersection[Unrelated, B, A],
i16: Intersection[A, B, Any],
i17: Intersection[B, A, Any],
i18: Intersection[B, Any, A],
i19: Intersection[A, Any, B],
i20: Intersection[Any, A, B],
i21: Intersection[Any, B, A],
) -> None:
reveal_type(i01) # revealed: B
reveal_type(i02) # revealed: B
reveal_type(i03) # revealed: C
reveal_type(i04) # revealed: C
reveal_type(i05) # revealed: C
reveal_type(i06) # revealed: C
reveal_type(i07) # revealed: C
reveal_type(i08) # revealed: C
reveal_type(i09) # revealed: C
reveal_type(i10) # revealed: B & Unrelated
reveal_type(i11) # revealed: B & Unrelated
reveal_type(i12) # revealed: B & Unrelated
reveal_type(i13) # revealed: Unrelated & B
reveal_type(i14) # revealed: Unrelated & B
reveal_type(i15) # revealed: Unrelated & B
reveal_type(i16) # revealed: B & Any
reveal_type(i17) # revealed: B & Any
reveal_type(i18) # revealed: B & Any
reveal_type(i19) # revealed: Any & B
reveal_type(i20) # revealed: Any & B
reveal_type(i21) # revealed: Any & B
```
#### Negative type and negative subtype
For negative contributions, this property is reversed. Here we can get remove superfluous
_subtypes_:
```py
from knot_extensions import Intersection, Not
from typing import Any
class A: ...
class B(A): ...
class C(B): ...
class Unrelated: ...
def _(
i01: Intersection[Not[B], Not[A]],
i02: Intersection[Not[A], Not[B]],
i03: Intersection[Not[A], Not[C]],
i04: Intersection[Not[C], Not[A]],
i05: Intersection[Not[B], Not[C]],
i06: Intersection[Not[C], Not[B]],
i07: Intersection[Not[A], Not[B], Not[C]],
i08: Intersection[Not[C], Not[B], Not[A]],
i09: Intersection[Not[B], Not[C], Not[A]],
i10: Intersection[Not[B], Not[A], Unrelated],
i11: Intersection[Not[A], Not[B], Unrelated],
i12: Intersection[Not[A], Unrelated, Not[B]],
i13: Intersection[Not[B], Unrelated, Not[A]],
i14: Intersection[Unrelated, Not[A], Not[B]],
i15: Intersection[Unrelated, Not[B], Not[A]],
i16: Intersection[Not[B], Not[A], Any],
i17: Intersection[Not[A], Not[B], Any],
i18: Intersection[Not[A], Any, Not[B]],
i19: Intersection[Not[B], Any, Not[A]],
i20: Intersection[Any, Not[A], Not[B]],
i21: Intersection[Any, Not[B], Not[A]],
) -> None:
reveal_type(i01) # revealed: ~A
reveal_type(i02) # revealed: ~A
reveal_type(i03) # revealed: ~A
reveal_type(i04) # revealed: ~A
reveal_type(i05) # revealed: ~B
reveal_type(i06) # revealed: ~B
reveal_type(i07) # revealed: ~A
reveal_type(i08) # revealed: ~A
reveal_type(i09) # revealed: ~A
reveal_type(i10) # revealed: Unrelated & ~A
reveal_type(i11) # revealed: Unrelated & ~A
reveal_type(i12) # revealed: Unrelated & ~A
reveal_type(i13) # revealed: Unrelated & ~A
reveal_type(i14) # revealed: Unrelated & ~A
reveal_type(i15) # revealed: Unrelated & ~A
reveal_type(i16) # revealed: Any & ~A
reveal_type(i17) # revealed: Any & ~A
reveal_type(i18) # revealed: Any & ~A
reveal_type(i19) # revealed: Any & ~A
reveal_type(i20) # revealed: Any & ~A
reveal_type(i21) # revealed: Any & ~A
```
#### Negative type and multiple negative subtypes
If there are multiple negative subtypes, all of them can be removed:
```py
from knot_extensions import Intersection, Not
class A: ...
class B1(A): ...
class B2(A): ...
def _(
i1: Intersection[Not[A], Not[B1], Not[B2]],
i2: Intersection[Not[A], Not[B2], Not[B1]],
i3: Intersection[Not[B1], Not[A], Not[B2]],
i4: Intersection[Not[B1], Not[B2], Not[A]],
i5: Intersection[Not[B2], Not[A], Not[B1]],
i6: Intersection[Not[B2], Not[B1], Not[A]],
) -> None:
reveal_type(i1) # revealed: ~A
reveal_type(i2) # revealed: ~A
reveal_type(i3) # revealed: ~A
reveal_type(i4) # revealed: ~A
reveal_type(i5) # revealed: ~A
reveal_type(i6) # revealed: ~A
```
#### Negative type and positive subtype
When `A` is a supertype of `B`, its negation `~A` is disjoint from `B`, so we can simplify the
intersection to `Never`:
```py
from knot_extensions import Intersection, Not
from typing import Any
class A: ...
class B(A): ...
class C(B): ...
class Unrelated: ...
def _(
i1: Intersection[Not[A], B],
i2: Intersection[B, Not[A]],
i3: Intersection[Not[A], C],
i4: Intersection[C, Not[A]],
i5: Intersection[Unrelated, Not[A], B],
i6: Intersection[B, Not[A], Not[Unrelated]],
i7: Intersection[Any, Not[A], B],
i8: Intersection[B, Not[A], Not[Any]],
) -> None:
reveal_type(i1) # revealed: Never
reveal_type(i2) # revealed: Never
reveal_type(i3) # revealed: Never
reveal_type(i4) # revealed: Never
reveal_type(i5) # revealed: Never
reveal_type(i6) # revealed: Never
reveal_type(i7) # revealed: Never
reveal_type(i8) # revealed: Never
```
## Non fully-static types
### Negation of dynamic types
`Any` represents the dynamic type, an unknown set of runtime values. The negation of that, `~Any`,
is still an unknown set of runtime values, so `~Any` is equivalent to `Any`. We therefore eagerly
simplify `~Any` to `Any` in intersections. The same applies to `Unknown`.
```py
from knot_extensions import Intersection, Not, Unknown
from typing_extensions import Any, Never
class P: ...
def any(
i1: Not[Any],
i2: Intersection[P, Not[Any]],
i3: Intersection[Never, Not[Any]],
) -> None:
reveal_type(i1) # revealed: Any
reveal_type(i2) # revealed: P & Any
reveal_type(i3) # revealed: Never
def unknown(
i1: Not[Unknown],
i2: Intersection[P, Not[Unknown]],
i3: Intersection[Never, Not[Unknown]],
) -> None:
reveal_type(i1) # revealed: Unknown
reveal_type(i2) # revealed: P & Unknown
reveal_type(i3) # revealed: Never
```
### Collapsing of multiple `Any`/`Unknown` contributions
The intersection of an unknown set of runtime values with (another) unknown set of runtime values is
still an unknown set of runtime values:
```py
from knot_extensions import Intersection, Not, Unknown
from typing_extensions import Any
class P: ...
def any(
i1: Intersection[Any, Any],
i2: Intersection[P, Any, Any],
i3: Intersection[Any, P, Any],
i4: Intersection[Any, Any, P],
) -> None:
reveal_type(i1) # revealed: Any
reveal_type(i2) # revealed: P & Any
reveal_type(i3) # revealed: Any & P
reveal_type(i4) # revealed: Any & P
def unknown(
i1: Intersection[Unknown, Unknown],
i2: Intersection[P, Unknown, Unknown],
i3: Intersection[Unknown, P, Unknown],
i4: Intersection[Unknown, Unknown, P],
) -> None:
reveal_type(i1) # revealed: Unknown
reveal_type(i2) # revealed: P & Unknown
reveal_type(i3) # revealed: Unknown & P
reveal_type(i4) # revealed: Unknown & P
```
### No self-cancellation
Dynamic types do not cancel each other out. Intersecting an unknown set of values with the negation
of another unknown set of values is not necessarily empty, so we keep the positive contribution:
```py
from knot_extensions import Intersection, Not, Unknown
def any(
i1: Intersection[Any, Not[Any]],
i2: Intersection[Not[Any], Any],
) -> None:
reveal_type(i1) # revealed: Any
reveal_type(i2) # revealed: Any
def unknown(
i1: Intersection[Unknown, Not[Unknown]],
i2: Intersection[Not[Unknown], Unknown],
) -> None:
reveal_type(i1) # revealed: Unknown
reveal_type(i2) # revealed: Unknown
```
### Mixed dynamic types
We currently do not simplify mixed dynamic types, but might consider doing so in the future:
```py
from knot_extensions import Intersection, Not, Unknown
def mixed(
i1: Intersection[Any, Unknown],
i2: Intersection[Any, Not[Unknown]],
i3: Intersection[Not[Any], Unknown],
i4: Intersection[Not[Any], Not[Unknown]],
) -> None:
reveal_type(i1) # revealed: Any & Unknown
reveal_type(i2) # revealed: Any & Unknown
reveal_type(i3) # revealed: Any & Unknown
reveal_type(i4) # revealed: Any & Unknown
```
[complement laws]: https://en.wikipedia.org/wiki/Complement_(set_theory)
[de morgan's laws]: https://en.wikipedia.org/wiki/De_Morgan%27s_laws

View file

@ -123,3 +123,21 @@ from knot_extensions import Unknown
def _(u1: str | Unknown | int | object):
reveal_type(u1) # revealed: Unknown | object
```
## Union of intersections
We can simplify unions of intersections:
```py
from knot_extensions import Intersection, Not
class P: ...
class Q: ...
def _(
i1: Intersection[P, Q] | Intersection[P, Q],
i2: Intersection[P, Q] | Intersection[Q, P],
) -> None:
reveal_type(i1) # revealed: P & Q
reveal_type(i2) # revealed: P & Q
```

View file

@ -321,6 +321,14 @@ impl<'db> InnerIntersectionBuilder<'db> {
self.add_positive(db, *neg);
}
}
Type::Never => {
// Adding ~Never to an intersection is a no-op.
}
Type::Instance(instance) if instance.class.is_known(db, KnownClass::Object) => {
// Adding ~object to an intersection results in Never.
*self = Self::default();
self.positive.insert(Type::Never);
}
ty @ Type::Dynamic(_) => {
// Adding any of these types to the negative side of an intersection
// is equivalent to adding it to the positive side. We do this to
@ -386,33 +394,34 @@ impl<'db> InnerIntersectionBuilder<'db> {
#[cfg(test)]
mod tests {
use super::{IntersectionBuilder, IntersectionType, Type, UnionType};
use super::{IntersectionBuilder, Type, UnionBuilder, UnionType};
use crate::db::tests::{setup_db, TestDb};
use crate::types::{global_symbol, todo_type, KnownClass, Truthiness, UnionBuilder};
use crate::db::tests::setup_db;
use crate::types::{KnownClass, Truthiness};
use ruff_db::files::system_path_to_file;
use ruff_db::system::DbWithTestSystem;
use test_case::test_case;
#[test]
fn build_union_no_elements() {
let db = setup_db();
let ty = UnionBuilder::new(&db).build();
assert_eq!(ty, Type::Never);
let empty_union = UnionBuilder::new(&db).build();
assert_eq!(empty_union, Type::Never);
}
#[test]
fn build_union_single_element() {
let db = setup_db();
let t0 = Type::IntLiteral(0);
let ty = UnionType::from_elements(&db, [t0]);
assert_eq!(ty, t0);
let union = UnionType::from_elements(&db, [t0]);
assert_eq!(union, t0);
}
#[test]
fn build_union_two_elements() {
let db = setup_db();
let t0 = Type::IntLiteral(0);
let t1 = Type::IntLiteral(1);
let union = UnionType::from_elements(&db, [t0, t1]).expect_union();
@ -420,499 +429,12 @@ mod tests {
assert_eq!(union.elements(&db), &[t0, t1]);
}
impl<'db> IntersectionType<'db> {
fn pos_vec(self, db: &'db TestDb) -> Vec<Type<'db>> {
self.positive(db).into_iter().copied().collect()
}
fn neg_vec(self, db: &'db TestDb) -> Vec<Type<'db>> {
self.negative(db).into_iter().copied().collect()
}
}
#[test]
fn build_intersection() {
let db = setup_db();
let t0 = Type::IntLiteral(0);
let ta = Type::any();
let intersection = IntersectionBuilder::new(&db)
.add_positive(ta)
.add_negative(t0)
.build()
.expect_intersection();
assert_eq!(intersection.pos_vec(&db), &[ta]);
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();
let ta = Type::any();
let t1 = Type::IntLiteral(1);
let t2 = Type::IntLiteral(2);
let i0 = IntersectionBuilder::new(&db)
.add_positive(ta)
.add_negative(t1)
.build();
let intersection = IntersectionBuilder::new(&db)
.add_positive(t2)
.add_positive(i0)
.build()
.expect_intersection();
assert_eq!(intersection.pos_vec(&db), &[t2, ta]);
assert_eq!(intersection.neg_vec(&db), &[]);
}
#[test]
fn build_intersection_flatten_negative() {
let db = setup_db();
let ta = Type::any();
let t1 = Type::IntLiteral(1);
let t2 = KnownClass::Int.to_instance(&db);
// i0 = Any & ~Literal[1]
let i0 = IntersectionBuilder::new(&db)
.add_positive(ta)
.add_negative(t1)
.build();
// ta_not_i0 = int & ~(Any & ~Literal[1])
// -> int & (~Any | Literal[1])
// (~Any is equivalent to Any)
// -> (int & Any) | (int & Literal[1])
// -> (int & Any) | Literal[1]
let ta_not_i0 = IntersectionBuilder::new(&db)
.add_positive(t2)
.add_negative(i0)
.build();
assert_eq!(ta_not_i0.display(&db).to_string(), "int & Any | Literal[1]");
}
#[test]
fn build_intersection_simplify_negative_any() {
let db = setup_db();
let ty = IntersectionBuilder::new(&db)
.add_negative(Type::any())
.build();
assert_eq!(ty, Type::any());
let ty = IntersectionBuilder::new(&db)
.add_positive(Type::Never)
.add_negative(Type::any())
.build();
assert_eq!(ty, Type::Never);
}
#[test]
fn build_intersection_simplify_multiple_unknown() {
let db = setup_db();
let ty = IntersectionBuilder::new(&db)
.add_positive(Type::unknown())
.add_positive(Type::unknown())
.build();
assert_eq!(ty, Type::unknown());
let ty = IntersectionBuilder::new(&db)
.add_positive(Type::unknown())
.add_negative(Type::unknown())
.build();
assert_eq!(ty, Type::unknown());
let ty = IntersectionBuilder::new(&db)
.add_negative(Type::unknown())
.add_negative(Type::unknown())
.build();
assert_eq!(ty, Type::unknown());
let ty = IntersectionBuilder::new(&db)
.add_positive(Type::unknown())
.add_positive(Type::IntLiteral(0))
.add_negative(Type::unknown())
.build();
assert_eq!(
ty,
IntersectionBuilder::new(&db)
.add_positive(Type::unknown())
.add_positive(Type::IntLiteral(0))
.build()
);
}
#[test]
fn intersection_distributes_over_union() {
let db = setup_db();
let t0 = Type::IntLiteral(0);
let t1 = Type::IntLiteral(1);
let ta = Type::any();
let u0 = UnionType::from_elements(&db, [t0, t1]);
let union = IntersectionBuilder::new(&db)
.add_positive(ta)
.add_positive(u0)
.build()
.expect_union();
let [Type::Intersection(i0), Type::Intersection(i1)] = union.elements(&db)[..] else {
panic!("expected a union of two intersections");
};
assert_eq!(i0.pos_vec(&db), &[ta, t0]);
assert_eq!(i1.pos_vec(&db), &[ta, t1]);
}
#[test]
fn intersection_negation_distributes_over_union() {
let mut db = setup_db();
db.write_dedented(
"/src/module.py",
r#"
class A: ...
class B: ...
"#,
)
.unwrap();
let module = ruff_db::files::system_path_to_file(&db, "/src/module.py").unwrap();
let a = global_symbol(&db, module, "A")
.expect_type()
.to_instance(&db);
let b = global_symbol(&db, module, "B")
.expect_type()
.to_instance(&db);
// intersection: A & B
let intersection = IntersectionBuilder::new(&db)
.add_positive(a)
.add_positive(b)
.build()
.expect_intersection();
assert_eq!(intersection.pos_vec(&db), &[a, b]);
assert_eq!(intersection.neg_vec(&db), &[]);
// ~intersection => ~A | ~B
let negated_intersection = IntersectionBuilder::new(&db)
.add_negative(Type::Intersection(intersection))
.build()
.expect_union();
// should have as elements ~A and ~B
let not_a = a.negate(&db);
let not_b = b.negate(&db);
assert_eq!(negated_intersection.elements(&db), &[not_a, not_b]);
}
#[test]
fn mixed_intersection_negation_distributes_over_union() {
let mut db = setup_db();
db.write_dedented(
"/src/module.py",
r#"
class A: ...
class B: ...
"#,
)
.unwrap();
let module = ruff_db::files::system_path_to_file(&db, "/src/module.py").unwrap();
let a = global_symbol(&db, module, "A")
.expect_type()
.to_instance(&db);
let b = global_symbol(&db, module, "B")
.expect_type()
.to_instance(&db);
let int = KnownClass::Int.to_instance(&db);
// a_not_b: A & ~B
let a_not_b = IntersectionBuilder::new(&db)
.add_positive(a)
.add_negative(b)
.build()
.expect_intersection();
assert_eq!(a_not_b.pos_vec(&db), &[a]);
assert_eq!(a_not_b.neg_vec(&db), &[b]);
// let's build
// int & ~(A & ~B)
// = int & ~(A & ~B)
// = int & (~A | B)
// = (int & ~A) | (int & B)
let t = IntersectionBuilder::new(&db)
.add_positive(int)
.add_negative(Type::Intersection(a_not_b))
.build();
assert_eq!(t.display(&db).to_string(), "int & ~A | int & B");
}
#[test]
fn build_intersection_self_negation() {
let db = setup_db();
let ty = IntersectionBuilder::new(&db)
.add_positive(Type::none(&db))
.add_negative(Type::none(&db))
.build();
assert_eq!(ty, Type::Never);
}
#[test]
fn build_intersection_simplify_negative_never() {
let db = setup_db();
let ty = IntersectionBuilder::new(&db)
.add_positive(Type::none(&db))
.add_negative(Type::Never)
.build();
assert_eq!(ty, Type::none(&db));
}
#[test]
fn build_intersection_simplify_positive_never() {
let db = setup_db();
let ty = IntersectionBuilder::new(&db)
.add_positive(Type::none(&db))
.add_positive(Type::Never)
.build();
assert_eq!(ty, Type::Never);
}
#[test]
fn build_intersection_simplify_negative_none() {
let db = setup_db();
let ty = IntersectionBuilder::new(&db)
.add_negative(Type::none(&db))
.add_positive(Type::IntLiteral(1))
.build();
assert_eq!(ty, Type::IntLiteral(1));
let ty = IntersectionBuilder::new(&db)
.add_positive(Type::IntLiteral(1))
.add_negative(Type::none(&db))
.build();
assert_eq!(ty, Type::IntLiteral(1));
}
#[test]
fn build_negative_union_de_morgan() {
let db = setup_db();
let union = UnionBuilder::new(&db)
.add(Type::IntLiteral(1))
.add(Type::IntLiteral(2))
.build();
assert_eq!(union.display(&db).to_string(), "Literal[1, 2]");
let ty = IntersectionBuilder::new(&db).add_negative(union).build();
let expected = IntersectionBuilder::new(&db)
.add_negative(Type::IntLiteral(1))
.add_negative(Type::IntLiteral(2))
.build();
assert_eq!(ty.display(&db).to_string(), "~Literal[1] & ~Literal[2]");
assert_eq!(ty, expected);
}
#[test]
fn build_intersection_simplify_positive_type_and_positive_subtype() {
let db = setup_db();
let t = KnownClass::Str.to_instance(&db);
let s = Type::LiteralString;
let ty = IntersectionBuilder::new(&db)
.add_positive(t)
.add_positive(s)
.build();
assert_eq!(ty, s);
let ty = IntersectionBuilder::new(&db)
.add_positive(s)
.add_positive(t)
.build();
assert_eq!(ty, s);
let literal = Type::string_literal(&db, "a");
let expected = IntersectionBuilder::new(&db)
.add_positive(s)
.add_negative(literal)
.build();
let ty = IntersectionBuilder::new(&db)
.add_positive(t)
.add_negative(literal)
.add_positive(s)
.build();
assert_eq!(ty, expected);
let ty = IntersectionBuilder::new(&db)
.add_positive(s)
.add_negative(literal)
.add_positive(t)
.build();
assert_eq!(ty, expected);
}
#[test]
fn build_intersection_simplify_negative_type_and_negative_subtype() {
let db = setup_db();
let t = KnownClass::Str.to_instance(&db);
let s = Type::LiteralString;
let expected = IntersectionBuilder::new(&db).add_negative(t).build();
let ty = IntersectionBuilder::new(&db)
.add_negative(t)
.add_negative(s)
.build();
assert_eq!(ty, expected);
let ty = IntersectionBuilder::new(&db)
.add_negative(s)
.add_negative(t)
.build();
assert_eq!(ty, expected);
let object = KnownClass::Object.to_instance(&db);
let expected = IntersectionBuilder::new(&db)
.add_negative(t)
.add_positive(object)
.build();
let ty = IntersectionBuilder::new(&db)
.add_negative(t)
.add_positive(object)
.add_negative(s)
.build();
assert_eq!(ty, expected);
}
#[test]
fn build_intersection_simplify_negative_type_and_multiple_negative_subtypes() {
let db = setup_db();
let s1 = Type::IntLiteral(1);
let s2 = Type::IntLiteral(2);
let t = KnownClass::Int.to_instance(&db);
let expected = IntersectionBuilder::new(&db).add_negative(t).build();
let ty = IntersectionBuilder::new(&db)
.add_negative(s1)
.add_negative(s2)
.add_negative(t)
.build();
assert_eq!(ty, expected);
}
#[test]
fn build_intersection_simplify_negative_type_and_positive_subtype() {
let db = setup_db();
let t = KnownClass::Str.to_instance(&db);
let s = Type::LiteralString;
let ty = IntersectionBuilder::new(&db)
.add_negative(t)
.add_positive(s)
.build();
assert_eq!(ty, Type::Never);
let ty = IntersectionBuilder::new(&db)
.add_positive(s)
.add_negative(t)
.build();
assert_eq!(ty, Type::Never);
// This should also work in the presence of additional contributions:
let ty = IntersectionBuilder::new(&db)
.add_positive(KnownClass::Object.to_instance(&db))
.add_negative(t)
.add_positive(s)
.build();
assert_eq!(ty, Type::Never);
let ty = IntersectionBuilder::new(&db)
.add_positive(s)
.add_negative(Type::string_literal(&db, "a"))
.add_negative(t)
.build();
assert_eq!(ty, Type::Never);
}
#[test]
fn build_intersection_simplify_disjoint_positive_types() {
let db = setup_db();
let t1 = Type::IntLiteral(1);
let t2 = Type::none(&db);
let ty = IntersectionBuilder::new(&db)
.add_positive(t1)
.add_positive(t2)
.build();
assert_eq!(ty, Type::Never);
// If there are any negative contributions, they should
// be removed too.
let ty = IntersectionBuilder::new(&db)
.add_positive(KnownClass::Str.to_instance(&db))
.add_negative(Type::LiteralString)
.add_positive(t2)
.build();
assert_eq!(ty, Type::Never);
}
#[test]
fn build_intersection_simplify_disjoint_positive_and_negative_types() {
let db = setup_db();
let t_p = KnownClass::Int.to_instance(&db);
let t_n = Type::string_literal(&db, "t_n");
let ty = IntersectionBuilder::new(&db)
.add_positive(t_p)
.add_negative(t_n)
.build();
assert_eq!(ty, t_p);
let ty = IntersectionBuilder::new(&db)
.add_negative(t_n)
.add_positive(t_p)
.build();
assert_eq!(ty, t_p);
let int_literal = Type::IntLiteral(1);
let expected = IntersectionBuilder::new(&db)
.add_positive(t_p)
.add_negative(int_literal)
.build();
let ty = IntersectionBuilder::new(&db)
.add_positive(t_p)
.add_negative(int_literal)
.add_negative(t_n)
.build();
assert_eq!(ty, expected);
let ty = IntersectionBuilder::new(&db)
.add_negative(t_n)
.add_negative(int_literal)
.add_positive(t_p)
.build();
assert_eq!(ty, expected);
let intersection = IntersectionBuilder::new(&db).build();
assert_eq!(intersection, KnownClass::Object.to_instance(&db));
}
#[test_case(Type::BooleanLiteral(true))]
@ -957,85 +479,4 @@ mod tests {
.build();
assert_eq!(ty, Type::BooleanLiteral(!bool_value));
}
#[test_case(Type::any())]
#[test_case(Type::unknown())]
#[test_case(todo_type!())]
fn build_intersection_t_and_negative_t_does_not_simplify(ty: Type) {
let db = setup_db();
let result = IntersectionBuilder::new(&db)
.add_positive(ty)
.add_negative(ty)
.build();
assert_eq!(result, ty);
let result = IntersectionBuilder::new(&db)
.add_negative(ty)
.add_positive(ty)
.build();
assert_eq!(result, ty);
}
#[test]
fn build_intersection_of_two_unions_simplify() {
let mut db = setup_db();
db.write_dedented(
"/src/module.py",
"
class A: ...
class B: ...
a = A()
b = B()
",
)
.unwrap();
let file = system_path_to_file(&db, "src/module.py").expect("file to exist");
let a = global_symbol(&db, file, "a").expect_type();
let b = global_symbol(&db, file, "b").expect_type();
let union = UnionBuilder::new(&db).add(a).add(b).build();
assert_eq!(union.display(&db).to_string(), "A | B");
let reversed_union = UnionBuilder::new(&db).add(b).add(a).build();
assert_eq!(reversed_union.display(&db).to_string(), "B | A");
let intersection = IntersectionBuilder::new(&db)
.add_positive(union)
.add_positive(reversed_union)
.build();
assert_eq!(intersection.display(&db).to_string(), "B | A");
}
#[test]
fn build_union_of_two_intersections_simplify() {
let mut db = setup_db();
db.write_dedented(
"/src/module.py",
"
class A: ...
class B: ...
a = A()
b = B()
",
)
.unwrap();
let file = system_path_to_file(&db, "src/module.py").expect("file to exist");
let a = global_symbol(&db, file, "a").expect_type();
let b = global_symbol(&db, file, "b").expect_type();
let intersection = IntersectionBuilder::new(&db)
.add_positive(a)
.add_positive(b)
.build();
let reversed_intersection = IntersectionBuilder::new(&db)
.add_positive(b)
.add_positive(a)
.build();
let union = UnionBuilder::new(&db)
.add(intersection)
.add(reversed_intersection)
.build();
assert_eq!(union.display(&db).to_string(), "A & B");
}
}

View file

@ -310,6 +310,18 @@ enum IntersectionOn {
Right,
}
/// A helper to track if we already know that declared and inferred types are the same.
#[derive(Debug, Clone, PartialEq, Eq)]
enum DeclaredAndInferredType<'db> {
/// We know that both the declared and inferred types are the same.
AreTheSame(Type<'db>),
/// Declared and inferred types might be different, we need to check assignability.
MightBeDifferent {
declared_ty: Type<'db>,
inferred_ty: Type<'db>,
},
}
/// Builder to infer all types in a region.
///
/// A builder is used by creating it with [`new()`](TypeInferenceBuilder::new), and then calling
@ -895,20 +907,28 @@ impl<'db> TypeInferenceBuilder<'db> {
&mut self,
node: AnyNodeRef,
definition: Definition<'db>,
declared_ty: Type<'db>,
inferred_ty: Type<'db>,
declared_and_inferred_ty: &DeclaredAndInferredType<'db>,
) {
debug_assert!(definition.is_binding(self.db()));
debug_assert!(definition.is_declaration(self.db()));
let inferred_ty = if inferred_ty.is_assignable_to(self.db(), declared_ty) {
inferred_ty
let (declared_ty, inferred_ty) = match declared_and_inferred_ty {
DeclaredAndInferredType::AreTheSame(ty) => (ty, ty),
DeclaredAndInferredType::MightBeDifferent {
declared_ty,
inferred_ty,
} => {
if inferred_ty.is_assignable_to(self.db(), *declared_ty) {
(declared_ty, inferred_ty)
} else {
report_invalid_assignment(&self.context, node, declared_ty, inferred_ty);
report_invalid_assignment(&self.context, node, *declared_ty, *inferred_ty);
// if the assignment is invalid, fall back to assuming the annotation is correct
declared_ty
(declared_ty, declared_ty)
}
}
};
self.types.declarations.insert(definition, declared_ty);
self.types.bindings.insert(definition, inferred_ty);
self.types.declarations.insert(definition, *declared_ty);
self.types.bindings.insert(definition, *inferred_ty);
}
fn add_unknown_declaration_with_binding(
@ -916,7 +936,11 @@ impl<'db> TypeInferenceBuilder<'db> {
node: AnyNodeRef,
definition: Definition<'db>,
) {
self.add_declaration_with_binding(node, definition, Type::unknown(), Type::unknown());
self.add_declaration_with_binding(
node,
definition,
&DeclaredAndInferredType::AreTheSame(Type::unknown()),
);
}
fn infer_module(&mut self, module: &ast::ModModule) {
@ -1097,7 +1121,11 @@ impl<'db> TypeInferenceBuilder<'db> {
decorator_tys.into_boxed_slice(),
));
self.add_declaration_with_binding(function.into(), definition, function_ty, function_ty);
self.add_declaration_with_binding(
function.into(),
definition,
&DeclaredAndInferredType::AreTheSame(function_ty),
);
}
fn infer_parameters(&mut self, parameters: &ast::Parameters) {
@ -1188,15 +1216,18 @@ impl<'db> TypeInferenceBuilder<'db> {
.map(|default| self.file_expression_ty(default));
if let Some(annotation) = parameter.annotation.as_ref() {
let declared_ty = self.file_expression_ty(annotation);
let inferred_ty = if let Some(default_ty) = default_ty {
let declared_and_inferred_ty = if let Some(default_ty) = default_ty {
if default_ty.is_assignable_to(self.db(), declared_ty) {
UnionType::from_elements(self.db(), [declared_ty, default_ty])
DeclaredAndInferredType::MightBeDifferent {
declared_ty,
inferred_ty: UnionType::from_elements(self.db(), [declared_ty, default_ty]),
}
} else if self.in_stub()
&& default
.as_ref()
.is_some_and(|d| d.is_ellipsis_literal_expr())
{
declared_ty
DeclaredAndInferredType::AreTheSame(declared_ty)
} else {
self.context.report_lint(
&INVALID_PARAMETER_DEFAULT,
@ -1205,16 +1236,15 @@ impl<'db> TypeInferenceBuilder<'db> {
"Default value of type `{}` is not assignable to annotated parameter type `{}`",
default_ty.display(self.db()), declared_ty.display(self.db())),
);
declared_ty
DeclaredAndInferredType::AreTheSame(declared_ty)
}
} else {
declared_ty
DeclaredAndInferredType::AreTheSame(declared_ty)
};
self.add_declaration_with_binding(
parameter.into(),
definition,
declared_ty,
inferred_ty,
&declared_and_inferred_ty,
);
} else {
let ty = if let Some(default_ty) = default_ty {
@ -1240,7 +1270,11 @@ impl<'db> TypeInferenceBuilder<'db> {
let _annotated_ty = self.file_expression_ty(annotation);
// TODO `tuple[annotated_ty, ...]`
let ty = KnownClass::Tuple.to_instance(self.db());
self.add_declaration_with_binding(parameter.into(), definition, ty, ty);
self.add_declaration_with_binding(
parameter.into(),
definition,
&DeclaredAndInferredType::AreTheSame(ty),
);
} else {
self.add_binding(
parameter.into(),
@ -1265,7 +1299,11 @@ impl<'db> TypeInferenceBuilder<'db> {
let _annotated_ty = self.file_expression_ty(annotation);
// TODO `dict[str, annotated_ty]`
let ty = KnownClass::Dict.to_instance(self.db());
self.add_declaration_with_binding(parameter.into(), definition, ty, ty);
self.add_declaration_with_binding(
parameter.into(),
definition,
&DeclaredAndInferredType::AreTheSame(ty),
);
} else {
self.add_binding(
parameter.into(),
@ -1308,7 +1346,11 @@ impl<'db> TypeInferenceBuilder<'db> {
let class = Class::new(self.db(), &name.id, body_scope, maybe_known_class);
let class_ty = Type::class_literal(class);
self.add_declaration_with_binding(class_node.into(), definition, class_ty, class_ty);
self.add_declaration_with_binding(
class_node.into(),
definition,
&DeclaredAndInferredType::AreTheSame(class_ty),
);
// if there are type parameters, then the keywords and bases are within that scope
// and we don't need to run inference here
@ -1365,8 +1407,7 @@ impl<'db> TypeInferenceBuilder<'db> {
self.add_declaration_with_binding(
type_alias.into(),
definition,
type_alias_ty,
type_alias_ty,
&DeclaredAndInferredType::AreTheSame(type_alias_ty),
);
}
@ -1718,7 +1759,11 @@ impl<'db> TypeInferenceBuilder<'db> {
bound_or_constraint,
default_ty,
)));
self.add_declaration_with_binding(node.into(), definition, ty, ty);
self.add_declaration_with_binding(
node.into(),
definition,
&DeclaredAndInferredType::AreTheSame(ty),
);
}
fn infer_paramspec_definition(
@ -1733,7 +1778,11 @@ impl<'db> TypeInferenceBuilder<'db> {
} = node;
self.infer_optional_expression(default.as_deref());
let pep_695_todo = todo_type!("PEP-695 ParamSpec definition types");
self.add_declaration_with_binding(node.into(), definition, pep_695_todo, pep_695_todo);
self.add_declaration_with_binding(
node.into(),
definition,
&DeclaredAndInferredType::AreTheSame(pep_695_todo),
);
}
fn infer_typevartuple_definition(
@ -1748,7 +1797,11 @@ impl<'db> TypeInferenceBuilder<'db> {
} = node;
self.infer_optional_expression(default.as_deref());
let pep_695_todo = todo_type!("PEP-695 TypeVarTuple definition types");
self.add_declaration_with_binding(node.into(), definition, pep_695_todo, pep_695_todo);
self.add_declaration_with_binding(
node.into(),
definition,
&DeclaredAndInferredType::AreTheSame(pep_695_todo),
);
}
fn infer_match_statement(&mut self, match_statement: &ast::StmtMatch) {
@ -2006,13 +2059,13 @@ impl<'db> TypeInferenceBuilder<'db> {
simple: _,
} = assignment;
let mut annotation_ty = self.infer_annotation_expression(
let mut declared_ty = self.infer_annotation_expression(
annotation,
DeferredExpressionState::from(self.are_all_types_deferred()),
);
// Handle various singletons.
if let Type::Instance(InstanceType { class }) = annotation_ty {
if let Type::Instance(InstanceType { class }) = declared_ty {
if class.is_known(self.db(), KnownClass::SpecialForm) {
if let Some(name_expr) = target.as_name_expr() {
if let Some(known_instance) = KnownInstanceType::try_from_file_and_name(
@ -2020,27 +2073,29 @@ impl<'db> TypeInferenceBuilder<'db> {
self.file(),
&name_expr.id,
) {
annotation_ty = Type::KnownInstance(known_instance);
declared_ty = Type::KnownInstance(known_instance);
}
}
}
}
if let Some(value) = value.as_deref() {
let value_ty = self.infer_expression(value);
let value_ty = if self.in_stub() && value.is_ellipsis_literal_expr() {
annotation_ty
let inferred_ty = self.infer_expression(value);
let inferred_ty = if self.in_stub() && value.is_ellipsis_literal_expr() {
declared_ty
} else {
value_ty
inferred_ty
};
self.add_declaration_with_binding(
assignment.into(),
definition,
annotation_ty,
value_ty,
&DeclaredAndInferredType::MightBeDifferent {
declared_ty,
inferred_ty,
},
);
} else {
self.add_declaration(assignment.into(), definition, annotation_ty);
self.add_declaration(assignment.into(), definition, declared_ty);
}
self.infer_expression(target);
@ -2294,7 +2349,11 @@ impl<'db> TypeInferenceBuilder<'db> {
full_module_ty
};
self.add_declaration_with_binding(alias.into(), definition, binding_ty, binding_ty);
self.add_declaration_with_binding(
alias.into(),
definition,
&DeclaredAndInferredType::AreTheSame(binding_ty),
);
}
fn infer_import_from_statement(&mut self, import: &ast::StmtImportFrom) {
@ -2470,7 +2529,11 @@ impl<'db> TypeInferenceBuilder<'db> {
format_args!("Member `{name}` of module `{module_name}` is possibly unbound",),
);
}
self.add_declaration_with_binding(alias.into(), definition, ty, ty);
self.add_declaration_with_binding(
alias.into(),
definition,
&DeclaredAndInferredType::AreTheSame(ty),
);
return;
};
@ -2496,8 +2559,7 @@ impl<'db> TypeInferenceBuilder<'db> {
self.add_declaration_with_binding(
alias.into(),
definition,
submodule_ty,
submodule_ty,
&DeclaredAndInferredType::AreTheSame(submodule_ty),
);
return;
}