mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 13:24:57 +00:00
[ty] Remove Type::Tuple
(#19669)
This commit is contained in:
parent
2abd683376
commit
d2fbf2af8f
27 changed files with 1189 additions and 1225 deletions
|
@ -183,33 +183,27 @@ def takes_at_least_two_positional_only(x: int, y: int, /, *args) -> None: ...
|
|||
class SingleElementTuple(tuple[int]): ...
|
||||
|
||||
def _(args: SingleElementTuple) -> None:
|
||||
# TODO: we should emit `[too-many-positional-arguments]` here
|
||||
takes_zero(*args)
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
|
||||
takes_one(*args)
|
||||
|
||||
# TODO: we should emit `[missing-argument]` on both of these
|
||||
takes_two(*args)
|
||||
takes_two_positional_only(*args)
|
||||
takes_two(*args) # error: [missing-argument]
|
||||
takes_two_positional_only(*args) # error: [missing-argument]
|
||||
|
||||
# TODO: these should both be `[missing-argument]`, not `[invalid-argument-type]`
|
||||
takes_two_different(*args) # error: [invalid-argument-type]
|
||||
takes_two_different_positional_only(*args) # error: [invalid-argument-type]
|
||||
takes_two_different(*args) # error: [missing-argument]
|
||||
takes_two_different_positional_only(*args) # error: [missing-argument]
|
||||
|
||||
takes_at_least_zero(*args)
|
||||
takes_at_least_one(*args)
|
||||
|
||||
# TODO: we should emit `[missing-argument]` on both of these
|
||||
takes_at_least_two(*args)
|
||||
takes_at_least_two_positional_only(*args)
|
||||
takes_at_least_two(*args) # error: [missing-argument]
|
||||
takes_at_least_two_positional_only(*args) # error: [missing-argument]
|
||||
|
||||
class TwoElementIntTuple(tuple[int, int]): ...
|
||||
|
||||
def _(args: TwoElementIntTuple) -> None:
|
||||
# TODO: we should emit `[too-many-positional-arguments]` on both of these
|
||||
takes_zero(*args)
|
||||
takes_one(*args)
|
||||
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
takes_one(*args) # error: [too-many-positional-arguments]
|
||||
takes_two(*args)
|
||||
takes_two_positional_only(*args)
|
||||
takes_two_different(*args) # error: [invalid-argument-type]
|
||||
|
@ -222,40 +216,23 @@ def _(args: TwoElementIntTuple) -> None:
|
|||
class IntStrTuple(tuple[int, str]): ...
|
||||
|
||||
def _(args: IntStrTuple) -> None:
|
||||
# TODO: we should emit `[too-many-positional-arguments]` here
|
||||
takes_zero(*args)
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
|
||||
# TODO: this should be `[too-many-positional-arguments]`, not `[invalid-argument-type]`
|
||||
takes_one(*args) # error: [invalid-argument-type]
|
||||
takes_one(*args) # error: [too-many-positional-arguments]
|
||||
|
||||
# TODO: we should have one diagnostic for each of these, not two
|
||||
# error: [invalid-argument-type]
|
||||
# error: [invalid-argument-type]
|
||||
takes_two(*args)
|
||||
# error: [invalid-argument-type]
|
||||
# error: [invalid-argument-type]
|
||||
takes_two_positional_only(*args)
|
||||
|
||||
# TODO: these are all false positives
|
||||
# error: [invalid-argument-type]
|
||||
# error: [invalid-argument-type]
|
||||
takes_two_different(*args)
|
||||
# error: [invalid-argument-type]
|
||||
# error: [invalid-argument-type]
|
||||
takes_two_different_positional_only(*args)
|
||||
|
||||
takes_at_least_zero(*args)
|
||||
|
||||
# TODO: false positive
|
||||
# error: [invalid-argument-type]
|
||||
takes_at_least_one(*args)
|
||||
|
||||
# TODO: we should only emit one diagnostic for each of these, not two
|
||||
# error: [invalid-argument-type]
|
||||
# error: [invalid-argument-type]
|
||||
takes_at_least_two(*args)
|
||||
# error: [invalid-argument-type]
|
||||
# error: [invalid-argument-type]
|
||||
takes_at_least_two_positional_only(*args)
|
||||
```
|
||||
|
||||
|
@ -377,9 +354,7 @@ def takes_at_least_two_positional_only(x: int, y: int, /, *args) -> None: ...
|
|||
class IntStarInt(tuple[int, *tuple[int, ...]]): ...
|
||||
|
||||
def _(args: IntStarInt) -> None:
|
||||
# TODO: we should emit `[too-many-positional-arguments]` here
|
||||
takes_zero(*args)
|
||||
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
takes_one(*args)
|
||||
takes_two(*args)
|
||||
takes_two_positional_only(*args)
|
||||
|
@ -393,50 +368,32 @@ def _(args: IntStarInt) -> None:
|
|||
class IntStarStr(tuple[int, *tuple[str, ...]]): ...
|
||||
|
||||
def _(args: IntStarStr) -> None:
|
||||
# TODO: we should emit `[too-many-positional-arguments]` here
|
||||
takes_zero(*args)
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
|
||||
# TODO: false positive
|
||||
# error: [invalid-argument-type]
|
||||
takes_one(*args)
|
||||
|
||||
# TODO: we should only emit one diagnostic for each of these, not two
|
||||
# error: [invalid-argument-type]
|
||||
# error: [invalid-argument-type]
|
||||
takes_two(*args)
|
||||
# error: [invalid-argument-type]
|
||||
# error: [invalid-argument-type]
|
||||
takes_two_positional_only(*args)
|
||||
|
||||
# TODO: false positives
|
||||
# error: [invalid-argument-type]
|
||||
# error: [invalid-argument-type]
|
||||
takes_two_different(*args)
|
||||
# error: [invalid-argument-type]
|
||||
# error: [invalid-argument-type]
|
||||
takes_two_different_positional_only(*args)
|
||||
|
||||
takes_at_least_zero(*args)
|
||||
|
||||
# TODO: false positive
|
||||
# error: [invalid-argument-type]
|
||||
takes_at_least_one(*args)
|
||||
|
||||
# TODO: we should only have one diagnostic for each of these, not two
|
||||
# error: [invalid-argument-type]
|
||||
# error: [invalid-argument-type]
|
||||
takes_at_least_two(*args)
|
||||
# error: [invalid-argument-type]
|
||||
# error: [invalid-argument-type]
|
||||
takes_at_least_two_positional_only(*args)
|
||||
|
||||
class IntIntStarInt(tuple[int, int, *tuple[int, ...]]): ...
|
||||
|
||||
def _(args: IntIntStarInt) -> None:
|
||||
# TODO: we should emit `[too-many-positional-arguments]` on both of these
|
||||
takes_zero(*args)
|
||||
takes_one(*args)
|
||||
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
takes_one(*args) # error: [too-many-positional-arguments]
|
||||
takes_two(*args)
|
||||
takes_two_positional_only(*args)
|
||||
takes_two_different(*args) # error: [invalid-argument-type]
|
||||
|
@ -449,51 +406,31 @@ def _(args: IntIntStarInt) -> None:
|
|||
class IntIntStarStr(tuple[int, int, *tuple[str, ...]]): ...
|
||||
|
||||
def _(args: IntIntStarStr) -> None:
|
||||
# TODO: we should emit `[too-many-positional-arguments]` here
|
||||
takes_zero(*args)
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
|
||||
# TODO: this should be `[too-many-positional-arguments]`, not `invalid-argument-type`
|
||||
takes_one(*args) # error: [invalid-argument-type]
|
||||
takes_one(*args) # error: [too-many-positional-arguments]
|
||||
|
||||
# TODO: these are all false positives
|
||||
# error: [invalid-argument-type]
|
||||
# error: [invalid-argument-type]
|
||||
takes_two(*args)
|
||||
# error: [invalid-argument-type]
|
||||
# error: [invalid-argument-type]
|
||||
takes_two_positional_only(*args)
|
||||
|
||||
# TODO: each of these should only have one diagnostic, not two
|
||||
# error: [invalid-argument-type]
|
||||
# error: [invalid-argument-type]
|
||||
takes_two_different(*args)
|
||||
# error: [invalid-argument-type]
|
||||
# error: [invalid-argument-type]
|
||||
takes_two_different_positional_only(*args)
|
||||
|
||||
takes_at_least_zero(*args)
|
||||
|
||||
# TODO: false positive
|
||||
# error: [invalid-argument-type]
|
||||
takes_at_least_one(*args)
|
||||
|
||||
# TODO: these are both false positives
|
||||
# error: [invalid-argument-type]
|
||||
# error: [invalid-argument-type]
|
||||
takes_at_least_two(*args)
|
||||
|
||||
# TODO: these are both false positives
|
||||
# error: [invalid-argument-type]
|
||||
# error: [invalid-argument-type]
|
||||
takes_at_least_two_positional_only(*args)
|
||||
|
||||
class IntStarIntInt(tuple[int, *tuple[int, ...], int]): ...
|
||||
|
||||
def _(args: IntStarIntInt) -> None:
|
||||
# TODO: we should emit `[too-many-positional-arguments]` on both of these
|
||||
takes_zero(*args)
|
||||
takes_one(*args)
|
||||
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
takes_one(*args) # error: [too-many-positional-arguments]
|
||||
takes_two(*args)
|
||||
takes_two_positional_only(*args)
|
||||
takes_two_different(*args) # error: [invalid-argument-type]
|
||||
|
@ -506,40 +443,25 @@ def _(args: IntStarIntInt) -> None:
|
|||
class IntStarStrInt(tuple[int, *tuple[str, ...], int]): ...
|
||||
|
||||
def _(args: IntStarStrInt) -> None:
|
||||
# TODO: we should emit `too-many-positional-arguments` here
|
||||
takes_zero(*args)
|
||||
takes_zero(*args) # error: [too-many-positional-arguments]
|
||||
|
||||
# TODO: this should be `too-many-positional-arguments`, not `invalid-argument-type`
|
||||
takes_one(*args) # error: [invalid-argument-type]
|
||||
takes_one(*args) # error: [too-many-positional-arguments]
|
||||
|
||||
# TODO: we should only emit one diagnostic for each of these
|
||||
# error: [invalid-argument-type]
|
||||
# error: [invalid-argument-type]
|
||||
takes_two(*args)
|
||||
# error: [invalid-argument-type]
|
||||
# error: [invalid-argument-type]
|
||||
takes_two_positional_only(*args)
|
||||
|
||||
# TODO: we should not emit diagnostics for these
|
||||
# error: [invalid-argument-type]
|
||||
# error: [invalid-argument-type]
|
||||
takes_two_different(*args)
|
||||
# error: [invalid-argument-type]
|
||||
# error: [invalid-argument-type]
|
||||
takes_two_different_positional_only(*args)
|
||||
|
||||
takes_at_least_zero(*args)
|
||||
|
||||
# TODO: false positive
|
||||
takes_at_least_one(*args) # error: [invalid-argument-type]
|
||||
takes_at_least_one(*args)
|
||||
|
||||
# TODO: should only have one diagnostic here
|
||||
# error: [invalid-argument-type]
|
||||
# error: [invalid-argument-type]
|
||||
takes_at_least_two(*args)
|
||||
|
||||
# TODO: should only have one diagnostic here
|
||||
# error: [invalid-argument-type]
|
||||
# error: [invalid-argument-type]
|
||||
takes_at_least_two_positional_only(*args)
|
||||
```
|
||||
|
|
|
@ -411,10 +411,9 @@ def test_seq(x: Sequence[T]) -> Sequence[T]:
|
|||
return x
|
||||
|
||||
def func8(t1: tuple[complex, list[int]], t2: tuple[int, *tuple[str, ...]], t3: tuple[()]):
|
||||
# TODO: should be `Sequence[int | float | complex | list[int]]`
|
||||
reveal_type(test_seq(t1)) # revealed: Sequence[Unknown]
|
||||
# TODO: should be `Sequence[int | str]`
|
||||
reveal_type(test_seq(t2)) # revealed: Sequence[Unknown]
|
||||
reveal_type(test_seq(t1)) # revealed: Sequence[int | float | complex | list[int]]
|
||||
reveal_type(test_seq(t2)) # revealed: Sequence[int | str]
|
||||
|
||||
# TODO: this should be `Sequence[Never]`
|
||||
reveal_type(test_seq(t3)) # revealed: Sequence[Unknown]
|
||||
```
|
||||
|
|
|
@ -367,10 +367,9 @@ def test_seq[T](x: Sequence[T]) -> Sequence[T]:
|
|||
return x
|
||||
|
||||
def func8(t1: tuple[complex, list[int]], t2: tuple[int, *tuple[str, ...]], t3: tuple[()]):
|
||||
# TODO: should be `Sequence[int | float | complex | list[int]]`
|
||||
reveal_type(test_seq(t1)) # revealed: Sequence[Unknown]
|
||||
# TODO: should be `Sequence[int | str]`
|
||||
reveal_type(test_seq(t2)) # revealed: Sequence[Unknown]
|
||||
reveal_type(test_seq(t1)) # revealed: Sequence[int | float | complex | list[int]]
|
||||
reveal_type(test_seq(t2)) # revealed: Sequence[int | str]
|
||||
|
||||
# TODO: this should be `Sequence[Never]`
|
||||
reveal_type(test_seq(t3)) # revealed: Sequence[Unknown]
|
||||
```
|
||||
|
|
|
@ -23,6 +23,7 @@ We can then place custom stub files in `/typeshed/stdlib`, for example:
|
|||
|
||||
```pyi
|
||||
class object: ...
|
||||
class tuple: ...
|
||||
class BuiltinClass: ...
|
||||
|
||||
builtin_symbol: BuiltinClass
|
||||
|
@ -97,6 +98,12 @@ simple untyped definition is enough to make `reveal_type` work in tests:
|
|||
typeshed = "/typeshed"
|
||||
```
|
||||
|
||||
`/typeshed/stdlib/builtins.pyi`:
|
||||
|
||||
```pyi
|
||||
class tuple: ...
|
||||
```
|
||||
|
||||
`/typeshed/stdlib/typing_extensions.pyi`:
|
||||
|
||||
```pyi
|
||||
|
@ -104,5 +111,5 @@ def reveal_type(obj, /): ...
|
|||
```
|
||||
|
||||
```py
|
||||
reveal_type(()) # revealed: tuple[()]
|
||||
reveal_type(()) # revealed: tuple
|
||||
```
|
||||
|
|
|
@ -193,6 +193,7 @@ typeshed = "/typeshed"
|
|||
|
||||
```pyi
|
||||
class object: ...
|
||||
class tuple: ...
|
||||
class int: ...
|
||||
class bytes: ...
|
||||
|
||||
|
|
|
@ -39,9 +39,13 @@ class HeterogeneousSubclass0(tuple[()]): ...
|
|||
reveal_type(HeterogeneousSubclass0.__getitem__)
|
||||
|
||||
def f0(h0: HeterogeneousSubclass0, i: int):
|
||||
reveal_type(h0[0]) # revealed: Never
|
||||
reveal_type(h0[1]) # revealed: Never
|
||||
reveal_type(h0[-1]) # revealed: Never
|
||||
# error: [index-out-of-bounds]
|
||||
reveal_type(h0[0]) # revealed: Unknown
|
||||
# error: [index-out-of-bounds]
|
||||
reveal_type(h0[1]) # revealed: Unknown
|
||||
# error: [index-out-of-bounds]
|
||||
reveal_type(h0[-1]) # revealed: Unknown
|
||||
|
||||
reveal_type(h0[i]) # revealed: Never
|
||||
|
||||
class HeterogeneousSubclass1(tuple[I0]): ...
|
||||
|
@ -51,7 +55,8 @@ reveal_type(HeterogeneousSubclass1.__getitem__)
|
|||
|
||||
def f0(h1: HeterogeneousSubclass1, i: int):
|
||||
reveal_type(h1[0]) # revealed: I0
|
||||
reveal_type(h1[1]) # revealed: I0
|
||||
# error: [index-out-of-bounds]
|
||||
reveal_type(h1[1]) # revealed: Unknown
|
||||
reveal_type(h1[-1]) # revealed: I0
|
||||
reveal_type(h1[i]) # revealed: I0
|
||||
|
||||
|
@ -84,25 +89,19 @@ def g(m: MixedSubclass, i: int):
|
|||
reveal_type(m[2]) # revealed: I1 | I2 | I3
|
||||
reveal_type(m[3]) # revealed: I1 | I2 | I3
|
||||
reveal_type(m[4]) # revealed: I1 | I2 | I3 | I5
|
||||
reveal_type(m[5]) # revealed: I1 | I2 | I3 | I5
|
||||
reveal_type(m[10]) # revealed: I1 | I2 | I3 | I5
|
||||
|
||||
reveal_type(m[-1]) # revealed: I5
|
||||
reveal_type(m[-2]) # revealed: I2
|
||||
reveal_type(m[-3]) # revealed: I3
|
||||
reveal_type(m[-4]) # revealed: I2
|
||||
reveal_type(m[-5]) # revealed: I1 | I0
|
||||
reveal_type(m[-5]) # revealed: I0 | I1
|
||||
reveal_type(m[-6]) # revealed: I0 | I1
|
||||
reveal_type(m[-10]) # revealed: I0 | I1
|
||||
|
||||
reveal_type(m[i]) # revealed: I0 | I1 | I2 | I3 | I5
|
||||
|
||||
# Ideally we would not include `I0` in the unions for these,
|
||||
# but it's not possible to do this using only synthesized overloads.
|
||||
reveal_type(m[5]) # revealed: I0 | I1 | I2 | I3 | I5
|
||||
reveal_type(m[10]) # revealed: I0 | I1 | I2 | I3 | I5
|
||||
|
||||
# Similarly, ideally these would just be `I0` | I1`,
|
||||
# but achieving that with only synthesized overloads wouldn't be possible
|
||||
reveal_type(m[-6]) # revealed: I0 | I1 | I2 | I3 | I5
|
||||
reveal_type(m[-10]) # revealed: I0 | I1 | I2 | I3 | I5
|
||||
|
||||
class MixedSubclass2(tuple[I0, I1, *tuple[I2, ...], I3]): ...
|
||||
|
||||
# revealed: Overload[(self, index: Literal[0], /) -> I0, (self, index: Literal[-2], /) -> I2 | I1, (self, index: Literal[1], /) -> I1, (self, index: Literal[-3], /) -> I2 | I1 | I0, (self, index: Literal[-1], /) -> I3, (self, index: Literal[2], /) -> I2 | I3, (self, index: SupportsIndex, /) -> I0 | I1 | I2 | I3, (self, index: slice[Any, Any, Any], /) -> tuple[I0 | I1 | I2 | I3, ...]]
|
||||
|
@ -112,18 +111,12 @@ def g(m: MixedSubclass2, i: int):
|
|||
reveal_type(m[0]) # revealed: I0
|
||||
reveal_type(m[1]) # revealed: I1
|
||||
reveal_type(m[2]) # revealed: I2 | I3
|
||||
|
||||
# Ideally this would just be `I2 | I3`,
|
||||
# but that's not possible to achieve with synthesized overloads
|
||||
reveal_type(m[3]) # revealed: I0 | I1 | I2 | I3
|
||||
reveal_type(m[3]) # revealed: I2 | I3
|
||||
|
||||
reveal_type(m[-1]) # revealed: I3
|
||||
reveal_type(m[-2]) # revealed: I2 | I1
|
||||
reveal_type(m[-3]) # revealed: I2 | I1 | I0
|
||||
|
||||
# Ideally this would just be `I2 | I1 | I0`,
|
||||
# but that's not possible to achieve with synthesized overloads
|
||||
reveal_type(m[-4]) # revealed: I0 | I1 | I2 | I3
|
||||
reveal_type(m[-2]) # revealed: I1 | I2
|
||||
reveal_type(m[-3]) # revealed: I0 | I1 | I2
|
||||
reveal_type(m[-4]) # revealed: I0 | I1 | I2
|
||||
```
|
||||
|
||||
The stdlib API `os.stat` is a commonly used API that returns an instance of a tuple subclass
|
||||
|
@ -225,75 +218,47 @@ class I3: ...
|
|||
class HeterogeneousTupleSubclass(tuple[I0, I1, I2, I3]): ...
|
||||
|
||||
def __(t: HeterogeneousTupleSubclass, m: int, n: int):
|
||||
# TODO: should be `tuple[()]`
|
||||
reveal_type(t[0:0]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||
# TODO: should be `tuple[I0]`
|
||||
reveal_type(t[0:1]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||
# TODO: should be `tuple[I0, I1]`
|
||||
reveal_type(t[0:2]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||
# TODO: should be tuple[I0, I1, I2, I3]`
|
||||
reveal_type(t[0:4]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||
# TODO: should be tuple[I0, I1, I2, I3]`
|
||||
reveal_type(t[0:5]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||
# TODO: should be `tuple[I0, I1]`
|
||||
reveal_type(t[1:3]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||
reveal_type(t[0:0]) # revealed: tuple[()]
|
||||
reveal_type(t[0:1]) # revealed: tuple[I0]
|
||||
reveal_type(t[0:2]) # revealed: tuple[I0, I1]
|
||||
reveal_type(t[0:4]) # revealed: tuple[I0, I1, I2, I3]
|
||||
reveal_type(t[0:5]) # revealed: tuple[I0, I1, I2, I3]
|
||||
reveal_type(t[1:3]) # revealed: tuple[I1, I2]
|
||||
|
||||
# TODO: should be `tuple[I2, I3]`
|
||||
reveal_type(t[-2:4]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||
# TODO: should be `tuple[I1, I2]`
|
||||
reveal_type(t[-3:-1]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||
# TODO: should be `tuple[I0, I1, I2, I3]`
|
||||
reveal_type(t[-10:10]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||
reveal_type(t[-2:4]) # revealed: tuple[I2, I3]
|
||||
reveal_type(t[-3:-1]) # revealed: tuple[I1, I2]
|
||||
reveal_type(t[-10:10]) # revealed: tuple[I0, I1, I2, I3]
|
||||
|
||||
# TODO: should be `tuple[I0, I1, I2, I3]`
|
||||
reveal_type(t[0:]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||
# TODO: should be `tuple[I2, I3]`
|
||||
reveal_type(t[2:]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||
# TODO: should be `tuple[()]`
|
||||
reveal_type(t[4:]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||
# TODO: should be `tuple[()]`
|
||||
reveal_type(t[:0]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||
# TODO: should be `tuple[I0, I1]`
|
||||
reveal_type(t[:2]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||
# TODO: should be `tuple[I0, I1, I2, I3]`
|
||||
reveal_type(t[:10]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||
# TODO: should be `tuple[I0, I1, I2, I3]`
|
||||
reveal_type(t[:]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||
reveal_type(t[0:]) # revealed: tuple[I0, I1, I2, I3]
|
||||
reveal_type(t[2:]) # revealed: tuple[I2, I3]
|
||||
reveal_type(t[4:]) # revealed: tuple[()]
|
||||
reveal_type(t[:0]) # revealed: tuple[()]
|
||||
reveal_type(t[:2]) # revealed: tuple[I0, I1]
|
||||
reveal_type(t[:10]) # revealed: tuple[I0, I1, I2, I3]
|
||||
reveal_type(t[:]) # revealed: tuple[I0, I1, I2, I3]
|
||||
|
||||
# TODO: should be `tuple[I3, I2, I1, I0]`
|
||||
reveal_type(t[::-1]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||
# TODO: should be `tuple[I0, I2]`
|
||||
reveal_type(t[::2]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||
# TODO: should be `tuple[I2, I1, I0]`
|
||||
reveal_type(t[-2:-5:-1]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||
# TODO: should be `tuple[I3, I1]`
|
||||
reveal_type(t[::-2]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||
# TODO: should be `tuple[I3, I0]`
|
||||
reveal_type(t[-1::-3]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||
reveal_type(t[::-1]) # revealed: tuple[I3, I2, I1, I0]
|
||||
reveal_type(t[::2]) # revealed: tuple[I0, I2]
|
||||
reveal_type(t[-2:-5:-1]) # revealed: tuple[I2, I1, I0]
|
||||
reveal_type(t[::-2]) # revealed: tuple[I3, I1]
|
||||
reveal_type(t[-1::-3]) # revealed: tuple[I3, I0]
|
||||
|
||||
# TODO: should be `tuple[I0, I1]`
|
||||
reveal_type(t[None:2:None]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||
# TODO: should be `tuple[I1, I2, I3]`
|
||||
reveal_type(t[1:None:1]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||
# TODO: should be `tuple[I0, I1, I2, I3]`
|
||||
reveal_type(t[None:None:None]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||
reveal_type(t[None:2:None]) # revealed: tuple[I0, I1]
|
||||
reveal_type(t[1:None:1]) # revealed: tuple[I1, I2, I3]
|
||||
reveal_type(t[None:None:None]) # revealed: tuple[I0, I1, I2, I3]
|
||||
|
||||
start = 1
|
||||
stop = None
|
||||
step = 2
|
||||
# TODO: should be `tuple[I1, I3]`
|
||||
reveal_type(t[start:stop:step]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||
reveal_type(t[start:stop:step]) # revealed: tuple[I1, I3]
|
||||
|
||||
# TODO: should be `tuple[I0]`
|
||||
reveal_type(t[False:True]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||
# TODO: should be `tuple[I1, I2]`
|
||||
reveal_type(t[True:3]) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||
reveal_type(t[False:True]) # revealed: tuple[I0]
|
||||
reveal_type(t[True:3]) # revealed: tuple[I1, I2]
|
||||
|
||||
# TODO: we should emit `zero-stepsize-in-slice` on all of these:
|
||||
t[0:4:0]
|
||||
t[:4:0]
|
||||
t[0::0]
|
||||
t[::0]
|
||||
t[0:4:0] # error: [zero-stepsize-in-slice]
|
||||
t[:4:0] # error: [zero-stepsize-in-slice]
|
||||
t[0::0] # error: [zero-stepsize-in-slice]
|
||||
t[::0] # error: [zero-stepsize-in-slice]
|
||||
|
||||
tuple_slice = t[m:n]
|
||||
reveal_type(tuple_slice) # revealed: tuple[I0 | I1 | I2 | I3, ...]
|
||||
|
|
|
@ -502,10 +502,10 @@ For covariant types, such as `frozenset`, the ideal behaviour would be to not pr
|
|||
types to their instance supertypes: doing so causes more false positives than it fixes:
|
||||
|
||||
```py
|
||||
# TODO: should be `frozenset[Literal[1, 2, 3]]`
|
||||
reveal_type(frozenset((1, 2, 3))) # revealed: frozenset[Unknown]
|
||||
# TODO: should be `frozenset[tuple[Literal[1], Literal[2], Literal[3]]]`
|
||||
reveal_type(frozenset(((1, 2, 3),))) # revealed: frozenset[Unknown]
|
||||
# TODO: better here would be `frozenset[Literal[1, 2, 3]]`
|
||||
reveal_type(frozenset((1, 2, 3))) # revealed: frozenset[int]
|
||||
# TODO: better here would be `frozenset[tuple[Literal[1], Literal[2], Literal[3]]]`
|
||||
reveal_type(frozenset(((1, 2, 3),))) # revealed: frozenset[tuple[int, int, int]]
|
||||
```
|
||||
|
||||
Literals are always promoted for invariant containers such as `list`, however, even though this can
|
||||
|
@ -514,15 +514,15 @@ in some cases cause false positives:
|
|||
```py
|
||||
from typing import Literal
|
||||
|
||||
# TODO: should be `list[int]`
|
||||
reveal_type(list((1, 2, 3))) # revealed: list[Unknown]
|
||||
# TODO: should be `list[tuple[int, int, int]]`
|
||||
reveal_type(list(((1, 2, 3),))) # revealed: list[Unknown]
|
||||
reveal_type(list((1, 2, 3))) # revealed: list[int]
|
||||
reveal_type(list(((1, 2, 3),))) # revealed: list[tuple[int, int, int]]
|
||||
|
||||
# TODO: we could bidirectionally infer that the user does not want literals to be promoted here,
|
||||
# and avoid this diagnostic
|
||||
#
|
||||
# error: [invalid-assignment] "`list[int]` is not assignable to `list[Literal[1, 2, 3]]`"
|
||||
x: list[Literal[1, 2, 3]] = list((1, 2, 3))
|
||||
|
||||
# TODO: should be `list[Literal[1, 2, 3]]`
|
||||
reveal_type(x) # revealed: list[Unknown]
|
||||
reveal_type(x) # revealed: list[Literal[1, 2, 3]]
|
||||
```
|
||||
|
||||
[not a singleton type]: https://discuss.python.org/t/should-we-specify-in-the-language-reference-that-the-empty-tuple-is-a-singleton/67957
|
||||
|
|
|
@ -26,10 +26,8 @@ class HeterogeneousTupleSubclass(tuple[Literal[True], Literal[1]]): ...
|
|||
# from being overridden on a tuple subclass. This is something we plan to do as part of
|
||||
# our implementation of the Liskov Substitution Principle
|
||||
# (https://github.com/astral-sh/ty/issues/166)
|
||||
#
|
||||
# TODO: these should pass
|
||||
static_assert(is_single_valued(EmptyTupleSubclass)) # error: [static-assert-error]
|
||||
static_assert(is_single_valued(HeterogeneousTupleSubclass)) # error: [static-assert-error]
|
||||
static_assert(is_single_valued(EmptyTupleSubclass))
|
||||
static_assert(is_single_valued(HeterogeneousTupleSubclass))
|
||||
|
||||
static_assert(not is_single_valued(str))
|
||||
static_assert(not is_single_valued(Never))
|
||||
|
|
|
@ -424,142 +424,97 @@ class HeterogeneousTupleSubclass(tuple[I0, I1, I2]): ...
|
|||
|
||||
def f(x: HeterogeneousTupleSubclass):
|
||||
a, b, c = x
|
||||
reveal_type(a) # revealed: I0
|
||||
reveal_type(b) # revealed: I1
|
||||
reveal_type(c) # revealed: I2
|
||||
|
||||
# TODO: should be `I0`
|
||||
reveal_type(a) # revealed: I0 | I1 | I2
|
||||
# TODO: should be `I1`
|
||||
reveal_type(b) # revealed: I0 | I1 | I2
|
||||
# TODO: should be `I2`
|
||||
reveal_type(c) # revealed: I0 | I1 | I2
|
||||
d, e = x # error: [invalid-assignment] "Too many values to unpack: Expected 2"
|
||||
|
||||
# TODO: should emit a diagnostic ([invalid-assignment] "Too many values to unpack: Expected 2")
|
||||
d, e = x
|
||||
reveal_type(d) # revealed: Unknown
|
||||
reveal_type(e) # revealed: Unknown
|
||||
|
||||
reveal_type(d) # revealed: I0 | I1 | I2
|
||||
reveal_type(e) # revealed: I0 | I1 | I2
|
||||
f, g, h, i = x # error: [invalid-assignment] "Not enough values to unpack: Expected 4"
|
||||
|
||||
# TODO: should emit a diagnostic ([invalid-assignment] "Not enough values to unpack: Expected 4")
|
||||
f, g, h, i = x
|
||||
|
||||
reveal_type(f) # revealed: I0 | I1 | I2
|
||||
reveal_type(g) # revealed: I0 | I1 | I2
|
||||
reveal_type(h) # revealed: I0 | I1 | I2
|
||||
reveal_type(i) # revealed: I0 | I1 | I2
|
||||
reveal_type(f) # revealed: Unknown
|
||||
reveal_type(g) # revealed: Unknown
|
||||
reveal_type(h) # revealed: Unknown
|
||||
reveal_type(i) # revealed: Unknown
|
||||
|
||||
[j, *k] = x
|
||||
|
||||
# TODO: should be `I0`
|
||||
reveal_type(j) # revealed: I0 | I1 | I2
|
||||
# TODO: should be `list[I1 | I2]`
|
||||
reveal_type(k) # revealed: list[I0 | I1 | I2]
|
||||
reveal_type(j) # revealed: I0
|
||||
reveal_type(k) # revealed: list[I1 | I2]
|
||||
|
||||
[l, m, *n] = x
|
||||
|
||||
# TODO: should be `I0`
|
||||
reveal_type(l) # revealed: I0 | I1 | I2
|
||||
# TODO: should be `I1`
|
||||
reveal_type(m) # revealed: I0 | I1 | I2
|
||||
# TODO: should be `list[I2]`
|
||||
reveal_type(n) # revealed: list[I0 | I1 | I2]
|
||||
reveal_type(l) # revealed: I0
|
||||
reveal_type(m) # revealed: I1
|
||||
reveal_type(n) # revealed: list[I2]
|
||||
|
||||
[o, p, q, *r] = x
|
||||
reveal_type(o) # revealed: I0
|
||||
reveal_type(p) # revealed: I1
|
||||
reveal_type(q) # revealed: I2
|
||||
reveal_type(r) # revealed: list[Never]
|
||||
|
||||
# TODO: should be `I0`
|
||||
reveal_type(o) # revealed: I0 | I1 | I2
|
||||
# TODO: should be `I1`
|
||||
reveal_type(p) # revealed: I0 | I1 | I2
|
||||
# TODO: should be `I2`
|
||||
reveal_type(q) # revealed: I0 | I1 | I2
|
||||
# TODO: should be `list[Never]`
|
||||
reveal_type(r) # revealed: list[I0 | I1 | I2]
|
||||
|
||||
# TODO: should emit a diagnostic ([invalid-assignment] "Not enough values to unpack: Expected at least 4")
|
||||
# error: [invalid-assignment] "Not enough values to unpack: Expected at least 4"
|
||||
[s, t, u, v, *w] = x
|
||||
reveal_type(s) # revealed: I0 | I1 | I2
|
||||
reveal_type(t) # revealed: I0 | I1 | I2
|
||||
reveal_type(u) # revealed: I0 | I1 | I2
|
||||
reveal_type(v) # revealed: I0 | I1 | I2
|
||||
reveal_type(w) # revealed: list[I0 | I1 | I2]
|
||||
reveal_type(s) # revealed: Unknown
|
||||
reveal_type(t) # revealed: Unknown
|
||||
reveal_type(u) # revealed: Unknown
|
||||
reveal_type(v) # revealed: Unknown
|
||||
reveal_type(w) # revealed: list[Unknown]
|
||||
|
||||
class MixedTupleSubclass(tuple[I0, *tuple[I1, ...], I2]): ...
|
||||
|
||||
def f(x: MixedTupleSubclass):
|
||||
# TODO: should emit a diagnostic: ([invalid-assignment] "Too many values to unpack: Expected 1"`)
|
||||
(a,) = x
|
||||
reveal_type(a) # revealed: I0 | I1 | I2
|
||||
(a,) = x # error: [invalid-assignment] "Too many values to unpack: Expected 1"
|
||||
reveal_type(a) # revealed: Unknown
|
||||
|
||||
c, d = x
|
||||
# TODO: should be `I0`
|
||||
reveal_type(c) # revealed: I0 | I1 | I2
|
||||
# TODO: should be `I2`
|
||||
reveal_type(d) # revealed: I0 | I1 | I2
|
||||
reveal_type(c) # revealed: I0
|
||||
reveal_type(d) # revealed: I2
|
||||
|
||||
e, f, g = x
|
||||
|
||||
# TODO: should be `I0`
|
||||
reveal_type(e) # revealed: I0 | I1 | I2
|
||||
# TODO: should be `I1`
|
||||
reveal_type(f) # revealed: I0 | I1 | I2
|
||||
# TODO: should be `I2`
|
||||
reveal_type(g) # revealed: I0 | I1 | I2
|
||||
reveal_type(e) # revealed: I0
|
||||
reveal_type(f) # revealed: I1
|
||||
reveal_type(g) # revealed: I2
|
||||
|
||||
h, i, j, k = x
|
||||
|
||||
# TODO: should be `I0`
|
||||
reveal_type(h) # revealed: I0 | I1 | I2
|
||||
# TODO: should be `I1`
|
||||
reveal_type(i) # revealed: I0 | I1 | I2
|
||||
# TODO: should be `I1`
|
||||
reveal_type(j) # revealed: I0 | I1 | I2
|
||||
# TODO: should be `I2`
|
||||
reveal_type(k) # revealed: I0 | I1 | I2
|
||||
reveal_type(h) # revealed: I0
|
||||
reveal_type(i) # revealed: I1
|
||||
reveal_type(j) # revealed: I1
|
||||
reveal_type(k) # revealed: I2
|
||||
|
||||
[l, *m] = x
|
||||
|
||||
# TODO: should be `I0`
|
||||
reveal_type(l) # revealed: I0 | I1 | I2
|
||||
# TODO: should be `list[I1 | I2]`
|
||||
reveal_type(m) # revealed: list[I0 | I1 | I2]
|
||||
reveal_type(l) # revealed: I0
|
||||
reveal_type(m) # revealed: list[I1 | I2]
|
||||
|
||||
[n, o, *p] = x
|
||||
reveal_type(n) # revealed: I0
|
||||
|
||||
# TODO: should be `I0`
|
||||
reveal_type(n) # revealed: I0 | I1 | I2
|
||||
# TODO: should be `I1 | I2`
|
||||
reveal_type(o) # revealed: I0 | I1 | I2
|
||||
# TODO: should be `list[I1 | I2]`
|
||||
reveal_type(p) # revealed: list[I0 | I1 | I2]
|
||||
# TODO: `I1 | I2` might be better here? (https://github.com/astral-sh/ty/issues/947)
|
||||
reveal_type(o) # revealed: I1
|
||||
|
||||
reveal_type(p) # revealed: list[I1 | I2]
|
||||
|
||||
[o, p, q, *r] = x
|
||||
reveal_type(o) # revealed: I0
|
||||
|
||||
# TODO: should be `I0`
|
||||
reveal_type(o) # revealed: I0 | I1 | I2
|
||||
# TODO: should be `I1 | I2`
|
||||
reveal_type(p) # revealed: I0 | I1 | I2
|
||||
# TODO: should be `I1 | I2
|
||||
reveal_type(q) # revealed: I0 | I1 | I2
|
||||
# TODO: should be `list[I1 | I2]
|
||||
reveal_type(r) # revealed: list[I0 | I1 | I2]
|
||||
# TODO: `I1 | I2` might be better for both of these? (https://github.com/astral-sh/ty/issues/947)
|
||||
reveal_type(p) # revealed: I1
|
||||
reveal_type(q) # revealed: I1
|
||||
|
||||
reveal_type(r) # revealed: list[I1 | I2]
|
||||
|
||||
s, *t, u = x
|
||||
|
||||
# TODO: should be `I0`
|
||||
reveal_type(s) # revealed: I0 | I1 | I2
|
||||
# TODO: should be `list[I1]`
|
||||
reveal_type(t) # revealed: list[I0 | I1 | I2]
|
||||
# TODO: should be `I2`
|
||||
reveal_type(u) # revealed: I0 | I1 | I2
|
||||
reveal_type(s) # revealed: I0
|
||||
reveal_type(t) # revealed: list[I1]
|
||||
reveal_type(u) # revealed: I2
|
||||
|
||||
aa, bb, *cc, dd = x
|
||||
|
||||
# TODO: should be `I0`
|
||||
reveal_type(aa) # revealed: I0 | I1 | I2
|
||||
# TODO: should be `I1`
|
||||
reveal_type(bb) # revealed: I0 | I1 | I2
|
||||
# TODO: should be `list[I1]`
|
||||
reveal_type(cc) # revealed: list[I0 | I1 | I2]
|
||||
# TODO: should be I2
|
||||
reveal_type(dd) # revealed: I0 | I1 | I2
|
||||
reveal_type(aa) # revealed: I0
|
||||
reveal_type(bb) # revealed: I1
|
||||
reveal_type(cc) # revealed: list[I1]
|
||||
reveal_type(dd) # revealed: I2
|
||||
```
|
||||
|
||||
## String
|
||||
|
|
|
@ -222,7 +222,6 @@ impl<'db> Completion<'db> {
|
|||
// "struct" here as a more general "object." ---AG
|
||||
Type::NominalInstance(_)
|
||||
| Type::PropertyInstance(_)
|
||||
| Type::Tuple(_)
|
||||
| Type::BoundSuper(_)
|
||||
| Type::TypedDict(_) => CompletionKind::Struct,
|
||||
Type::IntLiteral(_)
|
||||
|
|
|
@ -59,7 +59,7 @@ use crate::types::infer::infer_unpack_types;
|
|||
use crate::types::mro::{Mro, MroError, MroIterator};
|
||||
pub(crate) use crate::types::narrow::infer_narrowing_constraint;
|
||||
use crate::types::signatures::{Parameter, ParameterForm, Parameters, walk_signature};
|
||||
use crate::types::tuple::{TupleSpec, TupleType};
|
||||
use crate::types::tuple::TupleSpec;
|
||||
use crate::unpack::EvaluationMode;
|
||||
pub use crate::util::diagnostics::add_inferred_python_version_hint_to_diagnostic;
|
||||
use crate::{Db, FxOrderMap, FxOrderSet, Module, Program};
|
||||
|
@ -594,11 +594,6 @@ pub enum Type<'db> {
|
|||
LiteralString,
|
||||
/// A bytes literal
|
||||
BytesLiteral(BytesLiteralType<'db>),
|
||||
/// An instance of the builtin `tuple` class.
|
||||
/// TODO: Consider removing this in favor of `NominalInstance`. This is currently stored as a
|
||||
/// separate variant partly for historical reasons, and partly to allow us to easily
|
||||
/// distinguish tuples since they occur so often.
|
||||
Tuple(TupleType<'db>),
|
||||
/// An instance of a typevar in a generic class or function. When the generic class or function
|
||||
/// is specialized, we will replace this typevar with its specialization.
|
||||
TypeVar(BoundTypeVarInstance<'db>),
|
||||
|
@ -641,22 +636,25 @@ impl<'db> Type<'db> {
|
|||
|
||||
fn is_none(&self, db: &'db dyn Db) -> bool {
|
||||
self.into_nominal_instance()
|
||||
.is_some_and(|instance| instance.class.is_known(db, KnownClass::NoneType))
|
||||
.is_some_and(|instance| instance.class(db).is_known(db, KnownClass::NoneType))
|
||||
}
|
||||
|
||||
fn is_bool(&self, db: &'db dyn Db) -> bool {
|
||||
self.into_nominal_instance()
|
||||
.is_some_and(|instance| instance.class.is_known(db, KnownClass::Bool))
|
||||
.is_some_and(|instance| instance.class(db).is_known(db, KnownClass::Bool))
|
||||
}
|
||||
|
||||
pub fn is_notimplemented(&self, db: &'db dyn Db) -> bool {
|
||||
self.into_nominal_instance()
|
||||
.is_some_and(|instance| instance.class.is_known(db, KnownClass::NotImplementedType))
|
||||
self.into_nominal_instance().is_some_and(|instance| {
|
||||
instance
|
||||
.class(db)
|
||||
.is_known(db, KnownClass::NotImplementedType)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_object(&self, db: &'db dyn Db) -> bool {
|
||||
self.into_nominal_instance()
|
||||
.is_some_and(|instance| instance.class.is_object(db))
|
||||
.is_some_and(|instance| instance.is_object(db))
|
||||
}
|
||||
|
||||
pub const fn is_todo(&self) -> bool {
|
||||
|
@ -685,6 +683,30 @@ impl<'db> Type<'db> {
|
|||
self.materialize(db, TypeVarVariance::Contravariant)
|
||||
}
|
||||
|
||||
/// If this type is an instance type where the class has a tuple spec, returns the tuple spec.
|
||||
///
|
||||
/// I.e., for the type `tuple[int, str]`, this will return the tuple spec `[int, str]`.
|
||||
/// For a subclass of `tuple[int, str]`, it will return the same tuple spec.
|
||||
fn tuple_instance_spec(&self, db: &'db dyn Db) -> Option<Cow<'db, TupleSpec<'db>>> {
|
||||
self.into_nominal_instance()
|
||||
.and_then(|instance| instance.tuple_spec(db))
|
||||
}
|
||||
|
||||
/// If this type is an *exact* tuple type (*not* a subclass of `tuple`), returns the
|
||||
/// tuple spec.
|
||||
///
|
||||
/// You usually don't want to use this method, as you usually want to consider a subclass
|
||||
/// of a tuple type in the same way as the `tuple` type itself. Only use this method if you
|
||||
/// are certain that a *literal tuple* is required, and that a subclass of tuple will not
|
||||
/// do.
|
||||
///
|
||||
/// I.e., for the type `tuple[int, str]`, this will return the tuple spec `[int, str]`.
|
||||
/// But for a subclass of `tuple[int, str]`, it will return `None`.
|
||||
fn exact_tuple_instance_spec(&self, db: &'db dyn Db) -> Option<Cow<'db, TupleSpec<'db>>> {
|
||||
self.into_nominal_instance()
|
||||
.and_then(|instance| instance.own_tuple_spec(db))
|
||||
}
|
||||
|
||||
/// Returns the materialization of this type depending on the given `variance`.
|
||||
///
|
||||
/// More concretely, `T'`, the materialization of `T`, is the type `T` with all occurrences of
|
||||
|
@ -747,9 +769,7 @@ impl<'db> Type<'db> {
|
|||
*self
|
||||
}
|
||||
|
||||
Type::NominalInstance(nominal_instance_type) => {
|
||||
Type::NominalInstance(nominal_instance_type.materialize(db, variance))
|
||||
}
|
||||
Type::NominalInstance(instance) => instance.materialize(db, variance),
|
||||
Type::GenericAlias(generic_alias) => {
|
||||
Type::GenericAlias(generic_alias.materialize(db, variance))
|
||||
}
|
||||
|
@ -782,7 +802,6 @@ impl<'db> Type<'db> {
|
|||
.map(|ty| ty.materialize(db, variance.flip())),
|
||||
)
|
||||
.build(),
|
||||
Type::Tuple(tuple_type) => Type::tuple(tuple_type.materialize(db, variance)),
|
||||
Type::TypeVar(bound_typevar) => Type::TypeVar(bound_typevar.materialize(db, variance)),
|
||||
Type::TypeIs(type_is) => {
|
||||
type_is.with_type(db, type_is.return_type(db).materialize(db, variance))
|
||||
|
@ -1053,18 +1072,15 @@ impl<'db> Type<'db> {
|
|||
Type::Intersection(intersection) => visitor.visit(self, |v| {
|
||||
Type::Intersection(intersection.normalized_impl(db, v))
|
||||
}),
|
||||
Type::Tuple(tuple) => {
|
||||
visitor.visit(self, |v| Type::tuple(tuple.normalized_impl(db, v)))
|
||||
}
|
||||
Type::Callable(callable) => {
|
||||
visitor.visit(self, |v| Type::Callable(callable.normalized_impl(db, v)))
|
||||
}
|
||||
Type::ProtocolInstance(protocol) => {
|
||||
visitor.visit(self, |v| protocol.normalized_impl(db, v))
|
||||
}
|
||||
Type::NominalInstance(instance) => visitor.visit(self, |v| {
|
||||
Type::NominalInstance(instance.normalized_impl(db, v))
|
||||
}),
|
||||
Type::NominalInstance(instance) => {
|
||||
visitor.visit(self, |v| instance.normalized_impl(db, v))
|
||||
}
|
||||
Type::FunctionLiteral(function) => visitor.visit(self, |v| {
|
||||
Type::FunctionLiteral(function.normalized_impl(db, v))
|
||||
}),
|
||||
|
@ -1166,7 +1182,6 @@ impl<'db> Type<'db> {
|
|||
| Type::Union(_)
|
||||
| Type::Intersection(_)
|
||||
| Type::Callable(_)
|
||||
| Type::Tuple(_)
|
||||
| Type::TypeVar(_)
|
||||
| Type::BoundSuper(_)
|
||||
| Type::TypeIs(_)
|
||||
|
@ -1232,7 +1247,6 @@ impl<'db> Type<'db> {
|
|||
| Type::StringLiteral(_)
|
||||
| Type::LiteralString
|
||||
| Type::BytesLiteral(_)
|
||||
| Type::Tuple(_)
|
||||
| Type::TypeIs(_)
|
||||
| Type::TypedDict(_) => None,
|
||||
|
||||
|
@ -1309,7 +1323,7 @@ impl<'db> Type<'db> {
|
|||
|
||||
match (self, target) {
|
||||
// Everything is a subtype of `object`.
|
||||
(_, Type::NominalInstance(instance)) if instance.class.is_object(db) => true,
|
||||
(_, Type::NominalInstance(instance)) if instance.is_object(db) => true,
|
||||
|
||||
// `Never` is the bottom type, the empty set.
|
||||
// It is a subtype of all other types.
|
||||
|
@ -1577,24 +1591,6 @@ impl<'db> Type<'db> {
|
|||
|
||||
(Type::Callable(_), _) => false,
|
||||
|
||||
(Type::Tuple(self_tuple), Type::Tuple(target_tuple)) => {
|
||||
self_tuple.has_relation_to(db, target_tuple, relation)
|
||||
}
|
||||
|
||||
(Type::Tuple(self_tuple), Type::NominalInstance(target_instance)) => {
|
||||
self_tuple.to_class_type(db).is_some_and(|self_class| {
|
||||
self_class.has_relation_to(db, target_instance.class, relation)
|
||||
})
|
||||
}
|
||||
(Type::NominalInstance(self_instance), Type::Tuple(target_tuple)) => {
|
||||
target_tuple.to_class_type(db).is_some_and(|target_class| {
|
||||
self_instance
|
||||
.class
|
||||
.has_relation_to(db, target_class, relation)
|
||||
})
|
||||
}
|
||||
(Type::Tuple(_), _) => false,
|
||||
|
||||
(Type::BoundSuper(_), Type::BoundSuper(_)) => self.is_equivalent_to(db, target),
|
||||
(Type::BoundSuper(_), _) => KnownClass::Super
|
||||
.to_instance(db)
|
||||
|
@ -1724,8 +1720,6 @@ impl<'db> Type<'db> {
|
|||
first.is_equivalent_to(db, second)
|
||||
}
|
||||
|
||||
(Type::Tuple(first), Type::Tuple(second)) => first.is_equivalent_to(db, second),
|
||||
|
||||
(Type::Union(first), Type::Union(second)) => first.is_equivalent_to(db, second),
|
||||
|
||||
(Type::Intersection(first), Type::Intersection(second)) => {
|
||||
|
@ -1748,7 +1742,7 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
(Type::ProtocolInstance(protocol), nominal @ Type::NominalInstance(n))
|
||||
| (nominal @ Type::NominalInstance(n), Type::ProtocolInstance(protocol)) => {
|
||||
n.class.is_object(db) && protocol.normalized(db) == nominal
|
||||
n.is_object(db) && protocol.normalized(db) == nominal
|
||||
}
|
||||
// An instance of an enum class is equivalent to an enum literal of that class,
|
||||
// if that enum has only has one member.
|
||||
|
@ -1758,7 +1752,7 @@ impl<'db> Type<'db> {
|
|||
return false;
|
||||
}
|
||||
|
||||
let class_literal = instance.class.class_literal(db).0;
|
||||
let class_literal = instance.class(db).class_literal(db).0;
|
||||
is_single_member_enum(db, class_literal)
|
||||
}
|
||||
_ => false,
|
||||
|
@ -1906,45 +1900,6 @@ impl<'db> Type<'db> {
|
|||
| Type::KnownInstance(..)),
|
||||
) => left != right,
|
||||
|
||||
// One tuple type can be a subtype of another tuple type,
|
||||
// but we know for sure that any given tuple type is disjoint from all single-valued types
|
||||
(
|
||||
Type::Tuple(..),
|
||||
Type::ClassLiteral(..)
|
||||
| Type::GenericAlias(..)
|
||||
| Type::ModuleLiteral(..)
|
||||
| Type::BooleanLiteral(..)
|
||||
| Type::BytesLiteral(..)
|
||||
| Type::FunctionLiteral(..)
|
||||
| Type::BoundMethod(..)
|
||||
| Type::MethodWrapper(..)
|
||||
| Type::WrapperDescriptor(..)
|
||||
| Type::DataclassDecorator(..)
|
||||
| Type::DataclassTransformer(..)
|
||||
| Type::IntLiteral(..)
|
||||
| Type::EnumLiteral(..)
|
||||
| Type::StringLiteral(..)
|
||||
| Type::LiteralString,
|
||||
)
|
||||
| (
|
||||
Type::ClassLiteral(..)
|
||||
| Type::GenericAlias(..)
|
||||
| Type::ModuleLiteral(..)
|
||||
| Type::BooleanLiteral(..)
|
||||
| Type::BytesLiteral(..)
|
||||
| Type::FunctionLiteral(..)
|
||||
| Type::BoundMethod(..)
|
||||
| Type::MethodWrapper(..)
|
||||
| Type::WrapperDescriptor(..)
|
||||
| Type::DataclassDecorator(..)
|
||||
| Type::DataclassTransformer(..)
|
||||
| Type::IntLiteral(..)
|
||||
| Type::EnumLiteral(..)
|
||||
| Type::StringLiteral(..)
|
||||
| Type::LiteralString,
|
||||
Type::Tuple(..),
|
||||
) => true,
|
||||
|
||||
(
|
||||
Type::SubclassOf(_),
|
||||
Type::BooleanLiteral(..)
|
||||
|
@ -2058,7 +2013,7 @@ impl<'db> Type<'db> {
|
|||
// (<https://github.com/rust-lang/rust/issues/129967>)
|
||||
(Type::ProtocolInstance(protocol), nominal @ Type::NominalInstance(n))
|
||||
| (nominal @ Type::NominalInstance(n), Type::ProtocolInstance(protocol))
|
||||
if n.class.is_final(db) =>
|
||||
if n.class(db).is_final(db) =>
|
||||
{
|
||||
any_protocol_members_absent_or_disjoint(db, protocol, nominal, visitor)
|
||||
}
|
||||
|
@ -2107,29 +2062,19 @@ impl<'db> Type<'db> {
|
|||
|
||||
(Type::SpecialForm(special_form), Type::NominalInstance(instance))
|
||||
| (Type::NominalInstance(instance), Type::SpecialForm(special_form)) => {
|
||||
!special_form.is_instance_of(db, instance.class)
|
||||
!special_form.is_instance_of(db, instance.class(db))
|
||||
}
|
||||
|
||||
(Type::KnownInstance(known_instance), Type::NominalInstance(instance))
|
||||
| (Type::NominalInstance(instance), Type::KnownInstance(known_instance)) => {
|
||||
!known_instance.is_instance_of(db, instance.class)
|
||||
!known_instance.is_instance_of(db, instance.class(db))
|
||||
}
|
||||
|
||||
(Type::SpecialForm(special_form), Type::Tuple(tuple))
|
||||
| (Type::Tuple(tuple), Type::SpecialForm(special_form)) => tuple
|
||||
.to_class_type(db)
|
||||
.is_some_and(|tuple_class| !special_form.is_instance_of(db, tuple_class)),
|
||||
|
||||
(Type::KnownInstance(known_instance), Type::Tuple(tuple))
|
||||
| (Type::Tuple(tuple), Type::KnownInstance(known_instance)) => tuple
|
||||
.to_class_type(db)
|
||||
.is_some_and(|tuple_class| !known_instance.is_instance_of(db, tuple_class)),
|
||||
|
||||
(Type::BooleanLiteral(..) | Type::TypeIs(_), Type::NominalInstance(instance))
|
||||
| (Type::NominalInstance(instance), Type::BooleanLiteral(..) | Type::TypeIs(_)) => {
|
||||
// A `Type::BooleanLiteral()` must be an instance of exactly `bool`
|
||||
// (it cannot be an instance of a `bool` subclass)
|
||||
!KnownClass::Bool.is_subclass_of(db, instance.class)
|
||||
!KnownClass::Bool.is_subclass_of(db, instance.class(db))
|
||||
}
|
||||
|
||||
(Type::BooleanLiteral(..) | Type::TypeIs(_), _)
|
||||
|
@ -2139,7 +2084,7 @@ impl<'db> Type<'db> {
|
|||
| (Type::NominalInstance(instance), Type::IntLiteral(..)) => {
|
||||
// A `Type::IntLiteral()` must be an instance of exactly `int`
|
||||
// (it cannot be an instance of an `int` subclass)
|
||||
!KnownClass::Int.is_subclass_of(db, instance.class)
|
||||
!KnownClass::Int.is_subclass_of(db, instance.class(db))
|
||||
}
|
||||
|
||||
(Type::IntLiteral(..), _) | (_, Type::IntLiteral(..)) => true,
|
||||
|
@ -2151,7 +2096,7 @@ impl<'db> Type<'db> {
|
|||
| (Type::NominalInstance(instance), Type::StringLiteral(..) | Type::LiteralString) => {
|
||||
// A `Type::StringLiteral()` or a `Type::LiteralString` must be an instance of exactly `str`
|
||||
// (it cannot be an instance of a `str` subclass)
|
||||
!KnownClass::Str.is_subclass_of(db, instance.class)
|
||||
!KnownClass::Str.is_subclass_of(db, instance.class(db))
|
||||
}
|
||||
|
||||
(Type::LiteralString, Type::LiteralString) => false,
|
||||
|
@ -2161,7 +2106,7 @@ impl<'db> Type<'db> {
|
|||
| (Type::NominalInstance(instance), Type::BytesLiteral(..)) => {
|
||||
// A `Type::BytesLiteral()` must be an instance of exactly `bytes`
|
||||
// (it cannot be an instance of a `bytes` subclass)
|
||||
!KnownClass::Bytes.is_subclass_of(db, instance.class)
|
||||
!KnownClass::Bytes.is_subclass_of(db, instance.class(db))
|
||||
}
|
||||
|
||||
(Type::EnumLiteral(enum_literal), instance@Type::NominalInstance(_))
|
||||
|
@ -2188,7 +2133,7 @@ impl<'db> Type<'db> {
|
|||
| (Type::NominalInstance(instance), Type::FunctionLiteral(..)) => {
|
||||
// A `Type::FunctionLiteral()` must be an instance of exactly `types.FunctionType`
|
||||
// (it cannot be an instance of a `types.FunctionType` subclass)
|
||||
!KnownClass::FunctionType.is_subclass_of(db, instance.class)
|
||||
!KnownClass::FunctionType.is_subclass_of(db, instance.class(db))
|
||||
}
|
||||
|
||||
(Type::BoundMethod(_), other) | (other, Type::BoundMethod(_)) => KnownClass::MethodType
|
||||
|
@ -2234,12 +2179,12 @@ impl<'db> Type<'db> {
|
|||
|
||||
(
|
||||
Type::Callable(_) | Type::DataclassDecorator(_) | Type::DataclassTransformer(_),
|
||||
instance @ Type::NominalInstance(NominalInstanceType { class, .. }),
|
||||
instance @ Type::NominalInstance(nominal),
|
||||
)
|
||||
| (
|
||||
instance @ Type::NominalInstance(NominalInstanceType { class, .. }),
|
||||
instance @ Type::NominalInstance(nominal),
|
||||
Type::Callable(_) | Type::DataclassDecorator(_) | Type::DataclassTransformer(_),
|
||||
) if class.is_final(db) => instance
|
||||
) if nominal.class(db).is_final(db) => instance
|
||||
.member_lookup_with_policy(
|
||||
db,
|
||||
Name::new_static("__call__"),
|
||||
|
@ -2270,18 +2215,7 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
|
||||
(Type::NominalInstance(left), Type::NominalInstance(right)) => {
|
||||
left.is_disjoint_from_impl(db, right)
|
||||
}
|
||||
|
||||
(Type::Tuple(tuple), Type::Tuple(other_tuple)) => {
|
||||
tuple.is_disjoint_from_impl(db, other_tuple, visitor)
|
||||
}
|
||||
|
||||
(Type::Tuple(tuple), Type::NominalInstance(instance))
|
||||
| (Type::NominalInstance(instance), Type::Tuple(tuple)) => {
|
||||
tuple.to_class_type(db).is_some_and(|tuple_class| {
|
||||
!instance.class.could_coexist_in_mro_with(db, tuple_class)
|
||||
})
|
||||
left.is_disjoint_from_impl(db, right, visitor)
|
||||
}
|
||||
|
||||
(Type::PropertyInstance(_), other) | (other, Type::PropertyInstance(_)) => {
|
||||
|
@ -2396,14 +2330,6 @@ impl<'db> Type<'db> {
|
|||
Type::DataclassDecorator(_) | Type::DataclassTransformer(_) => false,
|
||||
Type::NominalInstance(instance) => instance.is_singleton(db),
|
||||
Type::PropertyInstance(_) => false,
|
||||
Type::Tuple(..) => {
|
||||
// The empty tuple is a singleton on CPython and PyPy, but not on other Python
|
||||
// implementations such as GraalPy. Its *use* as a singleton is discouraged and
|
||||
// should not be relied on for type narrowing, so we do not treat it as one.
|
||||
// See:
|
||||
// https://docs.python.org/3/reference/expressions.html#parenthesized-forms
|
||||
false
|
||||
}
|
||||
Type::Union(..) => {
|
||||
// A single-element union, where the sole element was a singleton, would itself
|
||||
// be a singleton type. However, unions with length < 2 should never appear in
|
||||
|
@ -2493,7 +2419,6 @@ impl<'db> Type<'db> {
|
|||
false
|
||||
}
|
||||
|
||||
Type::Tuple(tuple) => tuple.is_single_valued(db),
|
||||
Type::NominalInstance(instance) => instance.is_single_valued(db),
|
||||
|
||||
Type::BoundSuper(_) => {
|
||||
|
@ -2609,7 +2534,9 @@ impl<'db> Type<'db> {
|
|||
// i.e. Type::NominalInstance(type). So looking up a name in the MRO of
|
||||
// `Type::NominalInstance(type)` is equivalent to looking up the name in the
|
||||
// MRO of the class `object`.
|
||||
Type::NominalInstance(instance) if instance.class.is_known(db, KnownClass::Type) => {
|
||||
Type::NominalInstance(instance)
|
||||
if instance.class(db).is_known(db, KnownClass::Type) =>
|
||||
{
|
||||
if policy.mro_no_object_fallback() {
|
||||
Some(Place::Unbound.into())
|
||||
} else {
|
||||
|
@ -2637,7 +2564,6 @@ impl<'db> Type<'db> {
|
|||
| Type::LiteralString
|
||||
| Type::BytesLiteral(_)
|
||||
| Type::EnumLiteral(_)
|
||||
| Type::Tuple(_)
|
||||
| Type::TypeVar(_)
|
||||
| Type::NominalInstance(_)
|
||||
| Type::ProtocolInstance(_)
|
||||
|
@ -2724,7 +2650,7 @@ impl<'db> Type<'db> {
|
|||
|
||||
Type::Dynamic(_) | Type::Never => Place::bound(self).into(),
|
||||
|
||||
Type::NominalInstance(instance) => instance.class.instance_member(db, name),
|
||||
Type::NominalInstance(instance) => instance.class(db).instance_member(db, name),
|
||||
|
||||
Type::ProtocolInstance(protocol) => protocol.instance_member(db, name),
|
||||
|
||||
|
@ -2745,12 +2671,12 @@ impl<'db> Type<'db> {
|
|||
.to_instance(db)
|
||||
.instance_member(db, name),
|
||||
Type::Callable(_) | Type::DataclassTransformer(_) => {
|
||||
KnownClass::Object.to_instance(db).instance_member(db, name)
|
||||
Type::object(db).instance_member(db, name)
|
||||
}
|
||||
|
||||
Type::TypeVar(bound_typevar) => {
|
||||
match bound_typevar.typevar(db).bound_or_constraints(db) {
|
||||
None => KnownClass::Object.to_instance(db).instance_member(db, name),
|
||||
None => Type::object(db).instance_member(db, name),
|
||||
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
|
||||
bound.instance_member(db, name)
|
||||
}
|
||||
|
@ -2772,10 +2698,6 @@ impl<'db> Type<'db> {
|
|||
Type::EnumLiteral(enum_literal) => enum_literal
|
||||
.enum_class_instance(db)
|
||||
.instance_member(db, name),
|
||||
Type::Tuple(tuple) => tuple
|
||||
.to_class_type(db)
|
||||
.map(|class| class.instance_member(db, name))
|
||||
.unwrap_or(Place::Unbound.into()),
|
||||
|
||||
Type::AlwaysTruthy | Type::AlwaysFalsy => Type::object(db).instance_member(db, name),
|
||||
Type::ModuleLiteral(_) => KnownClass::ModuleType
|
||||
|
@ -3235,13 +3157,13 @@ impl<'db> Type<'db> {
|
|||
.to_instance(db)
|
||||
.member_lookup_with_policy(db, name, policy),
|
||||
|
||||
Type::Callable(_) | Type::DataclassTransformer(_) => KnownClass::Object
|
||||
.to_instance(db)
|
||||
.member_lookup_with_policy(db, name, policy),
|
||||
Type::Callable(_) | Type::DataclassTransformer(_) => {
|
||||
Type::object(db).member_lookup_with_policy(db, name, policy)
|
||||
}
|
||||
|
||||
Type::NominalInstance(instance)
|
||||
if matches!(name.as_str(), "major" | "minor")
|
||||
&& instance.class.is_known(db, KnownClass::VersionInfo) =>
|
||||
&& instance.class(db).is_known(db, KnownClass::VersionInfo) =>
|
||||
{
|
||||
let python_version = Program::get(db).python_version(db);
|
||||
let segment = if name == "major" {
|
||||
|
@ -3285,7 +3207,6 @@ impl<'db> Type<'db> {
|
|||
| Type::BytesLiteral(..)
|
||||
| Type::EnumLiteral(..)
|
||||
| Type::LiteralString
|
||||
| Type::Tuple(..)
|
||||
| Type::TypeVar(..)
|
||||
| Type::SpecialForm(..)
|
||||
| Type::KnownInstance(..)
|
||||
|
@ -3315,7 +3236,7 @@ impl<'db> Type<'db> {
|
|||
// resolve the attribute.
|
||||
if matches!(
|
||||
self.into_nominal_instance()
|
||||
.and_then(|instance| instance.class.known(db)),
|
||||
.and_then(|instance| instance.class(db).known(db)),
|
||||
Some(KnownClass::ModuleType | KnownClass::GenericAlias)
|
||||
) {
|
||||
return Place::Unbound.into();
|
||||
|
@ -3633,10 +3554,12 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
Type::NominalInstance(instance) => match instance.class.known(db) {
|
||||
Some(known_class) => known_class.bool(),
|
||||
None => try_dunder_bool()?,
|
||||
},
|
||||
Type::NominalInstance(instance) => instance
|
||||
.class(db)
|
||||
.known(db)
|
||||
.and_then(KnownClass::bool)
|
||||
.map(Ok)
|
||||
.unwrap_or_else(try_dunder_bool)?,
|
||||
|
||||
Type::ProtocolInstance(_) => try_dunder_bool()?,
|
||||
|
||||
|
@ -3657,7 +3580,6 @@ impl<'db> Type<'db> {
|
|||
Type::BooleanLiteral(bool) => Truthiness::from(*bool),
|
||||
Type::StringLiteral(str) => Truthiness::from(!str.value(db).is_empty()),
|
||||
Type::BytesLiteral(bytes) => Truthiness::from(!bytes.value(db).is_empty()),
|
||||
Type::Tuple(tuple) => tuple.truthiness(db),
|
||||
};
|
||||
|
||||
Ok(truthiness)
|
||||
|
@ -3684,13 +3606,6 @@ impl<'db> Type<'db> {
|
|||
let usize_len = match self {
|
||||
Type::BytesLiteral(bytes) => Some(bytes.python_len(db)),
|
||||
Type::StringLiteral(string) => Some(string.python_len(db)),
|
||||
|
||||
// N.B. This is strictly-speaking redundant, since the `__len__` method on tuples
|
||||
// is special-cased in `ClassType::own_class_member`. However, it's probably more
|
||||
// efficient to short-circuit here and check against the tuple spec directly,
|
||||
// rather than going through the `__len__` method.
|
||||
Type::Tuple(tuple) => tuple.tuple(db).len().into_fixed_length(),
|
||||
|
||||
_ => None,
|
||||
};
|
||||
|
||||
|
@ -4236,10 +4151,7 @@ impl<'db> Type<'db> {
|
|||
// ```
|
||||
Binding::single(
|
||||
self,
|
||||
Signature::new(
|
||||
Parameters::empty(),
|
||||
Some(KnownClass::Object.to_instance(db)),
|
||||
),
|
||||
Signature::new(Parameters::empty(), Some(Type::object(db))),
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
@ -4633,7 +4545,6 @@ impl<'db> Type<'db> {
|
|||
| Type::BytesLiteral(_)
|
||||
| Type::BooleanLiteral(_)
|
||||
| Type::LiteralString
|
||||
| Type::Tuple(_)
|
||||
| Type::BoundSuper(_)
|
||||
| Type::ModuleLiteral(_)
|
||||
| Type::TypeIs(_)
|
||||
|
@ -4791,7 +4702,11 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
|
||||
match self {
|
||||
Type::Tuple(tuple_type) => return Ok(Cow::Borrowed(tuple_type.tuple(db))),
|
||||
Type::NominalInstance(nominal) => {
|
||||
if let Some(spec) = nominal.tuple_spec(db) {
|
||||
return Ok(spec);
|
||||
}
|
||||
}
|
||||
Type::GenericAlias(alias) if alias.origin(db).is_tuple(db) => {
|
||||
return Ok(Cow::Owned(TupleSpec::homogeneous(todo_type!(
|
||||
"*tuple[] annotations"
|
||||
|
@ -5017,7 +4932,7 @@ impl<'db> Type<'db> {
|
|||
|
||||
match self {
|
||||
Type::NominalInstance(instance) => {
|
||||
instance.class.iter_mro(db).find_map(from_class_base)
|
||||
instance.class(db).iter_mro(db).find_map(from_class_base)
|
||||
}
|
||||
Type::ProtocolInstance(instance) => {
|
||||
if let Protocol::FromClass(class) = instance.inner {
|
||||
|
@ -5276,7 +5191,6 @@ impl<'db> Type<'db> {
|
|||
| Type::ModuleLiteral(_)
|
||||
| Type::IntLiteral(_)
|
||||
| Type::StringLiteral(_)
|
||||
| Type::Tuple(_)
|
||||
| Type::LiteralString
|
||||
| Type::BoundSuper(_)
|
||||
| Type::AlwaysTruthy
|
||||
|
@ -5344,7 +5258,6 @@ impl<'db> Type<'db> {
|
|||
| Type::LiteralString
|
||||
| Type::ModuleLiteral(_)
|
||||
| Type::StringLiteral(_)
|
||||
| Type::Tuple(_)
|
||||
| Type::TypeVar(_)
|
||||
| Type::Callable(_)
|
||||
| Type::BoundMethod(_)
|
||||
|
@ -5557,7 +5470,7 @@ impl<'db> Type<'db> {
|
|||
|
||||
Type::Dynamic(_) => Ok(*self),
|
||||
|
||||
Type::NominalInstance(instance) => match instance.class.known(db) {
|
||||
Type::NominalInstance(instance) => match instance.class(db).known(db) {
|
||||
Some(KnownClass::TypeVar) => Ok(todo_type!(
|
||||
"Support for `typing.TypeVar` instances in type expressions"
|
||||
)),
|
||||
|
@ -5593,40 +5506,6 @@ impl<'db> Type<'db> {
|
|||
KnownClass::NoneType.to_instance(db)
|
||||
}
|
||||
|
||||
/// Return the type of `tuple(sys.version_info)`.
|
||||
///
|
||||
/// This is not exactly the type that `sys.version_info` has at runtime,
|
||||
/// but it's a useful fallback for us in order to infer `Literal` types from `sys.version_info` comparisons.
|
||||
fn version_info_tuple(db: &'db dyn Db) -> Self {
|
||||
let python_version = Program::get(db).python_version(db);
|
||||
let int_instance_ty = KnownClass::Int.to_instance(db);
|
||||
|
||||
// TODO: just grab this type from typeshed (it's a `sys._ReleaseLevel` type alias there)
|
||||
let release_level_ty = {
|
||||
let elements: Box<[Type<'db>]> = ["alpha", "beta", "candidate", "final"]
|
||||
.iter()
|
||||
.map(|level| Type::string_literal(db, level))
|
||||
.collect();
|
||||
|
||||
// For most unions, it's better to go via `UnionType::from_elements` or use `UnionBuilder`;
|
||||
// those techniques ensure that union elements are deduplicated and unions are eagerly simplified
|
||||
// into other types where necessary. Here, however, we know that there are no duplicates
|
||||
// in this union, so it's probably more efficient to use `UnionType::new()` directly.
|
||||
Type::Union(UnionType::new(db, elements))
|
||||
};
|
||||
|
||||
Type::heterogeneous_tuple(
|
||||
db,
|
||||
[
|
||||
Type::IntLiteral(python_version.major.into()),
|
||||
Type::IntLiteral(python_version.minor.into()),
|
||||
int_instance_ty,
|
||||
release_level_ty,
|
||||
int_instance_ty,
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
/// Given a type that is assumed to represent an instance of a class,
|
||||
/// return a type that represents that class itself.
|
||||
///
|
||||
|
@ -5655,9 +5534,6 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
Type::Callable(_) | Type::DataclassTransformer(_) => KnownClass::Type.to_instance(db),
|
||||
Type::ModuleLiteral(_) => KnownClass::ModuleType.to_class_literal(db),
|
||||
Type::Tuple(tuple) => tuple
|
||||
.to_subclass_of(db)
|
||||
.unwrap_or_else(SubclassOfType::subclass_of_unknown),
|
||||
Type::TypeVar(bound_typevar) => {
|
||||
match bound_typevar.typevar(db).bound_or_constraints(db) {
|
||||
None => KnownClass::Type.to_instance(db),
|
||||
|
@ -5777,9 +5653,8 @@ impl<'db> Type<'db> {
|
|||
method.self_instance(db).apply_type_mapping(db, type_mapping),
|
||||
)),
|
||||
|
||||
Type::NominalInstance(instance) => Type::NominalInstance(
|
||||
Type::NominalInstance(instance) =>
|
||||
instance.apply_type_mapping(db, type_mapping),
|
||||
),
|
||||
|
||||
Type::ProtocolInstance(instance) => {
|
||||
Type::ProtocolInstance(instance.apply_type_mapping(db, type_mapping))
|
||||
|
@ -5844,7 +5719,6 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
builder.build()
|
||||
}
|
||||
Type::Tuple(tuple) => Type::tuple(tuple.apply_type_mapping(db, type_mapping)),
|
||||
|
||||
Type::TypeIs(type_is) => type_is.with_type(db, type_is.return_type(db).apply_type_mapping(db, type_mapping)),
|
||||
|
||||
|
@ -5950,10 +5824,6 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
Type::Tuple(tuple) => {
|
||||
tuple.find_legacy_typevars(db, binding_context, typevars);
|
||||
}
|
||||
|
||||
Type::GenericAlias(alias) => {
|
||||
alias.find_legacy_typevars(db, binding_context, typevars);
|
||||
}
|
||||
|
@ -6071,7 +5941,7 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
Self::GenericAlias(alias) => Some(TypeDefinition::Class(alias.definition(db))),
|
||||
Self::NominalInstance(instance) => {
|
||||
Some(TypeDefinition::Class(instance.class.definition(db)))
|
||||
Some(TypeDefinition::Class(instance.class(db).definition(db)))
|
||||
}
|
||||
Self::KnownInstance(instance) => match instance {
|
||||
KnownInstanceType::TypeVar(var) => {
|
||||
|
@ -6100,8 +5970,7 @@ impl<'db> Type<'db> {
|
|||
| Self::DataclassDecorator(_)
|
||||
| Self::DataclassTransformer(_)
|
||||
| Self::PropertyInstance(_)
|
||||
| Self::BoundSuper(_)
|
||||
| Self::Tuple(_) => self.to_meta_type(db).definition(db),
|
||||
| Self::BoundSuper(_) => self.to_meta_type(db).definition(db),
|
||||
|
||||
Self::TypeVar(bound_typevar) => Some(TypeDefinition::TypeVar(bound_typevar.typevar(db).definition(db)?)),
|
||||
|
||||
|
@ -6193,7 +6062,7 @@ impl<'db> Type<'db> {
|
|||
match self {
|
||||
Type::GenericAlias(generic) => Some(generic.origin(db)),
|
||||
Type::NominalInstance(instance) => {
|
||||
if let ClassType::Generic(generic) = instance.class {
|
||||
if let ClassType::Generic(generic) = instance.class(db) {
|
||||
Some(generic.origin(db))
|
||||
} else {
|
||||
None
|
||||
|
@ -9280,9 +9149,11 @@ impl<'db> SuperOwnerKind<'db> {
|
|||
SuperOwnerKind::Class(class) => {
|
||||
SuperOwnerKind::Class(class.normalized_impl(db, visitor))
|
||||
}
|
||||
SuperOwnerKind::Instance(instance) => {
|
||||
SuperOwnerKind::Instance(instance.normalized_impl(db, visitor))
|
||||
}
|
||||
SuperOwnerKind::Instance(instance) => instance
|
||||
.normalized_impl(db, visitor)
|
||||
.into_nominal_instance()
|
||||
.map(Self::Instance)
|
||||
.unwrap_or(Self::Dynamic(DynamicType::Any)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9292,7 +9163,7 @@ impl<'db> SuperOwnerKind<'db> {
|
|||
Either::Left(ClassBase::Dynamic(dynamic).mro(db, None))
|
||||
}
|
||||
SuperOwnerKind::Class(class) => Either::Right(class.iter_mro(db)),
|
||||
SuperOwnerKind::Instance(instance) => Either::Right(instance.class.iter_mro(db)),
|
||||
SuperOwnerKind::Instance(instance) => Either::Right(instance.class(db).iter_mro(db)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9304,11 +9175,11 @@ impl<'db> SuperOwnerKind<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
fn into_class(self) -> Option<ClassType<'db>> {
|
||||
fn into_class(self, db: &'db dyn Db) -> Option<ClassType<'db>> {
|
||||
match self {
|
||||
SuperOwnerKind::Dynamic(_) => None,
|
||||
SuperOwnerKind::Class(class) => Some(class),
|
||||
SuperOwnerKind::Instance(instance) => Some(instance.class),
|
||||
SuperOwnerKind::Instance(instance) => Some(instance.class(db)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9406,7 +9277,7 @@ impl<'db> BoundSuperType<'db> {
|
|||
let Some(pivot_class) = pivot_class.into_class() else {
|
||||
return Some(owner);
|
||||
};
|
||||
let Some(owner_class) = owner.into_class() else {
|
||||
let Some(owner_class) = owner.into_class(db) else {
|
||||
return Some(owner);
|
||||
};
|
||||
if owner_class.is_subclass_of(db, pivot_class) {
|
||||
|
@ -9507,7 +9378,7 @@ impl<'db> BoundSuperType<'db> {
|
|||
.expect("Calling `find_name_in_mro` on dynamic type should return `Some`");
|
||||
}
|
||||
SuperOwnerKind::Class(class) => class,
|
||||
SuperOwnerKind::Instance(instance) => instance.class,
|
||||
SuperOwnerKind::Instance(instance) => instance.class(db),
|
||||
};
|
||||
|
||||
let (class_literal, _) = class.class_literal(db);
|
||||
|
|
|
@ -572,7 +572,8 @@ impl<'db> IntersectionBuilder<'db> {
|
|||
self
|
||||
}
|
||||
Type::NominalInstance(instance)
|
||||
if enum_metadata(self.db, instance.class.class_literal(self.db).0).is_some() =>
|
||||
if enum_metadata(self.db, instance.class(self.db).class_literal(self.db).0)
|
||||
.is_some() =>
|
||||
{
|
||||
let mut contains_enum_literal_as_negative_element = false;
|
||||
for intersection in &self.intersections {
|
||||
|
@ -596,7 +597,7 @@ impl<'db> IntersectionBuilder<'db> {
|
|||
let db = self.db;
|
||||
self.add_positive(Type::Union(UnionType::new(
|
||||
db,
|
||||
enum_member_literals(db, instance.class.class_literal(db).0, None)
|
||||
enum_member_literals(db, instance.class(db).class_literal(db).0, None)
|
||||
.expect("Calling `enum_member_literals` on an enum class")
|
||||
.collect::<Box<[_]>>(),
|
||||
)))
|
||||
|
@ -762,7 +763,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
|||
_ => {
|
||||
let known_instance = new_positive
|
||||
.into_nominal_instance()
|
||||
.and_then(|instance| instance.class.known(db));
|
||||
.and_then(|instance| instance.class(db).known(db));
|
||||
|
||||
if known_instance == Some(KnownClass::Object) {
|
||||
// `object & T` -> `T`; it is always redundant to add `object` to an intersection
|
||||
|
@ -782,7 +783,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
|||
new_positive = Type::BooleanLiteral(false);
|
||||
}
|
||||
Type::NominalInstance(instance)
|
||||
if instance.class.is_known(db, KnownClass::Bool) =>
|
||||
if instance.class(db).is_known(db, KnownClass::Bool) =>
|
||||
{
|
||||
match new_positive {
|
||||
// `bool & AlwaysTruthy` -> `Literal[True]`
|
||||
|
@ -876,7 +877,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
|||
self.positive
|
||||
.iter()
|
||||
.filter_map(|ty| ty.into_nominal_instance())
|
||||
.filter_map(|instance| instance.class.known(db))
|
||||
.filter_map(|instance| instance.class(db).known(db))
|
||||
.any(KnownClass::is_bool)
|
||||
};
|
||||
|
||||
|
@ -892,7 +893,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
|||
Type::Never => {
|
||||
// Adding ~Never to an intersection is a no-op.
|
||||
}
|
||||
Type::NominalInstance(instance) if instance.class.is_object(db) => {
|
||||
Type::NominalInstance(instance) if instance.is_object(db) => {
|
||||
// Adding ~object to an intersection results in Never.
|
||||
*self = Self::default();
|
||||
self.positive.insert(Type::Never);
|
||||
|
|
|
@ -6,7 +6,7 @@ use ruff_python_ast as ast;
|
|||
use crate::Db;
|
||||
use crate::types::KnownClass;
|
||||
use crate::types::enums::enum_member_literals;
|
||||
use crate::types::tuple::{TupleLength, TupleSpec};
|
||||
use crate::types::tuple::{Tuple, TupleLength, TupleType};
|
||||
|
||||
use super::Type;
|
||||
|
||||
|
@ -214,29 +214,20 @@ impl<'a, 'db> FromIterator<(Argument<'a>, Option<Type<'db>>)> for CallArguments<
|
|||
fn expand_type<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option<Vec<Type<'db>>> {
|
||||
match ty {
|
||||
Type::NominalInstance(instance) => {
|
||||
if instance.class.is_known(db, KnownClass::Bool) {
|
||||
let class = instance.class(db);
|
||||
|
||||
if class.is_known(db, KnownClass::Bool) {
|
||||
return Some(vec![
|
||||
Type::BooleanLiteral(true),
|
||||
Type::BooleanLiteral(false),
|
||||
]);
|
||||
}
|
||||
|
||||
let class_literal = instance.class.class_literal(db).0;
|
||||
|
||||
if let Some(enum_members) = enum_member_literals(db, class_literal, None) {
|
||||
return Some(enum_members.collect());
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
Type::Tuple(tuple_type) => {
|
||||
// Note: This should only account for tuples of known length, i.e., `tuple[bool, ...]`
|
||||
// should not be expanded here.
|
||||
let tuple = tuple_type.tuple(db);
|
||||
if !matches!(tuple, TupleSpec::Fixed(_)) {
|
||||
return None;
|
||||
}
|
||||
let expanded = tuple
|
||||
// If the class is a fixed-length tuple subtype, we expand it to its elements.
|
||||
if let Some(spec) = instance.tuple_spec(db) {
|
||||
return match &*spec {
|
||||
Tuple::Fixed(fixed_length_tuple) => {
|
||||
let expanded = fixed_length_tuple
|
||||
.all_elements()
|
||||
.map(|element| {
|
||||
if let Some(expanded) = expand_type(db, *element) {
|
||||
|
@ -246,8 +237,9 @@ fn expand_type<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option<Vec<Type<'db>>> {
|
|||
}
|
||||
})
|
||||
.multi_cartesian_product()
|
||||
.map(|types| Type::heterogeneous_tuple(db, types))
|
||||
.map(|types| Type::tuple(TupleType::from_elements(db, types)))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if expanded.len() == 1 {
|
||||
// There are no elements in the tuple type that can be expanded.
|
||||
None
|
||||
|
@ -255,6 +247,16 @@ fn expand_type<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option<Vec<Type<'db>>> {
|
|||
Some(expanded)
|
||||
}
|
||||
}
|
||||
Tuple::Variable(_) => None,
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(enum_members) = enum_member_literals(db, class.class_literal(db).0, None) {
|
||||
return Some(enum_members.collect());
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
Type::Union(union) => Some(union.iter(db).copied().collect()),
|
||||
// We don't handle `type[A | B]` here because it's already stored in the expanded form
|
||||
// i.e., `type[A] | type[B]` which is handled by the `Type::Union` case.
|
||||
|
|
|
@ -273,8 +273,6 @@ impl<'db> GenericAlias<'db> {
|
|||
binding_context: Option<Definition<'db>>,
|
||||
typevars: &mut FxOrderSet<BoundTypeVarInstance<'db>>,
|
||||
) {
|
||||
// A tuple's specialization will include all of its element types, so we don't need to also
|
||||
// look in `self.tuple`.
|
||||
self.specialization(db)
|
||||
.find_legacy_typevars(db, binding_context, typevars);
|
||||
}
|
||||
|
@ -316,6 +314,13 @@ impl<'db> ClassType<'db> {
|
|||
matches!(self, Self::NonGeneric(_))
|
||||
}
|
||||
|
||||
pub(super) const fn into_generic_alias(self) -> Option<GenericAlias<'db>> {
|
||||
match self {
|
||||
Self::NonGeneric(_) => None,
|
||||
Self::Generic(generic) => Some(generic),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn normalized_impl(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
|
@ -663,7 +668,8 @@ impl<'db> ClassType<'db> {
|
|||
match name {
|
||||
"__len__" if class_literal.is_tuple(db) => {
|
||||
let return_type = specialization
|
||||
.and_then(|spec| spec.tuple(db).len().into_fixed_length())
|
||||
.and_then(|spec| spec.tuple(db))
|
||||
.and_then(|tuple| tuple.len().into_fixed_length())
|
||||
.and_then(|len| i64::try_from(len).ok())
|
||||
.map(Type::IntLiteral)
|
||||
.unwrap_or_else(|| KnownClass::Int.to_instance(db));
|
||||
|
@ -673,7 +679,8 @@ impl<'db> ClassType<'db> {
|
|||
|
||||
"__bool__" if class_literal.is_tuple(db) => {
|
||||
let return_type = specialization
|
||||
.map(|spec| spec.tuple(db).truthiness().into_type(db))
|
||||
.and_then(|spec| spec.tuple(db))
|
||||
.map(|tuple| tuple.truthiness().into_type(db))
|
||||
.unwrap_or_else(|| KnownClass::Bool.to_instance(db));
|
||||
|
||||
synthesize_simple_tuple_method(return_type)
|
||||
|
@ -681,9 +688,8 @@ impl<'db> ClassType<'db> {
|
|||
|
||||
"__getitem__" if class_literal.is_tuple(db) => {
|
||||
specialization
|
||||
.map(|spec| {
|
||||
let tuple = spec.tuple(db);
|
||||
|
||||
.and_then(|spec| spec.tuple(db))
|
||||
.map(|tuple| {
|
||||
let mut element_type_to_indices: FxIndexMap<Type<'db>, Vec<i64>> =
|
||||
FxIndexMap::default();
|
||||
|
||||
|
@ -846,11 +852,12 @@ impl<'db> ClassType<'db> {
|
|||
let mut iterable_parameter =
|
||||
Parameter::positional_only(Some(Name::new_static("iterable")));
|
||||
|
||||
match specialization {
|
||||
Some(spec) => {
|
||||
let tuple = specialization.and_then(|spec| spec.tuple(db));
|
||||
|
||||
match tuple {
|
||||
Some(tuple) => {
|
||||
// TODO: Once we support PEP 646 annotations for `*args` parameters, we can
|
||||
// use the tuple itself as the argument type.
|
||||
let tuple = spec.tuple(db);
|
||||
let tuple_len = tuple.len();
|
||||
|
||||
if tuple_len.minimum() == 0 && tuple_len.maximum().is_none() {
|
||||
|
@ -885,7 +892,7 @@ impl<'db> ClassType<'db> {
|
|||
// - a zero-length tuple
|
||||
// - an unspecialized tuple
|
||||
// - a tuple with no minimum length
|
||||
if specialization.is_none_or(|spec| spec.tuple(db).len().minimum() == 0) {
|
||||
if tuple.is_none_or(|tuple| tuple.len().minimum() == 0) {
|
||||
iterable_parameter =
|
||||
iterable_parameter.with_default_type(Type::empty_tuple(db));
|
||||
}
|
||||
|
@ -1227,7 +1234,7 @@ impl<'db> ClassLiteral<'db> {
|
|||
let parsed = parsed_module(db, file).load(db);
|
||||
let class_def_node = scope.node(db).expect_class(&parsed);
|
||||
class_def_node.type_params.as_ref().map(|type_params| {
|
||||
let index = semantic_index(db, file);
|
||||
let index = semantic_index(db, scope.file(db));
|
||||
let definition = index.expect_single_definition(class_def_node);
|
||||
GenericContext::from_type_params(db, index, definition, type_params)
|
||||
})
|
||||
|
@ -1297,7 +1304,8 @@ impl<'db> ClassLiteral<'db> {
|
|||
specialization: Option<Specialization<'db>>,
|
||||
) -> ClassType<'db> {
|
||||
self.apply_specialization(db, |generic_context| {
|
||||
specialization.unwrap_or_else(|| generic_context.default_specialization(db))
|
||||
specialization
|
||||
.unwrap_or_else(|| generic_context.default_specialization(db, self.known(db)))
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1306,7 +1314,7 @@ impl<'db> ClassLiteral<'db> {
|
|||
/// applies the default specialization to the class's typevars.
|
||||
pub(crate) fn default_specialization(self, db: &'db dyn Db) -> ClassType<'db> {
|
||||
self.apply_specialization(db, |generic_context| {
|
||||
generic_context.default_specialization(db)
|
||||
generic_context.default_specialization(db, self.known(db))
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1887,7 +1895,7 @@ impl<'db> ClassLiteral<'db> {
|
|||
|
||||
if field_ty
|
||||
.into_nominal_instance()
|
||||
.is_some_and(|instance| instance.class.is_known(db, KnownClass::KwOnly))
|
||||
.is_some_and(|instance| instance.class(db).is_known(db, KnownClass::KwOnly))
|
||||
{
|
||||
// Attributes annotated with `dataclass.KW_ONLY` are not present in the synthesized
|
||||
// `__init__` method; they are used to indicate that the following parameters are
|
||||
|
@ -3100,7 +3108,10 @@ impl KnownClass {
|
|||
|
||||
/// Determine whether instances of this class are always truthy, always falsy,
|
||||
/// or have an ambiguous truthiness.
|
||||
pub(crate) const fn bool(self) -> Truthiness {
|
||||
///
|
||||
/// Returns `None` for `KnownClass::Tuple`, since the truthiness of a tuple
|
||||
/// depends on its spec.
|
||||
pub(crate) const fn bool(self) -> Option<Truthiness> {
|
||||
match self {
|
||||
// N.B. It's only generally safe to infer `Truthiness::AlwaysTrue` for a `KnownClass`
|
||||
// variant if the class's `__bool__` method always returns the same thing *and* the
|
||||
|
@ -3126,9 +3137,9 @@ impl KnownClass {
|
|||
| Self::GeneratorType
|
||||
| Self::AsyncGeneratorType
|
||||
| Self::MethodWrapperType
|
||||
| Self::CoroutineType => Truthiness::AlwaysTrue,
|
||||
| Self::CoroutineType => Some(Truthiness::AlwaysTrue),
|
||||
|
||||
Self::NoneType => Truthiness::AlwaysFalse,
|
||||
Self::NoneType => Some(Truthiness::AlwaysFalse),
|
||||
|
||||
Self::Any
|
||||
| Self::BaseException
|
||||
|
@ -3145,7 +3156,6 @@ impl KnownClass {
|
|||
| Self::StdlibAlias
|
||||
| Self::SupportsIndex
|
||||
| Self::Set
|
||||
| Self::Tuple
|
||||
| Self::Int
|
||||
| Self::Type
|
||||
| Self::Bytes
|
||||
|
@ -3184,7 +3194,9 @@ impl KnownClass {
|
|||
| Self::KwOnly
|
||||
| Self::InitVar
|
||||
| Self::NamedTupleFallback
|
||||
| Self::TypedDictFallback => Truthiness::Ambiguous,
|
||||
| Self::TypedDictFallback => Some(Truthiness::Ambiguous),
|
||||
|
||||
Self::Tuple => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3432,6 +3444,82 @@ impl KnownClass {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn is_tuple_subclass(self) -> bool {
|
||||
match self {
|
||||
KnownClass::Tuple | KnownClass::VersionInfo => true,
|
||||
|
||||
KnownClass::Bool
|
||||
| KnownClass::Object
|
||||
| KnownClass::Bytes
|
||||
| KnownClass::Bytearray
|
||||
| KnownClass::Type
|
||||
| KnownClass::Int
|
||||
| KnownClass::Float
|
||||
| KnownClass::Complex
|
||||
| KnownClass::Str
|
||||
| KnownClass::List
|
||||
| KnownClass::Set
|
||||
| KnownClass::FrozenSet
|
||||
| KnownClass::Dict
|
||||
| KnownClass::Slice
|
||||
| KnownClass::Property
|
||||
| KnownClass::BaseException
|
||||
| KnownClass::Exception
|
||||
| KnownClass::BaseExceptionGroup
|
||||
| KnownClass::ExceptionGroup
|
||||
| KnownClass::Staticmethod
|
||||
| KnownClass::Classmethod
|
||||
| KnownClass::Awaitable
|
||||
| KnownClass::Generator
|
||||
| KnownClass::Deprecated
|
||||
| KnownClass::Super
|
||||
| KnownClass::Enum
|
||||
| KnownClass::EnumType
|
||||
| KnownClass::Auto
|
||||
| KnownClass::Member
|
||||
| KnownClass::Nonmember
|
||||
| KnownClass::ABCMeta
|
||||
| KnownClass::GenericAlias
|
||||
| KnownClass::ModuleType
|
||||
| KnownClass::FunctionType
|
||||
| KnownClass::MethodType
|
||||
| KnownClass::MethodWrapperType
|
||||
| KnownClass::WrapperDescriptorType
|
||||
| KnownClass::UnionType
|
||||
| KnownClass::GeneratorType
|
||||
| KnownClass::AsyncGeneratorType
|
||||
| KnownClass::CoroutineType
|
||||
| KnownClass::NoneType
|
||||
| KnownClass::Any
|
||||
| KnownClass::StdlibAlias
|
||||
| KnownClass::SpecialForm
|
||||
| KnownClass::TypeVar
|
||||
| KnownClass::ParamSpec
|
||||
| KnownClass::ParamSpecArgs
|
||||
| KnownClass::ParamSpecKwargs
|
||||
| KnownClass::TypeVarTuple
|
||||
| KnownClass::TypeAliasType
|
||||
| KnownClass::NoDefaultType
|
||||
| KnownClass::NamedTuple
|
||||
| KnownClass::NewType
|
||||
| KnownClass::SupportsIndex
|
||||
| KnownClass::Iterable
|
||||
| KnownClass::Iterator
|
||||
| KnownClass::ChainMap
|
||||
| KnownClass::Counter
|
||||
| KnownClass::DefaultDict
|
||||
| KnownClass::Deque
|
||||
| KnownClass::OrderedDict
|
||||
| KnownClass::EllipsisType
|
||||
| KnownClass::NotImplementedType
|
||||
| KnownClass::Field
|
||||
| KnownClass::KwOnly
|
||||
| KnownClass::InitVar
|
||||
| KnownClass::TypedDictFallback
|
||||
| KnownClass::NamedTupleFallback => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if this class is a protocol class.
|
||||
///
|
||||
/// In an ideal world, perhaps we wouldn't hardcode this knowledge here;
|
||||
|
@ -3874,8 +3962,10 @@ impl KnownClass {
|
|||
}
|
||||
}
|
||||
|
||||
/// Return true if all instances of this `KnownClass` compare equal.
|
||||
pub(super) const fn is_single_valued(self) -> bool {
|
||||
/// Returns `Some(true)` if all instances of this `KnownClass` compare equal.
|
||||
/// Returns `None` for `KnownClass::Tuple`, since whether or not a tuple type
|
||||
/// is single-valued depends on the tuple spec.
|
||||
pub(super) const fn is_single_valued(self) -> Option<bool> {
|
||||
match self {
|
||||
Self::NoneType
|
||||
| Self::NoDefaultType
|
||||
|
@ -3883,7 +3973,7 @@ impl KnownClass {
|
|||
| Self::EllipsisType
|
||||
| Self::TypeAliasType
|
||||
| Self::UnionType
|
||||
| Self::NotImplementedType => true,
|
||||
| Self::NotImplementedType => Some(true),
|
||||
|
||||
Self::Any
|
||||
| Self::Bool
|
||||
|
@ -3896,7 +3986,6 @@ impl KnownClass {
|
|||
| Self::Complex
|
||||
| Self::Str
|
||||
| Self::List
|
||||
| Self::Tuple
|
||||
| Self::Set
|
||||
| Self::FrozenSet
|
||||
| Self::Dict
|
||||
|
@ -3948,7 +4037,9 @@ impl KnownClass {
|
|||
| Self::Iterable
|
||||
| Self::Iterator
|
||||
| Self::NamedTupleFallback
|
||||
| Self::TypedDictFallback => false,
|
||||
| Self::TypedDictFallback => Some(false),
|
||||
|
||||
Self::Tuple => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4555,47 +4646,6 @@ impl<'db> KnownClassLookupError<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) struct SliceLiteral {
|
||||
pub(crate) start: Option<i32>,
|
||||
pub(crate) stop: Option<i32>,
|
||||
pub(crate) step: Option<i32>,
|
||||
}
|
||||
|
||||
impl<'db> ClassType<'db> {
|
||||
/// If this class is a specialization of `slice`, returns a [`SliceLiteral`] describing it.
|
||||
/// Otherwise returns `None`.
|
||||
///
|
||||
/// The specialization must be one in which the typevars are solved as being statically known
|
||||
/// integers or `None`.
|
||||
pub(crate) fn slice_literal(self, db: &'db dyn Db) -> Option<SliceLiteral> {
|
||||
let ClassType::Generic(alias) = self else {
|
||||
return None;
|
||||
};
|
||||
if !alias.origin(db).is_known(db, KnownClass::Slice) {
|
||||
return None;
|
||||
}
|
||||
let [start, stop, step] = alias.specialization(db).types(db) else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let to_u32 = |ty: &Type<'db>| match ty {
|
||||
Type::IntLiteral(n) => i32::try_from(*n).map(Some).ok(),
|
||||
Type::BooleanLiteral(b) => Some(Some(i32::from(*b))),
|
||||
Type::NominalInstance(instance)
|
||||
if instance.class.is_known(db, KnownClass::NoneType) =>
|
||||
{
|
||||
Some(None)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
Some(SliceLiteral {
|
||||
start: to_u32(start)?,
|
||||
stop: to_u32(stop)?,
|
||||
step: to_u32(step)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, salsa::Update, get_size2::GetSize)]
|
||||
pub(super) struct MetaclassError<'db> {
|
||||
kind: MetaclassErrorKind<'db>,
|
||||
|
@ -4663,16 +4713,14 @@ impl SlotsKind {
|
|||
|
||||
match slots_ty {
|
||||
// __slots__ = ("a", "b")
|
||||
Type::Tuple(tuple) => {
|
||||
let tuple = tuple.tuple(db);
|
||||
if tuple.is_variadic() {
|
||||
Self::Dynamic
|
||||
} else if tuple.is_empty() {
|
||||
Self::Empty
|
||||
} else {
|
||||
Self::NotEmpty
|
||||
}
|
||||
}
|
||||
Type::NominalInstance(nominal) => match nominal
|
||||
.tuple_spec(db)
|
||||
.and_then(|spec| spec.len().into_fixed_length())
|
||||
{
|
||||
Some(0) => Self::Empty,
|
||||
Some(_) => Self::NotEmpty,
|
||||
None => Self::Dynamic,
|
||||
},
|
||||
|
||||
// __slots__ = "abc" # Same as `("abc",)`
|
||||
Type::StringLiteral(_) => Self::NotEmpty,
|
||||
|
|
|
@ -87,7 +87,7 @@ impl<'db> ClassBase<'db> {
|
|||
}
|
||||
Type::GenericAlias(generic) => Some(Self::Class(ClassType::Generic(generic))),
|
||||
Type::NominalInstance(instance)
|
||||
if instance.class.is_known(db, KnownClass::GenericAlias) =>
|
||||
if instance.class(db).is_known(db, KnownClass::GenericAlias) =>
|
||||
{
|
||||
Self::try_from_type(db, todo_type!("GenericAlias instance"))
|
||||
}
|
||||
|
@ -153,7 +153,6 @@ impl<'db> ClassBase<'db> {
|
|||
| Type::EnumLiteral(_)
|
||||
| Type::StringLiteral(_)
|
||||
| Type::LiteralString
|
||||
| Type::Tuple(_)
|
||||
| Type::ModuleLiteral(_)
|
||||
| Type::TypeVar(_)
|
||||
| Type::BoundSuper(_)
|
||||
|
|
|
@ -73,9 +73,17 @@ impl Display for DisplayRepresentation<'_> {
|
|||
Type::Dynamic(dynamic) => dynamic.fmt(f),
|
||||
Type::Never => f.write_str("Never"),
|
||||
Type::NominalInstance(instance) => {
|
||||
match (instance.class, instance.class.known(self.db)) {
|
||||
let class = instance.class(self.db);
|
||||
|
||||
match (class, class.known(self.db)) {
|
||||
(_, Some(KnownClass::NoneType)) => f.write_str("None"),
|
||||
(_, Some(KnownClass::NoDefaultType)) => f.write_str("NoDefault"),
|
||||
(ClassType::Generic(alias), Some(KnownClass::Tuple)) => alias
|
||||
.specialization(self.db)
|
||||
.tuple(self.db)
|
||||
.expect("Specialization::tuple() should always return `Some()` for `KnownClass::Tuple`")
|
||||
.display(self.db)
|
||||
.fmt(f),
|
||||
(ClassType::NonGeneric(class), _) => f.write_str(class.name(self.db)),
|
||||
(ClassType::Generic(alias), _) => alias.display(self.db).fmt(f),
|
||||
}
|
||||
|
@ -207,7 +215,6 @@ impl Display for DisplayRepresentation<'_> {
|
|||
name = enum_literal.name(self.db),
|
||||
)
|
||||
}
|
||||
Type::Tuple(specialization) => specialization.tuple(self.db).display(self.db).fmt(f),
|
||||
Type::TypeVar(bound_typevar) => {
|
||||
f.write_str(bound_typevar.typevar(self.db).name(self.db))?;
|
||||
if let Some(binding_context) = bound_typevar.binding_context(self.db).name(self.db)
|
||||
|
@ -395,8 +402,8 @@ pub(crate) struct DisplayGenericAlias<'db> {
|
|||
|
||||
impl Display for DisplayGenericAlias<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
if self.origin.is_known(self.db, KnownClass::Tuple) {
|
||||
self.specialization.tuple(self.db).display(self.db).fmt(f)
|
||||
if let Some(tuple) = self.specialization.tuple(self.db) {
|
||||
tuple.display(self.db).fmt(f)
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
|
|
|
@ -119,39 +119,44 @@ pub(crate) fn enum_metadata<'db>(
|
|||
}
|
||||
|
||||
let inferred = place_from_bindings(db, bindings);
|
||||
|
||||
let value_ty = match inferred {
|
||||
Place::Unbound => {
|
||||
return None;
|
||||
}
|
||||
Place::Type(ty, _) => {
|
||||
match ty {
|
||||
let special_case = match ty {
|
||||
Type::Callable(_) | Type::FunctionLiteral(_) => {
|
||||
// Some types are specifically disallowed for enum members.
|
||||
return None;
|
||||
}
|
||||
Type::NominalInstance(instance) => match instance.class(db).known(db) {
|
||||
// enum.nonmember
|
||||
Type::NominalInstance(instance)
|
||||
if instance.class.is_known(db, KnownClass::Nonmember) =>
|
||||
{
|
||||
return None;
|
||||
}
|
||||
Some(KnownClass::Nonmember) => return None,
|
||||
|
||||
// enum.member
|
||||
Type::NominalInstance(instance)
|
||||
if instance.class.is_known(db, KnownClass::Member) =>
|
||||
{
|
||||
Some(KnownClass::Member) => Some(
|
||||
ty.member(db, "value")
|
||||
.place
|
||||
.ignore_possibly_unbound()
|
||||
.unwrap_or(Type::unknown())
|
||||
}
|
||||
.unwrap_or(Type::unknown()),
|
||||
),
|
||||
|
||||
// enum.auto
|
||||
Type::NominalInstance(instance)
|
||||
if instance.class.is_known(db, KnownClass::Auto) =>
|
||||
{
|
||||
Some(KnownClass::Auto) => {
|
||||
auto_counter += 1;
|
||||
Type::IntLiteral(auto_counter)
|
||||
Some(Type::IntLiteral(auto_counter))
|
||||
}
|
||||
_ => {
|
||||
|
||||
_ => None,
|
||||
},
|
||||
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(special_case) = special_case {
|
||||
special_case
|
||||
} else {
|
||||
let dunder_get = ty
|
||||
.member_lookup_with_policy(
|
||||
db,
|
||||
|
@ -170,7 +175,6 @@ pub(crate) fn enum_metadata<'db>(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Duplicate values are aliases that are not considered separate members. This check is only
|
||||
|
@ -203,7 +207,7 @@ pub(crate) fn enum_metadata<'db>(
|
|||
Ok(PlaceAndQualifiers {
|
||||
place: Place::Type(Type::NominalInstance(instance), _),
|
||||
..
|
||||
}) if instance.class.is_known(db, KnownClass::Member) => {
|
||||
}) if instance.class(db).is_known(db, KnownClass::Member) => {
|
||||
// If the attribute is specifically declared with `enum.member`, it is considered a member
|
||||
}
|
||||
_ => {
|
||||
|
|
|
@ -945,7 +945,7 @@ fn is_instance_truthiness<'db>(
|
|||
let is_instance = |ty: &Type<'_>| {
|
||||
if let Type::NominalInstance(instance) = ty {
|
||||
if instance
|
||||
.class
|
||||
.class(db)
|
||||
.iter_mro(db)
|
||||
.filter_map(ClassBase::into_class)
|
||||
.any(|c| match c {
|
||||
|
@ -994,8 +994,6 @@ fn is_instance_truthiness<'db>(
|
|||
.is_some_and(is_instance),
|
||||
),
|
||||
|
||||
Type::Tuple(..) => always_true_if(class.is_known(db, KnownClass::Tuple)),
|
||||
|
||||
Type::FunctionLiteral(..) => {
|
||||
always_true_if(is_instance(&KnownClass::FunctionType.to_instance(db)))
|
||||
}
|
||||
|
|
|
@ -10,13 +10,13 @@ use crate::semantic_index::scope::{FileScopeId, NodeWithScopeKind};
|
|||
use crate::types::class::ClassType;
|
||||
use crate::types::class_base::ClassBase;
|
||||
use crate::types::infer::infer_definition_types;
|
||||
use crate::types::instance::{NominalInstanceType, Protocol, ProtocolInstanceType};
|
||||
use crate::types::instance::{Protocol, ProtocolInstanceType};
|
||||
use crate::types::signatures::{Parameter, Parameters, Signature};
|
||||
use crate::types::tuple::{TupleSpec, TupleType};
|
||||
use crate::types::tuple::{TupleSpec, TupleType, walk_tuple_type};
|
||||
use crate::types::{
|
||||
BoundTypeVarInstance, KnownInstanceType, Type, TypeMapping, TypeRelation, TypeTransformer,
|
||||
TypeVarBoundOrConstraints, TypeVarInstance, TypeVarVariance, UnionType, binding_type,
|
||||
declaration_type,
|
||||
BoundTypeVarInstance, KnownClass, KnownInstanceType, Type, TypeMapping, TypeRelation,
|
||||
TypeTransformer, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarVariance, UnionType,
|
||||
binding_type, declaration_type,
|
||||
};
|
||||
use crate::{Db, FxOrderSet};
|
||||
|
||||
|
@ -230,8 +230,22 @@ impl<'db> GenericContext<'db> {
|
|||
parameter
|
||||
}
|
||||
|
||||
pub(crate) fn default_specialization(self, db: &'db dyn Db) -> Specialization<'db> {
|
||||
self.specialize_partial(db, &vec![None; self.variables(db).len()])
|
||||
pub(crate) fn default_specialization(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
known_class: Option<KnownClass>,
|
||||
) -> Specialization<'db> {
|
||||
let partial = self.specialize_partial(db, &vec![None; self.variables(db).len()]);
|
||||
if known_class == Some(KnownClass::Tuple) {
|
||||
Specialization::new(
|
||||
db,
|
||||
self,
|
||||
partial.types(db),
|
||||
TupleType::homogeneous(db, Type::unknown()),
|
||||
)
|
||||
} else {
|
||||
partial
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn identity_specialization(self, db: &'db dyn Db) -> Specialization<'db> {
|
||||
|
@ -401,24 +415,14 @@ pub(super) fn walk_specialization<'db, V: super::visitor::TypeVisitor<'db> + ?Si
|
|||
visitor.visit_type(db, *ty);
|
||||
}
|
||||
if let Some(tuple) = specialization.tuple_inner(db) {
|
||||
visitor.visit_tuple_type(db, tuple);
|
||||
walk_tuple_type(db, tuple, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> Specialization<'db> {
|
||||
/// Returns the tuple spec for a specialization of the `tuple` class.
|
||||
pub(crate) fn tuple(self, db: &'db dyn Db) -> &'db TupleSpec<'db> {
|
||||
if let Some(tuple) = self.tuple_inner(db).map(|tuple_type| tuple_type.tuple(db)) {
|
||||
return tuple;
|
||||
}
|
||||
if let [element_type] = self.types(db) {
|
||||
if let Some(tuple) = TupleType::new(db, TupleSpec::homogeneous(*element_type)) {
|
||||
return tuple.tuple(db);
|
||||
}
|
||||
}
|
||||
TupleType::new(db, TupleSpec::homogeneous(Type::unknown()))
|
||||
.expect("tuple[Unknown, ...] should never contain Never")
|
||||
.tuple(db)
|
||||
pub(crate) fn tuple(self, db: &'db dyn Db) -> Option<&'db TupleSpec<'db>> {
|
||||
self.tuple_inner(db).map(|tuple_type| tuple_type.tuple(db))
|
||||
}
|
||||
|
||||
/// Returns the type that a typevar is mapped to, or None if the typevar isn't part of this
|
||||
|
@ -628,6 +632,16 @@ impl<'db> Specialization<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
match (self.tuple_inner(db), other.tuple_inner(db)) {
|
||||
(Some(_), None) | (None, Some(_)) => return false,
|
||||
(None, None) => {}
|
||||
(Some(self_tuple), Some(other_tuple)) => {
|
||||
if !self_tuple.is_equivalent_to(db, other_tuple) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
|
@ -640,6 +654,8 @@ impl<'db> Specialization<'db> {
|
|||
for ty in self.types(db) {
|
||||
ty.find_legacy_typevars(db, binding_context, typevars);
|
||||
}
|
||||
// A tuple's specialization will include all of its element types, so we don't need to also
|
||||
// look in `self.tuple`.
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -834,10 +850,15 @@ impl<'db> SpecializationBuilder<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
(Type::Tuple(formal_tuple), Type::Tuple(actual_tuple)) => {
|
||||
let formal_tuple = formal_tuple.tuple(self.db);
|
||||
let actual_tuple = actual_tuple.tuple(self.db);
|
||||
let Some(most_precise_length) = formal_tuple.len().most_precise(actual_tuple.len()) else {
|
||||
(formal, Type::NominalInstance(actual_nominal)) => {
|
||||
// Special case: `formal` and `actual` are both tuples.
|
||||
if let (Some(formal_tuple), Some(actual_tuple)) = (
|
||||
formal.tuple_instance_spec(self.db),
|
||||
actual_nominal.tuple_spec(self.db),
|
||||
) {
|
||||
let Some(most_precise_length) =
|
||||
formal_tuple.len().most_precise(actual_tuple.len())
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
let Ok(formal_tuple) = formal_tuple.resize(self.db, most_precise_length) else {
|
||||
|
@ -851,35 +872,36 @@ impl<'db> SpecializationBuilder<'db> {
|
|||
{
|
||||
self.infer(*formal_element, *actual_element)?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
(
|
||||
Type::NominalInstance(NominalInstanceType {
|
||||
class: ClassType::Generic(formal_alias),
|
||||
..
|
||||
})
|
||||
// Extract formal_alias if this is a generic class
|
||||
let formal_alias = match formal {
|
||||
Type::NominalInstance(formal_nominal) => {
|
||||
formal_nominal.class(self.db).into_generic_alias()
|
||||
}
|
||||
// TODO: This will only handle classes that explicit implement a generic protocol
|
||||
// by listing it as a base class. To handle classes that implicitly implement a
|
||||
// generic protocol, we will need to check the types of the protocol members to be
|
||||
// able to infer the specialization of the protocol that the class implements.
|
||||
| Type::ProtocolInstance(ProtocolInstanceType {
|
||||
inner: Protocol::FromClass(ClassType::Generic(formal_alias)),
|
||||
Type::ProtocolInstance(ProtocolInstanceType {
|
||||
inner: Protocol::FromClass(ClassType::Generic(alias)),
|
||||
..
|
||||
}),
|
||||
Type::NominalInstance(NominalInstanceType {
|
||||
class: actual_class,
|
||||
..
|
||||
}),
|
||||
) => {
|
||||
}) => Some(alias),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(formal_alias) = formal_alias {
|
||||
let formal_origin = formal_alias.origin(self.db);
|
||||
for base in actual_class.iter_mro(self.db) {
|
||||
for base in actual_nominal.class(self.db).iter_mro(self.db) {
|
||||
let ClassBase::Class(ClassType::Generic(base_alias)) = base else {
|
||||
continue;
|
||||
};
|
||||
if formal_origin != base_alias.origin(self.db) {
|
||||
continue;
|
||||
}
|
||||
let formal_specialization = formal_alias.specialization(self.db).types(self.db);
|
||||
let formal_specialization =
|
||||
formal_alias.specialization(self.db).types(self.db);
|
||||
let base_specialization = base_alias.specialization(self.db).types(self.db);
|
||||
for (formal_ty, base_ty) in
|
||||
formal_specialization.iter().zip(base_specialization)
|
||||
|
@ -889,6 +911,7 @@ impl<'db> SpecializationBuilder<'db> {
|
|||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Add more forms that we can structurally induct into: type[C], callables
|
||||
_ => {}
|
||||
|
|
|
@ -94,7 +94,7 @@ impl<'db> AllMembers<'db> {
|
|||
),
|
||||
|
||||
Type::NominalInstance(instance) => {
|
||||
let (class_literal, _specialization) = instance.class.class_literal(db);
|
||||
let (class_literal, _specialization) = instance.class(db).class_literal(db);
|
||||
self.extend_with_instance_members(db, ty, class_literal);
|
||||
}
|
||||
|
||||
|
@ -137,7 +137,6 @@ impl<'db> AllMembers<'db> {
|
|||
| Type::BytesLiteral(_)
|
||||
| Type::EnumLiteral(_)
|
||||
| Type::LiteralString
|
||||
| Type::Tuple(_)
|
||||
| Type::PropertyInstance(_)
|
||||
| Type::FunctionLiteral(_)
|
||||
| Type::BoundMethod(_)
|
||||
|
@ -208,7 +207,7 @@ impl<'db> AllMembers<'db> {
|
|||
match ty {
|
||||
Type::NominalInstance(instance)
|
||||
if matches!(
|
||||
instance.class.known(db),
|
||||
instance.class(db).known(db),
|
||||
Some(
|
||||
KnownClass::TypeVar
|
||||
| KnownClass::TypeVarTuple
|
||||
|
|
|
@ -62,7 +62,7 @@ use super::string_annotation::{
|
|||
BYTE_STRING_TYPE_ANNOTATION, FSTRING_TYPE_ANNOTATION, parse_string_annotation,
|
||||
};
|
||||
use super::subclass_of::SubclassOfInner;
|
||||
use super::{ClassBase, NominalInstanceType, add_inferred_python_version_hint_to_diagnostic};
|
||||
use super::{ClassBase, add_inferred_python_version_hint_to_diagnostic};
|
||||
use crate::module_name::{ModuleName, ModuleNameResolutionError};
|
||||
use crate::module_resolver::resolve_module;
|
||||
use crate::node_key::NodeKey;
|
||||
|
@ -90,7 +90,7 @@ use crate::semantic_index::{
|
|||
ApplicableConstraints, EnclosingSnapshotResult, SemanticIndex, place_table, semantic_index,
|
||||
};
|
||||
use crate::types::call::{Binding, Bindings, CallArguments, CallError, CallErrorKind};
|
||||
use crate::types::class::{CodeGeneratorKind, Field, MetaclassErrorKind, SliceLiteral};
|
||||
use crate::types::class::{CodeGeneratorKind, Field, MetaclassErrorKind};
|
||||
use crate::types::diagnostic::{
|
||||
self, CALL_NON_CALLABLE, CONFLICTING_DECLARATIONS, CONFLICTING_METACLASS,
|
||||
CYCLIC_CLASS_DEFINITION, DIVISION_BY_ZERO, DUPLICATE_KW_ONLY, INCONSISTENT_MRO,
|
||||
|
@ -111,9 +111,10 @@ use crate::types::function::{
|
|||
FunctionDecorators, FunctionLiteral, FunctionType, KnownFunction, OverloadLiteral,
|
||||
};
|
||||
use crate::types::generics::{GenericContext, bind_typevar};
|
||||
use crate::types::instance::SliceLiteral;
|
||||
use crate::types::mro::MroErrorKind;
|
||||
use crate::types::signatures::{CallableSignature, Signature};
|
||||
use crate::types::tuple::{TupleSpec, TupleType};
|
||||
use crate::types::tuple::{Tuple, TupleSpec, TupleType};
|
||||
use crate::types::unpacker::{UnpackResult, Unpacker};
|
||||
use crate::types::{
|
||||
CallDunderError, CallableType, ClassLiteral, ClassType, DataclassParams, DynamicType,
|
||||
|
@ -1375,7 +1376,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
continue;
|
||||
};
|
||||
|
||||
if !instance.class.is_known(self.db(), KnownClass::KwOnly) {
|
||||
if !instance
|
||||
.class(self.db())
|
||||
.is_known(self.db(), KnownClass::KwOnly)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -1772,7 +1776,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
Type::BooleanLiteral(_) | Type::IntLiteral(_) => {}
|
||||
Type::NominalInstance(instance)
|
||||
if matches!(
|
||||
instance.class.known(self.db()),
|
||||
instance.class(self.db()).known(self.db()),
|
||||
Some(KnownClass::Float | KnownClass::Int | KnownClass::Bool)
|
||||
) => {}
|
||||
_ => return false,
|
||||
|
@ -3286,22 +3290,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
}
|
||||
|
||||
fn infer_exception(&mut self, node: Option<&ast::Expr>, is_star: bool) -> Type<'db> {
|
||||
fn extract_tuple_specialization<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option<Type<'db>> {
|
||||
let class = ty.into_nominal_instance()?.class;
|
||||
if !class.is_known(db, KnownClass::Tuple) {
|
||||
return None;
|
||||
}
|
||||
let ClassType::Generic(class) = class else {
|
||||
return None;
|
||||
};
|
||||
let specialization = class.specialization(db).types(db)[0];
|
||||
let specialization_instance = specialization.to_instance(db)?;
|
||||
|
||||
specialization_instance
|
||||
.is_assignable_to(db, KnownClass::BaseException.to_instance(db))
|
||||
.then_some(specialization_instance)
|
||||
}
|
||||
|
||||
// If there is no handled exception, it's invalid syntax;
|
||||
// a diagnostic will have already been emitted
|
||||
let node_ty = node.map_or(Type::unknown(), |ty| self.infer_expression(ty));
|
||||
|
@ -3309,9 +3297,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
|
||||
// If it's an `except*` handler, this won't actually be the type of the bound symbol;
|
||||
// it will actually be the type of the generic parameters to `BaseExceptionGroup` or `ExceptionGroup`.
|
||||
let symbol_ty = if let Type::Tuple(tuple) = node_ty {
|
||||
let symbol_ty = if let Some(tuple_spec) = node_ty.tuple_instance_spec(self.db()) {
|
||||
let mut builder = UnionBuilder::new(self.db());
|
||||
for element in tuple.tuple(self.db()).all_elements().copied() {
|
||||
for element in tuple_spec.all_elements().copied() {
|
||||
builder = builder.add(
|
||||
if element.is_assignable_to(self.db(), type_base_exception) {
|
||||
element.to_instance(self.db()).expect(
|
||||
|
@ -3336,7 +3324,22 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
self.db(),
|
||||
Type::homogeneous_tuple(self.db(), type_base_exception),
|
||||
) {
|
||||
extract_tuple_specialization(self.db(), node_ty)
|
||||
node_ty
|
||||
.tuple_instance_spec(self.db())
|
||||
.and_then(|spec| {
|
||||
let specialization = spec
|
||||
.homogeneous_element_type(self.db())
|
||||
.to_instance(self.db());
|
||||
|
||||
debug_assert!(specialization.is_some_and(|specialization_type| {
|
||||
specialization_type.is_assignable_to(
|
||||
self.db(),
|
||||
KnownClass::BaseException.to_instance(self.db()),
|
||||
)
|
||||
}));
|
||||
|
||||
specialization
|
||||
})
|
||||
.unwrap_or_else(|| KnownClass::BaseException.to_instance(self.db()))
|
||||
} else if node_ty.is_assignable_to(
|
||||
self.db(),
|
||||
|
@ -3954,7 +3957,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
}
|
||||
|
||||
// Super instances do not allow attribute assignment
|
||||
Type::NominalInstance(instance) if instance.class.is_known(db, KnownClass::Super) => {
|
||||
Type::NominalInstance(instance)
|
||||
if instance.class(db).is_known(db, KnownClass::Super) =>
|
||||
{
|
||||
if emit_diagnostics {
|
||||
if let Some(builder) = self.context.report_lint(&INVALID_ASSIGNMENT, target) {
|
||||
builder.into_diagnostic(format_args!(
|
||||
|
@ -3987,7 +3992,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
| Type::BytesLiteral(..)
|
||||
| Type::EnumLiteral(..)
|
||||
| Type::LiteralString
|
||||
| Type::Tuple(..)
|
||||
| Type::SpecialForm(..)
|
||||
| Type::KnownInstance(..)
|
||||
| Type::PropertyInstance(..)
|
||||
|
@ -4403,15 +4407,17 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
ast::Expr::Name(name) => self.infer_definition(name),
|
||||
ast::Expr::List(ast::ExprList { elts, .. })
|
||||
| ast::Expr::Tuple(ast::ExprTuple { elts, .. }) => {
|
||||
let mut assigned_tys = match assigned_ty {
|
||||
Some(Type::Tuple(tuple)) => {
|
||||
Either::Left(tuple.tuple(self.db()).all_elements().copied())
|
||||
}
|
||||
Some(_) | None => Either::Right(std::iter::empty()),
|
||||
};
|
||||
|
||||
if let Some(tuple_spec) =
|
||||
assigned_ty.and_then(|ty| ty.tuple_instance_spec(self.db()))
|
||||
{
|
||||
let mut assigned_tys = tuple_spec.all_elements();
|
||||
for element in elts {
|
||||
self.infer_target_impl(element, value, assigned_tys.next());
|
||||
self.infer_target_impl(element, value, assigned_tys.next().copied());
|
||||
}
|
||||
} else {
|
||||
for element in elts {
|
||||
self.infer_target_impl(element, value, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
ast::Expr::Attribute(
|
||||
|
@ -4613,7 +4619,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
|
||||
// Handle various singletons.
|
||||
if let Type::NominalInstance(instance) = declared.inner_type() {
|
||||
if instance.class.is_known(self.db(), KnownClass::SpecialForm) {
|
||||
if instance
|
||||
.class(self.db())
|
||||
.is_known(self.db(), KnownClass::SpecialForm)
|
||||
{
|
||||
if let Some(name_expr) = target.as_name_expr() {
|
||||
if let Some(special_form) = SpecialFormType::try_from_file_and_name(
|
||||
self.db(),
|
||||
|
@ -7182,7 +7191,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
| Type::LiteralString
|
||||
| Type::BytesLiteral(_)
|
||||
| Type::EnumLiteral(_)
|
||||
| Type::Tuple(_)
|
||||
| Type::BoundSuper(_)
|
||||
| Type::TypeVar(_)
|
||||
| Type::TypeIs(_)
|
||||
|
@ -7508,7 +7516,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
| Type::LiteralString
|
||||
| Type::BytesLiteral(_)
|
||||
| Type::EnumLiteral(_)
|
||||
| Type::Tuple(_)
|
||||
| Type::BoundSuper(_)
|
||||
| Type::TypeVar(_)
|
||||
| Type::TypeIs(_)
|
||||
|
@ -7538,7 +7545,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
| Type::LiteralString
|
||||
| Type::BytesLiteral(_)
|
||||
| Type::EnumLiteral(_)
|
||||
| Type::Tuple(_)
|
||||
| Type::BoundSuper(_)
|
||||
| Type::TypeVar(_)
|
||||
| Type::TypeIs(_)
|
||||
|
@ -7938,14 +7944,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
// language spec.
|
||||
// - `[ast::CompOp::Is]`: return `false` if unequal, `bool` if equal
|
||||
// - `[ast::CompOp::IsNot]`: return `true` if unequal, `bool` if equal
|
||||
match (left, right) {
|
||||
let comparison_result = match (left, right) {
|
||||
(Type::Union(union), other) => {
|
||||
let mut builder = UnionBuilder::new(self.db());
|
||||
for element in union.elements(self.db()) {
|
||||
builder =
|
||||
builder.add(self.infer_binary_type_comparison(*element, op, other, range)?);
|
||||
}
|
||||
Ok(builder.build())
|
||||
Some(Ok(builder.build()))
|
||||
}
|
||||
(other, Type::Union(union)) => {
|
||||
let mut builder = UnionBuilder::new(self.db());
|
||||
|
@ -7953,27 +7959,29 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
builder =
|
||||
builder.add(self.infer_binary_type_comparison(other, op, *element, range)?);
|
||||
}
|
||||
Ok(builder.build())
|
||||
Some(Ok(builder.build()))
|
||||
}
|
||||
|
||||
(Type::Intersection(intersection), right) => self
|
||||
.infer_binary_intersection_type_comparison(
|
||||
(Type::Intersection(intersection), right) => {
|
||||
Some(self.infer_binary_intersection_type_comparison(
|
||||
intersection,
|
||||
op,
|
||||
right,
|
||||
IntersectionOn::Left,
|
||||
range,
|
||||
),
|
||||
(left, Type::Intersection(intersection)) => self
|
||||
.infer_binary_intersection_type_comparison(
|
||||
))
|
||||
}
|
||||
(left, Type::Intersection(intersection)) => {
|
||||
Some(self.infer_binary_intersection_type_comparison(
|
||||
intersection,
|
||||
op,
|
||||
left,
|
||||
IntersectionOn::Right,
|
||||
range,
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
(Type::IntLiteral(n), Type::IntLiteral(m)) => match op {
|
||||
(Type::IntLiteral(n), Type::IntLiteral(m)) => Some(match op {
|
||||
ast::CmpOp::Eq => Ok(Type::BooleanLiteral(n == m)),
|
||||
ast::CmpOp::NotEq => Ok(Type::BooleanLiteral(n != m)),
|
||||
ast::CmpOp::Lt => Ok(Type::BooleanLiteral(n < m)),
|
||||
|
@ -8002,45 +8010,54 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
left_ty: left,
|
||||
right_ty: right,
|
||||
}),
|
||||
},
|
||||
(Type::IntLiteral(_), Type::NominalInstance(_)) => self.infer_binary_type_comparison(
|
||||
}),
|
||||
(Type::IntLiteral(_), Type::NominalInstance(_)) => {
|
||||
Some(self.infer_binary_type_comparison(
|
||||
KnownClass::Int.to_instance(self.db()),
|
||||
op,
|
||||
right,
|
||||
range,
|
||||
),
|
||||
(Type::NominalInstance(_), Type::IntLiteral(_)) => self.infer_binary_type_comparison(
|
||||
))
|
||||
}
|
||||
(Type::NominalInstance(_), Type::IntLiteral(_)) => {
|
||||
Some(self.infer_binary_type_comparison(
|
||||
left,
|
||||
op,
|
||||
KnownClass::Int.to_instance(self.db()),
|
||||
range,
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
// Booleans are coded as integers (False = 0, True = 1)
|
||||
(Type::IntLiteral(n), Type::BooleanLiteral(b)) => self.infer_binary_type_comparison(
|
||||
(Type::IntLiteral(n), Type::BooleanLiteral(b)) => {
|
||||
Some(self.infer_binary_type_comparison(
|
||||
Type::IntLiteral(n),
|
||||
op,
|
||||
Type::IntLiteral(i64::from(b)),
|
||||
range,
|
||||
),
|
||||
(Type::BooleanLiteral(b), Type::IntLiteral(m)) => self.infer_binary_type_comparison(
|
||||
))
|
||||
}
|
||||
(Type::BooleanLiteral(b), Type::IntLiteral(m)) => {
|
||||
Some(self.infer_binary_type_comparison(
|
||||
Type::IntLiteral(i64::from(b)),
|
||||
op,
|
||||
Type::IntLiteral(m),
|
||||
range,
|
||||
),
|
||||
(Type::BooleanLiteral(a), Type::BooleanLiteral(b)) => self
|
||||
.infer_binary_type_comparison(
|
||||
))
|
||||
}
|
||||
(Type::BooleanLiteral(a), Type::BooleanLiteral(b)) => {
|
||||
Some(self.infer_binary_type_comparison(
|
||||
Type::IntLiteral(i64::from(a)),
|
||||
op,
|
||||
Type::IntLiteral(i64::from(b)),
|
||||
range,
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
(Type::StringLiteral(salsa_s1), Type::StringLiteral(salsa_s2)) => {
|
||||
let s1 = salsa_s1.value(self.db());
|
||||
let s2 = salsa_s2.value(self.db());
|
||||
match op {
|
||||
let result = match op {
|
||||
ast::CmpOp::Eq => Ok(Type::BooleanLiteral(s1 == s2)),
|
||||
ast::CmpOp::NotEq => Ok(Type::BooleanLiteral(s1 != s2)),
|
||||
ast::CmpOp::Lt => Ok(Type::BooleanLiteral(s1 < s2)),
|
||||
|
@ -8063,38 +8080,39 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
Ok(Type::BooleanLiteral(true))
|
||||
}
|
||||
}
|
||||
};
|
||||
Some(result)
|
||||
}
|
||||
}
|
||||
(Type::StringLiteral(_), _) => self.infer_binary_type_comparison(
|
||||
(Type::StringLiteral(_), _) => Some(self.infer_binary_type_comparison(
|
||||
KnownClass::Str.to_instance(self.db()),
|
||||
op,
|
||||
right,
|
||||
range,
|
||||
),
|
||||
(_, Type::StringLiteral(_)) => self.infer_binary_type_comparison(
|
||||
)),
|
||||
(_, Type::StringLiteral(_)) => Some(self.infer_binary_type_comparison(
|
||||
left,
|
||||
op,
|
||||
KnownClass::Str.to_instance(self.db()),
|
||||
range,
|
||||
),
|
||||
)),
|
||||
|
||||
(Type::LiteralString, _) => self.infer_binary_type_comparison(
|
||||
(Type::LiteralString, _) => Some(self.infer_binary_type_comparison(
|
||||
KnownClass::Str.to_instance(self.db()),
|
||||
op,
|
||||
right,
|
||||
range,
|
||||
),
|
||||
(_, Type::LiteralString) => self.infer_binary_type_comparison(
|
||||
)),
|
||||
(_, Type::LiteralString) => Some(self.infer_binary_type_comparison(
|
||||
left,
|
||||
op,
|
||||
KnownClass::Str.to_instance(self.db()),
|
||||
range,
|
||||
),
|
||||
)),
|
||||
|
||||
(Type::BytesLiteral(salsa_b1), Type::BytesLiteral(salsa_b2)) => {
|
||||
let b1 = salsa_b1.value(self.db());
|
||||
let b2 = salsa_b2.value(self.db());
|
||||
match op {
|
||||
let result = match op {
|
||||
ast::CmpOp::Eq => Ok(Type::BooleanLiteral(b1 == b2)),
|
||||
ast::CmpOp::NotEq => Ok(Type::BooleanLiteral(b1 != b2)),
|
||||
ast::CmpOp::Lt => Ok(Type::BooleanLiteral(b1 < b2)),
|
||||
|
@ -8121,58 +8139,41 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
Ok(Type::BooleanLiteral(true))
|
||||
}
|
||||
}
|
||||
};
|
||||
Some(result)
|
||||
}
|
||||
}
|
||||
(Type::BytesLiteral(_), _) => self.infer_binary_type_comparison(
|
||||
(Type::BytesLiteral(_), _) => Some(self.infer_binary_type_comparison(
|
||||
KnownClass::Bytes.to_instance(self.db()),
|
||||
op,
|
||||
right,
|
||||
range,
|
||||
),
|
||||
(_, Type::BytesLiteral(_)) => self.infer_binary_type_comparison(
|
||||
)),
|
||||
(_, Type::BytesLiteral(_)) => Some(self.infer_binary_type_comparison(
|
||||
left,
|
||||
op,
|
||||
KnownClass::Bytes.to_instance(self.db()),
|
||||
range,
|
||||
),
|
||||
)),
|
||||
|
||||
(Type::EnumLiteral(literal_1), Type::EnumLiteral(literal_2))
|
||||
if op == ast::CmpOp::Eq =>
|
||||
{
|
||||
Ok(Type::BooleanLiteral(literal_1 == literal_2))
|
||||
Some(Ok(Type::BooleanLiteral(literal_1 == literal_2)))
|
||||
}
|
||||
(Type::EnumLiteral(literal_1), Type::EnumLiteral(literal_2))
|
||||
if op == ast::CmpOp::NotEq =>
|
||||
{
|
||||
Ok(Type::BooleanLiteral(literal_1 != literal_2))
|
||||
Some(Ok(Type::BooleanLiteral(literal_1 != literal_2)))
|
||||
}
|
||||
|
||||
(Type::Tuple(_), Type::NominalInstance(instance))
|
||||
if instance.class.is_known(self.db(), KnownClass::VersionInfo) =>
|
||||
{
|
||||
self.infer_binary_type_comparison(
|
||||
left,
|
||||
op,
|
||||
Type::version_info_tuple(self.db()),
|
||||
range,
|
||||
)
|
||||
}
|
||||
(Type::NominalInstance(instance), Type::Tuple(_))
|
||||
if instance.class.is_known(self.db(), KnownClass::VersionInfo) =>
|
||||
{
|
||||
self.infer_binary_type_comparison(
|
||||
Type::version_info_tuple(self.db()),
|
||||
op,
|
||||
right,
|
||||
range,
|
||||
)
|
||||
}
|
||||
(Type::Tuple(lhs), Type::Tuple(rhs)) => {
|
||||
let lhs_tuple = lhs.tuple(self.db());
|
||||
let rhs_tuple = rhs.tuple(self.db());
|
||||
|
||||
(
|
||||
Type::NominalInstance(nominal1),
|
||||
Type::NominalInstance(nominal2),
|
||||
) => nominal1.tuple_spec(self.db())
|
||||
.and_then(|lhs_tuple| Some((lhs_tuple, nominal2.tuple_spec(self.db())?)))
|
||||
.map(|(lhs_tuple, rhs_tuple)| {
|
||||
let mut tuple_rich_comparison =
|
||||
|op| self.infer_tuple_rich_comparison(lhs_tuple, op, rhs_tuple, range);
|
||||
|op| self.infer_tuple_rich_comparison(&lhs_tuple, op, &rhs_tuple, range);
|
||||
|
||||
match op {
|
||||
ast::CmpOp::Eq => tuple_rich_comparison(RichCompareOperator::Eq),
|
||||
|
@ -8187,7 +8188,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
|
||||
for ty in rhs_tuple.all_elements().copied() {
|
||||
let eq_result = self.infer_binary_type_comparison(
|
||||
Type::Tuple(lhs),
|
||||
left,
|
||||
ast::CmpOp::Eq,
|
||||
ty,
|
||||
range,
|
||||
|
@ -8234,13 +8235,19 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
}
|
||||
}
|
||||
}
|
||||
),
|
||||
|
||||
// Lookup the rich comparison `__dunder__` methods
|
||||
_ => {
|
||||
let rich_comparison = |op| self.infer_rich_comparison(left, right, op);
|
||||
let membership_test_comparison = |op, range: TextRange| {
|
||||
self.infer_membership_test_comparison(left, right, op, range)
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(result) = comparison_result {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Final generalized fallback: lookup the rich comparison `__dunder__` methods
|
||||
let rich_comparison = |op| self.infer_rich_comparison(left, right, op);
|
||||
let membership_test_comparison =
|
||||
|op, range: TextRange| self.infer_membership_test_comparison(left, right, op, range);
|
||||
match op {
|
||||
ast::CmpOp::Eq => rich_comparison(RichCompareOperator::Eq),
|
||||
ast::CmpOp::NotEq => rich_comparison(RichCompareOperator::Ne),
|
||||
|
@ -8248,18 +8255,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
ast::CmpOp::LtE => rich_comparison(RichCompareOperator::Le),
|
||||
ast::CmpOp::Gt => rich_comparison(RichCompareOperator::Gt),
|
||||
ast::CmpOp::GtE => rich_comparison(RichCompareOperator::Ge),
|
||||
ast::CmpOp::In => {
|
||||
membership_test_comparison(MembershipTestCompareOperator::In, range)
|
||||
}
|
||||
ast::CmpOp::In => membership_test_comparison(MembershipTestCompareOperator::In, range),
|
||||
ast::CmpOp::NotIn => {
|
||||
membership_test_comparison(MembershipTestCompareOperator::NotIn, range)
|
||||
}
|
||||
ast::CmpOp::Is => {
|
||||
if left.is_disjoint_from(self.db(), right) {
|
||||
Ok(Type::BooleanLiteral(false))
|
||||
} else if left.is_singleton(self.db())
|
||||
&& left.is_equivalent_to(self.db(), right)
|
||||
{
|
||||
} else if left.is_singleton(self.db()) && left.is_equivalent_to(self.db(), right) {
|
||||
Ok(Type::BooleanLiteral(true))
|
||||
} else {
|
||||
Ok(KnownClass::Bool.to_instance(self.db()))
|
||||
|
@ -8268,9 +8271,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
ast::CmpOp::IsNot => {
|
||||
if left.is_disjoint_from(self.db(), right) {
|
||||
Ok(Type::BooleanLiteral(true))
|
||||
} else if left.is_singleton(self.db())
|
||||
&& left.is_equivalent_to(self.db(), right)
|
||||
{
|
||||
} else if left.is_singleton(self.db()) && left.is_equivalent_to(self.db(), right) {
|
||||
Ok(Type::BooleanLiteral(false))
|
||||
} else {
|
||||
Ok(KnownClass::Bool.to_instance(self.db()))
|
||||
|
@ -8278,8 +8279,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Rich comparison in Python are the operators `==`, `!=`, `<`, `<=`, `>`, and `>=`. Their
|
||||
/// behaviour can be edited for classes by implementing corresponding dunder methods.
|
||||
|
@ -8527,10 +8526,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
let tuple_generic_alias = |db: &'db dyn Db, tuple: Option<TupleType<'db>>| {
|
||||
let tuple =
|
||||
tuple.unwrap_or_else(|| TupleType::homogeneous(db, Type::unknown()).unwrap());
|
||||
tuple
|
||||
.to_class_type(db)
|
||||
.map(Type::from)
|
||||
.unwrap_or_else(Type::unknown)
|
||||
Type::from(tuple.to_class_type(db))
|
||||
};
|
||||
|
||||
// HACK ALERT: If we are subscripting a generic class, short-circuit the rest of the
|
||||
|
@ -8619,17 +8615,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
let value_node = subscript.value.as_ref();
|
||||
|
||||
let inferred = match (value_ty, slice_ty) {
|
||||
(Type::NominalInstance(instance), _)
|
||||
if instance.class.is_known(db, KnownClass::VersionInfo) =>
|
||||
{
|
||||
Some(self.infer_subscript_expression_types(
|
||||
subscript,
|
||||
Type::version_info_tuple(db),
|
||||
slice_ty,
|
||||
expr_context,
|
||||
))
|
||||
}
|
||||
|
||||
(Type::Union(union), _) => Some(union.map(db, |element| {
|
||||
self.infer_subscript_expression_types(subscript, *element, slice_ty, expr_context)
|
||||
})),
|
||||
|
@ -8643,9 +8628,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
}
|
||||
|
||||
// Ex) Given `("a", "b", "c", "d")[1]`, return `"b"`
|
||||
(Type::Tuple(tuple_ty), Type::IntLiteral(i64_int)) => {
|
||||
i32::try_from(i64_int).ok().map(|i32_int| {
|
||||
let tuple = tuple_ty.tuple(db);
|
||||
(Type::NominalInstance(nominal), Type::IntLiteral(i64_int)) => nominal
|
||||
.tuple_spec(db)
|
||||
.and_then(|tuple| Some((tuple, i32::try_from(i64_int).ok()?)))
|
||||
.map(|(tuple, i32_int)| {
|
||||
tuple.py_index(db, i32_int).unwrap_or_else(|_| {
|
||||
report_index_out_of_bounds(
|
||||
context,
|
||||
|
@ -8657,26 +8643,29 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
);
|
||||
Type::unknown()
|
||||
})
|
||||
})
|
||||
}
|
||||
}),
|
||||
|
||||
// Ex) Given `("a", 1, Null)[0:2]`, return `("a", 1)`
|
||||
(Type::Tuple(tuple_ty), Type::NominalInstance(NominalInstanceType { class, .. })) => {
|
||||
class
|
||||
.slice_literal(db)
|
||||
.map(|SliceLiteral { start, stop, step }| {
|
||||
let TupleSpec::Fixed(tuple) = tuple_ty.tuple(db) else {
|
||||
return todo_type!("slice into variable-length tuple");
|
||||
};
|
||||
|
||||
(
|
||||
Type::NominalInstance(maybe_tuple_nominal),
|
||||
Type::NominalInstance(maybe_slice_nominal),
|
||||
) => maybe_tuple_nominal
|
||||
.tuple_spec(db)
|
||||
.as_deref()
|
||||
.and_then(|tuple_spec| Some((tuple_spec, maybe_slice_nominal.slice_literal(db)?)))
|
||||
.map(|(tuple, SliceLiteral { start, stop, step })| match tuple {
|
||||
TupleSpec::Fixed(tuple) => {
|
||||
if let Ok(new_elements) = tuple.py_slice(db, start, stop, step) {
|
||||
Type::heterogeneous_tuple(db, new_elements)
|
||||
} else {
|
||||
report_slice_step_size_zero(context, value_node.into());
|
||||
Type::unknown()
|
||||
}
|
||||
})
|
||||
}
|
||||
TupleSpec::Variable(_) => {
|
||||
todo_type!("slice into variable-length tuple")
|
||||
}
|
||||
}),
|
||||
|
||||
// Ex) Given `"value"[1]`, return `"a"`
|
||||
(Type::StringLiteral(literal_ty), Type::IntLiteral(i64_int)) => {
|
||||
|
@ -8700,10 +8689,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
}
|
||||
|
||||
// Ex) Given `"value"[1:3]`, return `"al"`
|
||||
(
|
||||
Type::StringLiteral(literal_ty),
|
||||
Type::NominalInstance(NominalInstanceType { class, .. }),
|
||||
) => class
|
||||
(Type::StringLiteral(literal_ty), Type::NominalInstance(nominal)) => nominal
|
||||
.slice_literal(db)
|
||||
.map(|SliceLiteral { start, stop, step }| {
|
||||
let literal_value = literal_ty.value(db);
|
||||
|
@ -8740,10 +8726,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
}
|
||||
|
||||
// Ex) Given `b"value"[1:3]`, return `b"al"`
|
||||
(
|
||||
Type::BytesLiteral(literal_ty),
|
||||
Type::NominalInstance(NominalInstanceType { class, .. }),
|
||||
) => class
|
||||
(Type::BytesLiteral(literal_ty), Type::NominalInstance(nominal)) => nominal
|
||||
.slice_literal(db)
|
||||
.map(|SliceLiteral { start, stop, step }| {
|
||||
let literal_value = literal_ty.value(db);
|
||||
|
@ -8758,37 +8741,30 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
}),
|
||||
|
||||
// Ex) Given `"value"[True]`, return `"a"`
|
||||
(
|
||||
Type::Tuple(_) | Type::StringLiteral(_) | Type::BytesLiteral(_),
|
||||
Type::BooleanLiteral(bool),
|
||||
) => Some(self.infer_subscript_expression_types(
|
||||
(Type::StringLiteral(_) | Type::BytesLiteral(_), Type::BooleanLiteral(bool)) => {
|
||||
Some(self.infer_subscript_expression_types(
|
||||
subscript,
|
||||
value_ty,
|
||||
Type::IntLiteral(i64::from(bool)),
|
||||
expr_context,
|
||||
)),
|
||||
|
||||
(Type::SpecialForm(SpecialFormType::Protocol), Type::Tuple(typevars)) => {
|
||||
Some(match typevars.tuple(db) {
|
||||
TupleSpec::Fixed(typevars) => self
|
||||
.legacy_generic_class_context(
|
||||
value_node,
|
||||
typevars.elements_slice(),
|
||||
LegacyGenericBase::Protocol,
|
||||
)
|
||||
.map(|context| {
|
||||
Type::KnownInstance(KnownInstanceType::SubscriptedProtocol(context))
|
||||
})
|
||||
.unwrap_or_else(GenericContextError::into_type),
|
||||
// TODO: emit a diagnostic
|
||||
TupleSpec::Variable(_) => Type::unknown(),
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
(Type::SpecialForm(SpecialFormType::Protocol), typevar) => Some(
|
||||
(Type::NominalInstance(nominal), Type::BooleanLiteral(bool))
|
||||
if nominal.tuple_spec(db).is_some() =>
|
||||
{
|
||||
Some(self.infer_subscript_expression_types(
|
||||
subscript,
|
||||
value_ty,
|
||||
Type::IntLiteral(i64::from(bool)),
|
||||
expr_context,
|
||||
))
|
||||
}
|
||||
|
||||
(Type::SpecialForm(SpecialFormType::Protocol), typevars) => Some(
|
||||
self.legacy_generic_class_context(
|
||||
value_node,
|
||||
std::slice::from_ref(&typevar),
|
||||
typevars,
|
||||
LegacyGenericBase::Protocol,
|
||||
)
|
||||
.map(|context| Type::KnownInstance(KnownInstanceType::SubscriptedProtocol(context)))
|
||||
|
@ -8800,31 +8776,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
Some(todo_type!("doubly-specialized typing.Protocol"))
|
||||
}
|
||||
|
||||
(Type::SpecialForm(SpecialFormType::Generic), Type::Tuple(typevars)) => {
|
||||
Some(match typevars.tuple(db) {
|
||||
TupleSpec::Fixed(typevars) => self
|
||||
.legacy_generic_class_context(
|
||||
value_node,
|
||||
typevars.elements_slice(),
|
||||
LegacyGenericBase::Generic,
|
||||
)
|
||||
(Type::SpecialForm(SpecialFormType::Generic), typevars) => Some(
|
||||
self.legacy_generic_class_context(value_node, typevars, LegacyGenericBase::Generic)
|
||||
.map(|context| {
|
||||
Type::KnownInstance(KnownInstanceType::SubscriptedGeneric(context))
|
||||
})
|
||||
.unwrap_or_else(GenericContextError::into_type),
|
||||
// TODO: emit a diagnostic
|
||||
TupleSpec::Variable(_) => Type::unknown(),
|
||||
})
|
||||
}
|
||||
|
||||
(Type::SpecialForm(SpecialFormType::Generic), typevar) => Some(
|
||||
self.legacy_generic_class_context(
|
||||
value_node,
|
||||
std::slice::from_ref(&typevar),
|
||||
LegacyGenericBase::Generic,
|
||||
)
|
||||
.map(|context| Type::KnownInstance(KnownInstanceType::SubscriptedGeneric(context)))
|
||||
.unwrap_or_else(GenericContextError::into_type),
|
||||
),
|
||||
|
||||
(Type::KnownInstance(KnownInstanceType::SubscriptedGeneric(_)), _) => {
|
||||
|
@ -9010,9 +8967,21 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
fn legacy_generic_class_context(
|
||||
&self,
|
||||
value_node: &ast::Expr,
|
||||
typevars: &[Type<'db>],
|
||||
typevars: Type<'db>,
|
||||
origin: LegacyGenericBase,
|
||||
) -> Result<GenericContext<'db>, GenericContextError> {
|
||||
let typevars_class_tuple_spec = typevars.exact_tuple_instance_spec(self.db());
|
||||
|
||||
let typevars = if let Some(tuple_spec) = typevars_class_tuple_spec.as_deref() {
|
||||
match tuple_spec {
|
||||
Tuple::Fixed(typevars) => typevars.elements_slice(),
|
||||
// TODO: emit a diagnostic
|
||||
Tuple::Variable(_) => return Err(GenericContextError::VariadicTupleArguments),
|
||||
}
|
||||
} else {
|
||||
std::slice::from_ref(&typevars)
|
||||
};
|
||||
|
||||
let typevars: Result<FxOrderSet<_>, GenericContextError> = typevars
|
||||
.iter()
|
||||
.map(|typevar| match typevar {
|
||||
|
@ -9026,9 +8995,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
)
|
||||
.ok_or(GenericContextError::InvalidArgument),
|
||||
Type::Dynamic(DynamicType::TodoUnpack) => Err(GenericContextError::NotYetSupported),
|
||||
Type::NominalInstance(NominalInstanceType { class, .. })
|
||||
Type::NominalInstance(nominal)
|
||||
if matches!(
|
||||
class.known(self.db()),
|
||||
nominal.class(self.db()).known(self.db()),
|
||||
Some(KnownClass::TypeVarTuple | KnownClass::ParamSpec)
|
||||
) =>
|
||||
{
|
||||
|
@ -9071,7 +9040,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
let type_to_slice_argument = |ty: Option<Type<'db>>| match ty {
|
||||
Some(ty @ (Type::IntLiteral(_) | Type::BooleanLiteral(_))) => SliceArg::Arg(ty),
|
||||
Some(ty @ Type::NominalInstance(instance))
|
||||
if instance.class.is_known(self.db(), KnownClass::NoneType) =>
|
||||
if instance
|
||||
.class(self.db())
|
||||
.is_known(self.db(), KnownClass::NoneType) =>
|
||||
{
|
||||
SliceArg::Arg(ty)
|
||||
}
|
||||
|
@ -9984,7 +9955,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
|||
} = starred;
|
||||
|
||||
let starred_type = self.infer_type_expression(value);
|
||||
if let Type::Tuple(_) = starred_type {
|
||||
if starred_type.exact_tuple_instance_spec(self.db()).is_some() {
|
||||
starred_type
|
||||
} else {
|
||||
todo_type!("PEP 646")
|
||||
|
@ -10042,7 +10013,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
|||
}
|
||||
|
||||
match element {
|
||||
ast::Expr::Starred(_) => !matches!(element_ty, Type::Tuple(_)),
|
||||
ast::Expr::Starred(_) => {
|
||||
element_ty.exact_tuple_instance_spec(builder.db()).is_none()
|
||||
}
|
||||
ast::Expr::Subscript(ast::ExprSubscript { value, .. }) => {
|
||||
let value_ty = if builder.deferred_state.in_string_annotation() {
|
||||
// Using `.expression_type` does not work in string annotations, because
|
||||
|
@ -10079,10 +10052,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
|||
let element_ty = self.infer_type_expression(element);
|
||||
return_todo |=
|
||||
element_could_alter_type_of_whole_tuple(element, element_ty, self);
|
||||
|
||||
if let ast::Expr::Starred(_) = element {
|
||||
if let Type::Tuple(inner_tuple) = element_ty {
|
||||
element_types =
|
||||
element_types.concat(self.db(), inner_tuple.tuple(self.db()));
|
||||
if let Some(inner_tuple) = element_ty.exact_tuple_instance_spec(self.db()) {
|
||||
element_types = element_types.concat(self.db(), &inner_tuple);
|
||||
} else {
|
||||
// TODO: emit a diagnostic
|
||||
}
|
||||
|
@ -10941,8 +10914,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
|||
Type::Dynamic(DynamicType::TodoPEP695ParamSpec) => {
|
||||
return Some(Parameters::todo());
|
||||
}
|
||||
Type::NominalInstance(NominalInstanceType { class, .. })
|
||||
if class.is_known(self.db(), KnownClass::ParamSpec) =>
|
||||
Type::NominalInstance(nominal)
|
||||
if nominal
|
||||
.class(self.db())
|
||||
.is_known(self.db(), KnownClass::ParamSpec) =>
|
||||
{
|
||||
return Some(Parameters::todo());
|
||||
}
|
||||
|
@ -10966,6 +10941,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
|||
enum GenericContextError {
|
||||
/// It's invalid to subscript `Generic` or `Protocol` with this type
|
||||
InvalidArgument,
|
||||
/// It's invalid to subscript `Generic` or `Protocol` with a variadic tuple type.
|
||||
/// We should emit a diagnostic for this, but we don't yet.
|
||||
VariadicTupleArguments,
|
||||
/// It's valid to subscribe `Generic` or `Protocol` with this type,
|
||||
/// but the type is not yet supported.
|
||||
NotYetSupported,
|
||||
|
@ -10974,7 +10952,9 @@ enum GenericContextError {
|
|||
impl GenericContextError {
|
||||
const fn into_type<'db>(self) -> Type<'db> {
|
||||
match self {
|
||||
GenericContextError::InvalidArgument => Type::unknown(),
|
||||
GenericContextError::InvalidArgument | GenericContextError::VariadicTupleArguments => {
|
||||
Type::unknown()
|
||||
}
|
||||
GenericContextError::NotYetSupported => todo_type!("ParamSpecs and TypeVarTuples"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
//! Instance types: both nominal and structural.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use super::protocol_class::ProtocolInterface;
|
||||
|
@ -9,33 +10,46 @@ use crate::semantic_index::definition::Definition;
|
|||
use crate::types::cyclic::PairVisitor;
|
||||
use crate::types::enums::is_single_member_enum;
|
||||
use crate::types::protocol_class::walk_protocol_interface;
|
||||
use crate::types::tuple::TupleType;
|
||||
use crate::types::{DynamicType, TypeMapping, TypeRelation, TypeTransformer, TypedDictType};
|
||||
use crate::{Db, FxOrderSet};
|
||||
use crate::types::tuple::{TupleSpec, TupleType};
|
||||
use crate::types::{
|
||||
ClassBase, DynamicType, TypeMapping, TypeRelation, TypeTransformer, TypedDictType, UnionType,
|
||||
};
|
||||
use crate::{Db, FxOrderSet, Program};
|
||||
|
||||
pub(super) use synthesized_protocol::SynthesizedProtocolType;
|
||||
|
||||
impl<'db> Type<'db> {
|
||||
pub(crate) fn instance(db: &'db dyn Db, class: ClassType<'db>) -> Self {
|
||||
match (class, class.known(db)) {
|
||||
(_, Some(KnownClass::Any)) => Self::Dynamic(DynamicType::Any),
|
||||
(ClassType::NonGeneric(_), Some(KnownClass::Tuple)) => {
|
||||
Type::tuple(TupleType::homogeneous(db, Type::unknown()))
|
||||
}
|
||||
(ClassType::Generic(alias), Some(KnownClass::Tuple)) => {
|
||||
Self::tuple(TupleType::new(db, alias.specialization(db).tuple(db)))
|
||||
}
|
||||
_ => {
|
||||
let class_literal = class.class_literal(db).0;
|
||||
if class_literal.is_protocol(db) {
|
||||
let (class_literal, specialization) = class.class_literal(db);
|
||||
|
||||
match class_literal.known(db) {
|
||||
Some(KnownClass::Any) => Type::Dynamic(DynamicType::Any),
|
||||
Some(KnownClass::Tuple) => Type::tuple(TupleType::new(
|
||||
db,
|
||||
specialization
|
||||
.and_then(|spec| Some(Cow::Borrowed(spec.tuple(db)?)))
|
||||
.unwrap_or_else(|| Cow::Owned(TupleSpec::homogeneous(Type::unknown())))
|
||||
.as_ref(),
|
||||
)),
|
||||
_ if class_literal.is_protocol(db) => {
|
||||
Self::ProtocolInstance(ProtocolInstanceType::from_class(class))
|
||||
} else if class_literal.is_typed_dict(db) {
|
||||
TypedDictType::from(db, class)
|
||||
} else {
|
||||
Self::NominalInstance(NominalInstanceType::from_class(class))
|
||||
}
|
||||
_ if class_literal.is_typed_dict(db) => TypedDictType::from(db, class),
|
||||
_ => Type::non_tuple_instance(class),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn tuple(tuple: Option<TupleType<'db>>) -> Self {
|
||||
let Some(tuple) = tuple else {
|
||||
return Type::Never;
|
||||
};
|
||||
Type::NominalInstance(NominalInstanceType(NominalInstanceInner::ExactTuple(tuple)))
|
||||
}
|
||||
|
||||
/// **Private** helper function to create a `Type::NominalInstance` from a class that
|
||||
/// is known not to be `Any`, a protocol class, or a typed dict class.
|
||||
fn non_tuple_instance(class: ClassType<'db>) -> Self {
|
||||
Type::NominalInstance(NominalInstanceType(NominalInstanceInner::NonTuple(class)))
|
||||
}
|
||||
|
||||
pub(crate) const fn into_nominal_instance(self) -> Option<NominalInstanceType<'db>> {
|
||||
|
@ -76,42 +90,176 @@ impl<'db> Type<'db> {
|
|||
|
||||
/// A type representing the set of runtime objects which are instances of a certain nominal class.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, salsa::Update, get_size2::GetSize)]
|
||||
pub struct NominalInstanceType<'db> {
|
||||
pub(super) class: ClassType<'db>,
|
||||
|
||||
pub struct NominalInstanceType<'db>(
|
||||
// Keep this field private, so that the only way of constructing `NominalInstanceType` instances
|
||||
// is through the `Type::instance` constructor function.
|
||||
_phantom: PhantomData<()>,
|
||||
}
|
||||
NominalInstanceInner<'db>,
|
||||
);
|
||||
|
||||
pub(super) fn walk_nominal_instance_type<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>(
|
||||
db: &'db dyn Db,
|
||||
nominal: NominalInstanceType<'db>,
|
||||
visitor: &mut V,
|
||||
) {
|
||||
visitor.visit_type(db, nominal.class.into());
|
||||
visitor.visit_type(db, nominal.class(db).into());
|
||||
}
|
||||
|
||||
impl<'db> NominalInstanceType<'db> {
|
||||
// Keep this method private, so that the only way of constructing `NominalInstanceType`
|
||||
// instances is through the `Type::instance` constructor function.
|
||||
fn from_class(class: ClassType<'db>) -> Self {
|
||||
Self {
|
||||
class,
|
||||
_phantom: PhantomData,
|
||||
pub(super) fn class(&self, db: &'db dyn Db) -> ClassType<'db> {
|
||||
match self.0 {
|
||||
NominalInstanceInner::ExactTuple(tuple) => tuple.to_class_type(db),
|
||||
NominalInstanceInner::NonTuple(class) => class,
|
||||
}
|
||||
}
|
||||
|
||||
/// If this is an instance type where the class has a tuple spec, returns the tuple spec.
|
||||
///
|
||||
/// I.e., for the type `tuple[int, str]`, this will return the tuple spec `[int, str]`.
|
||||
/// For a subclass of `tuple[int, str]`, it will return the same tuple spec.
|
||||
pub(super) fn tuple_spec(&self, db: &'db dyn Db) -> Option<Cow<'db, TupleSpec<'db>>> {
|
||||
fn own_tuple_spec_of_class<'db>(
|
||||
db: &'db dyn Db,
|
||||
class: ClassType<'db>,
|
||||
) -> Option<Cow<'db, TupleSpec<'db>>> {
|
||||
let (class_literal, specialization) = class.class_literal(db);
|
||||
match class_literal.known(db)? {
|
||||
KnownClass::Tuple => Some(
|
||||
specialization
|
||||
.and_then(|spec| Some(Cow::Borrowed(spec.tuple(db)?)))
|
||||
.unwrap_or_else(|| Cow::Owned(TupleSpec::homogeneous(Type::unknown()))),
|
||||
),
|
||||
KnownClass::VersionInfo => {
|
||||
let python_version = Program::get(db).python_version(db);
|
||||
let int_instance_ty = KnownClass::Int.to_instance(db);
|
||||
|
||||
// TODO: just grab this type from typeshed (it's a `sys._ReleaseLevel` type alias there)
|
||||
let release_level_ty = {
|
||||
let elements: Box<[Type<'db>]> = ["alpha", "beta", "candidate", "final"]
|
||||
.iter()
|
||||
.map(|level| Type::string_literal(db, level))
|
||||
.collect();
|
||||
|
||||
// For most unions, it's better to go via `UnionType::from_elements` or use `UnionBuilder`;
|
||||
// those techniques ensure that union elements are deduplicated and unions are eagerly simplified
|
||||
// into other types where necessary. Here, however, we know that there are no duplicates
|
||||
// in this union, so it's probably more efficient to use `UnionType::new()` directly.
|
||||
Type::Union(UnionType::new(db, elements))
|
||||
};
|
||||
|
||||
Some(Cow::Owned(TupleSpec::from_elements([
|
||||
Type::IntLiteral(python_version.major.into()),
|
||||
Type::IntLiteral(python_version.minor.into()),
|
||||
int_instance_ty,
|
||||
release_level_ty,
|
||||
int_instance_ty,
|
||||
])))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
match self.0 {
|
||||
NominalInstanceInner::ExactTuple(tuple) => Some(Cow::Borrowed(tuple.tuple(db))),
|
||||
NominalInstanceInner::NonTuple(class) => {
|
||||
// Avoid an expensive MRO traversal for common stdlib classes.
|
||||
if class
|
||||
.known(db)
|
||||
.is_some_and(|known_class| !known_class.is_tuple_subclass())
|
||||
{
|
||||
return None;
|
||||
}
|
||||
class
|
||||
.iter_mro(db)
|
||||
.filter_map(ClassBase::into_class)
|
||||
.find_map(|class| own_tuple_spec_of_class(db, class))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if this type represents instances of the class `builtins.object`.
|
||||
pub(super) fn is_object(self, db: &'db dyn Db) -> bool {
|
||||
match self.0 {
|
||||
NominalInstanceInner::ExactTuple(_) => false,
|
||||
NominalInstanceInner::NonTuple(class) => class.is_object(db),
|
||||
}
|
||||
}
|
||||
|
||||
/// If this type is an *exact* tuple type (*not* a subclass of `tuple`), returns the
|
||||
/// tuple spec.
|
||||
///
|
||||
/// You usually don't want to use this method, as you usually want to consider a subclass
|
||||
/// of a tuple type in the same way as the `tuple` type itself. Only use this method if you
|
||||
/// are certain that a *literal tuple* is required, and that a subclass of tuple will not
|
||||
/// do.
|
||||
///
|
||||
/// I.e., for the type `tuple[int, str]`, this will return the tuple spec `[int, str]`.
|
||||
/// But for a subclass of `tuple[int, str]`, it will return `None`.
|
||||
pub(super) fn own_tuple_spec(&self, db: &'db dyn Db) -> Option<Cow<'db, TupleSpec<'db>>> {
|
||||
match self.0 {
|
||||
NominalInstanceInner::ExactTuple(tuple) => Some(Cow::Borrowed(tuple.tuple(db))),
|
||||
NominalInstanceInner::NonTuple(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// If this is a specialized instance of `slice`, returns a [`SliceLiteral`] describing it.
|
||||
/// Otherwise returns `None`.
|
||||
///
|
||||
/// The specialization must be one in which the typevars are solved as being statically known
|
||||
/// integers or `None`.
|
||||
pub(crate) fn slice_literal(self, db: &'db dyn Db) -> Option<SliceLiteral> {
|
||||
let class = match self.0 {
|
||||
NominalInstanceInner::ExactTuple(_) => return None,
|
||||
NominalInstanceInner::NonTuple(class) => class,
|
||||
};
|
||||
let (class, Some(specialization)) = class.class_literal(db) else {
|
||||
return None;
|
||||
};
|
||||
if !class.is_known(db, KnownClass::Slice) {
|
||||
return None;
|
||||
}
|
||||
let [start, stop, step] = specialization.types(db) else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let to_u32 = |ty: &Type<'db>| match ty {
|
||||
Type::IntLiteral(n) => i32::try_from(*n).map(Some).ok(),
|
||||
Type::BooleanLiteral(b) => Some(Some(i32::from(*b))),
|
||||
Type::NominalInstance(instance)
|
||||
if instance.class(db).is_known(db, KnownClass::NoneType) =>
|
||||
{
|
||||
Some(None)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
Some(SliceLiteral {
|
||||
start: to_u32(start)?,
|
||||
stop: to_u32(stop)?,
|
||||
step: to_u32(step)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn normalized_impl(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
visitor: &mut TypeTransformer<'db>,
|
||||
) -> Self {
|
||||
Self::from_class(self.class.normalized_impl(db, visitor))
|
||||
) -> Type<'db> {
|
||||
match self.0 {
|
||||
NominalInstanceInner::ExactTuple(tuple) => {
|
||||
Type::tuple(tuple.normalized_impl(db, visitor))
|
||||
}
|
||||
NominalInstanceInner::NonTuple(class) => {
|
||||
Type::non_tuple_instance(class.normalized_impl(db, visitor))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
|
||||
Self::from_class(self.class.materialize(db, variance))
|
||||
pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Type<'db> {
|
||||
match self.0 {
|
||||
NominalInstanceInner::ExactTuple(tuple) => Type::tuple(tuple.materialize(db, variance)),
|
||||
NominalInstanceInner::NonTuple(class) => {
|
||||
Type::non_tuple_instance(class.materialize(db, variance))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn has_relation_to(
|
||||
|
@ -120,41 +268,93 @@ impl<'db> NominalInstanceType<'db> {
|
|||
other: Self,
|
||||
relation: TypeRelation,
|
||||
) -> bool {
|
||||
self.class.has_relation_to(db, other.class, relation)
|
||||
match (self.0, other.0) {
|
||||
(
|
||||
NominalInstanceInner::ExactTuple(tuple1),
|
||||
NominalInstanceInner::ExactTuple(tuple2),
|
||||
) => tuple1.has_relation_to(db, tuple2, relation),
|
||||
_ => self
|
||||
.class(db)
|
||||
.has_relation_to(db, other.class(db), relation),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool {
|
||||
self.class.is_equivalent_to(db, other.class)
|
||||
match (self.0, other.0) {
|
||||
(
|
||||
NominalInstanceInner::ExactTuple(tuple1),
|
||||
NominalInstanceInner::ExactTuple(tuple2),
|
||||
) => tuple1.is_equivalent_to(db, tuple2),
|
||||
(NominalInstanceInner::NonTuple(class1), NominalInstanceInner::NonTuple(class2)) => {
|
||||
class1.is_equivalent_to(db, class2)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn is_disjoint_from_impl(self, db: &'db dyn Db, other: Self) -> bool {
|
||||
!self.class.could_coexist_in_mro_with(db, other.class)
|
||||
pub(super) fn is_disjoint_from_impl(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
other: Self,
|
||||
visitor: &mut PairVisitor<'db>,
|
||||
) -> bool {
|
||||
let self_spec = self.tuple_spec(db);
|
||||
if let Some(self_spec) = self_spec.as_deref() {
|
||||
let other_spec = other.tuple_spec(db);
|
||||
if let Some(other_spec) = other_spec.as_deref() {
|
||||
if self_spec.is_disjoint_from_impl(db, other_spec, visitor) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
!self
|
||||
.class(db)
|
||||
.could_coexist_in_mro_with(db, other.class(db))
|
||||
}
|
||||
|
||||
pub(super) fn is_singleton(self, db: &'db dyn Db) -> bool {
|
||||
self.class
|
||||
match self.0 {
|
||||
// The empty tuple is a singleton on CPython and PyPy, but not on other Python
|
||||
// implementations such as GraalPy. Its *use* as a singleton is discouraged and
|
||||
// should not be relied on for type narrowing, so we do not treat it as one.
|
||||
// See:
|
||||
// https://docs.python.org/3/reference/expressions.html#parenthesized-forms
|
||||
NominalInstanceInner::ExactTuple(_) => false,
|
||||
NominalInstanceInner::NonTuple(class) => class
|
||||
.known(db)
|
||||
.map(KnownClass::is_singleton)
|
||||
.unwrap_or_else(|| is_single_member_enum(db, self.class.class_literal(db).0))
|
||||
.unwrap_or_else(|| is_single_member_enum(db, class.class_literal(db).0)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn is_single_valued(self, db: &'db dyn Db) -> bool {
|
||||
self.class
|
||||
match self.0 {
|
||||
NominalInstanceInner::ExactTuple(tuple) => tuple.is_single_valued(db),
|
||||
NominalInstanceInner::NonTuple(class) => class
|
||||
.known(db)
|
||||
.map(KnownClass::is_single_valued)
|
||||
.unwrap_or_else(|| is_single_member_enum(db, self.class.class_literal(db).0))
|
||||
.and_then(KnownClass::is_single_valued)
|
||||
.or_else(|| Some(self.tuple_spec(db)?.is_single_valued(db)))
|
||||
.unwrap_or_else(|| is_single_member_enum(db, class.class_literal(db).0)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn to_meta_type(self, db: &'db dyn Db) -> Type<'db> {
|
||||
SubclassOfType::from(db, self.class)
|
||||
SubclassOfType::from(db, self.class(db))
|
||||
}
|
||||
|
||||
pub(super) fn apply_type_mapping<'a>(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
type_mapping: &TypeMapping<'a, 'db>,
|
||||
) -> Self {
|
||||
Self::from_class(self.class.apply_type_mapping(db, type_mapping))
|
||||
) -> Type<'db> {
|
||||
match self.0 {
|
||||
NominalInstanceInner::ExactTuple(tuple) => {
|
||||
Type::tuple(tuple.apply_type_mapping(db, type_mapping))
|
||||
}
|
||||
NominalInstanceInner::NonTuple(class) => {
|
||||
Type::non_tuple_instance(class.apply_type_mapping(db, type_mapping))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn find_legacy_typevars(
|
||||
|
@ -163,8 +363,14 @@ impl<'db> NominalInstanceType<'db> {
|
|||
binding_context: Option<Definition<'db>>,
|
||||
typevars: &mut FxOrderSet<BoundTypeVarInstance<'db>>,
|
||||
) {
|
||||
self.class
|
||||
.find_legacy_typevars(db, binding_context, typevars);
|
||||
match self.0 {
|
||||
NominalInstanceInner::ExactTuple(tuple) => {
|
||||
tuple.find_legacy_typevars(db, binding_context, typevars);
|
||||
}
|
||||
NominalInstanceInner::NonTuple(class) => {
|
||||
class.find_legacy_typevars(db, binding_context, typevars);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -174,6 +380,30 @@ impl<'db> From<NominalInstanceType<'db>> for Type<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
/// [`NominalInstanceType`] is split into two variants internally as a pure
|
||||
/// optimization to avoid having to materialize the [`ClassType`] for tuple
|
||||
/// instances where it would be unnecessary (this is somewhat expensive!).
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, salsa::Update, get_size2::GetSize)]
|
||||
enum NominalInstanceInner<'db> {
|
||||
/// A tuple type, e.g. `tuple[int, str]`.
|
||||
///
|
||||
/// Note that the type `tuple[int, str]` includes subtypes of `tuple[int, str]`,
|
||||
/// but those subtypes would be represented using the `NonTuple` variant.
|
||||
ExactTuple(TupleType<'db>),
|
||||
/// Any instance type that does not represent some kind of instance of the
|
||||
/// builtin `tuple` class.
|
||||
///
|
||||
/// This variant includes types that are subtypes of "exact tuple" types,
|
||||
/// because they represent "all instances of a class that is a tuple subclass".
|
||||
NonTuple(ClassType<'db>),
|
||||
}
|
||||
|
||||
pub(crate) struct SliceLiteral {
|
||||
pub(crate) start: Option<i32>,
|
||||
pub(crate) stop: Option<i32>,
|
||||
pub(crate) step: Option<i32>,
|
||||
}
|
||||
|
||||
/// A `ProtocolInstanceType` represents the set of all possible runtime objects
|
||||
/// that conform to the interface described by a certain protocol.
|
||||
#[derive(
|
||||
|
@ -253,7 +483,7 @@ impl<'db> ProtocolInstanceType<'db> {
|
|||
db: &'db dyn Db,
|
||||
visitor: &mut TypeTransformer<'db>,
|
||||
) -> Type<'db> {
|
||||
let object = KnownClass::Object.to_instance(db);
|
||||
let object = Type::object(db);
|
||||
if object.satisfies_protocol(db, self, TypeRelation::Subtyping) {
|
||||
return object;
|
||||
}
|
||||
|
|
|
@ -182,14 +182,6 @@ impl ClassInfoConstraintFunction {
|
|||
};
|
||||
|
||||
match classinfo {
|
||||
Type::Tuple(tuple) => UnionType::try_from_elements(
|
||||
db,
|
||||
tuple
|
||||
.tuple(db)
|
||||
.all_elements()
|
||||
.copied()
|
||||
.map(|element| self.generate_constraint(db, element)),
|
||||
),
|
||||
Type::ClassLiteral(class_literal) => {
|
||||
// At runtime (on Python 3.11+), this will return `True` for classes that actually
|
||||
// do inherit `typing.Any` and `False` otherwise. We could accurately model that?
|
||||
|
@ -236,6 +228,15 @@ impl ClassInfoConstraintFunction {
|
|||
// e.g. `isinstance(x, list[int])` fails at runtime.
|
||||
Type::GenericAlias(_) => None,
|
||||
|
||||
Type::NominalInstance(nominal) => nominal.tuple_spec(db).and_then(|tuple| {
|
||||
UnionType::try_from_elements(
|
||||
db,
|
||||
tuple
|
||||
.all_elements()
|
||||
.map(|element| self.generate_constraint(db, *element)),
|
||||
)
|
||||
}),
|
||||
|
||||
Type::AlwaysFalsy
|
||||
| Type::AlwaysTruthy
|
||||
| Type::BooleanLiteral(_)
|
||||
|
@ -252,7 +253,6 @@ impl ClassInfoConstraintFunction {
|
|||
| Type::ProtocolInstance(_)
|
||||
| Type::PropertyInstance(_)
|
||||
| Type::SpecialForm(_)
|
||||
| Type::NominalInstance(_)
|
||||
| Type::LiteralString
|
||||
| Type::StringLiteral(_)
|
||||
| Type::IntLiteral(_)
|
||||
|
@ -560,7 +560,7 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
|||
}
|
||||
// Treat `bool` as `Literal[True, False]`.
|
||||
Type::NominalInstance(instance)
|
||||
if instance.class.is_known(db, KnownClass::Bool) =>
|
||||
if instance.class(db).is_known(db, KnownClass::Bool) =>
|
||||
{
|
||||
UnionType::from_elements(
|
||||
db,
|
||||
|
@ -571,11 +571,11 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
|||
}
|
||||
// Treat enums as a union of their members.
|
||||
Type::NominalInstance(instance)
|
||||
if enum_metadata(db, instance.class.class_literal(db).0).is_some() =>
|
||||
if enum_metadata(db, instance.class(db).class_literal(db).0).is_some() =>
|
||||
{
|
||||
UnionType::from_elements(
|
||||
db,
|
||||
enum_member_literals(db, instance.class.class_literal(db).0, None)
|
||||
enum_member_literals(db, instance.class(db).class_literal(db).0, None)
|
||||
.expect("Calling `enum_member_literals` on an enum class")
|
||||
.map(|ty| filter_to_cannot_be_equal(db, ty, rhs_ty)),
|
||||
)
|
||||
|
@ -602,7 +602,7 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
|||
fn evaluate_expr_ne(&mut self, lhs_ty: Type<'db>, rhs_ty: Type<'db>) -> Option<Type<'db>> {
|
||||
match (lhs_ty, rhs_ty) {
|
||||
(Type::NominalInstance(instance), Type::IntLiteral(i))
|
||||
if instance.class.is_known(self.db, KnownClass::Bool) =>
|
||||
if instance.class(self.db).is_known(self.db, KnownClass::Bool) =>
|
||||
{
|
||||
if i == 0 {
|
||||
Some(Type::BooleanLiteral(false).negate(self.db))
|
||||
|
@ -623,20 +623,21 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
|
|||
|
||||
fn evaluate_expr_in(&mut self, lhs_ty: Type<'db>, rhs_ty: Type<'db>) -> Option<Type<'db>> {
|
||||
if lhs_ty.is_single_valued(self.db) || lhs_ty.is_union_of_single_valued(self.db) {
|
||||
match rhs_ty {
|
||||
Type::Tuple(rhs_tuple) => Some(UnionType::from_elements(
|
||||
self.db,
|
||||
rhs_tuple.tuple(self.db).all_elements(),
|
||||
)),
|
||||
|
||||
Type::StringLiteral(string_literal) => Some(UnionType::from_elements(
|
||||
if let Type::StringLiteral(string_literal) = rhs_ty {
|
||||
Some(UnionType::from_elements(
|
||||
self.db,
|
||||
string_literal
|
||||
.iter_each_char(self.db)
|
||||
.map(Type::StringLiteral),
|
||||
)),
|
||||
|
||||
_ => None,
|
||||
))
|
||||
} else if let Some(tuple_spec) = rhs_ty.tuple_instance_spec(self.db) {
|
||||
// N.B. Strictly speaking this is unsound, since a tuple subclass might override `__contains__`
|
||||
// but we'd still apply the narrowing here. This seems unlikely, however, and narrowing is
|
||||
// generally unsound in numerous ways anyway (attribute narrowing, subscript, narrowing,
|
||||
// narrowing of globals, etc.). So this doesn't seem worth worrying about too much.
|
||||
Some(UnionType::from_elements(self.db, tuple_spec.all_elements()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
|
|
|
@ -153,7 +153,7 @@ impl Ty {
|
|||
.place
|
||||
.expect_type();
|
||||
debug_assert!(
|
||||
matches!(ty, Type::NominalInstance(instance) if is_single_member_enum(db, instance.class.class_literal(db).0))
|
||||
matches!(ty, Type::NominalInstance(instance) if is_single_member_enum(db, instance.class(db).class_literal(db).0))
|
||||
);
|
||||
ty
|
||||
}
|
||||
|
|
|
@ -23,12 +23,12 @@ use std::hash::Hash;
|
|||
use itertools::{Either, EitherOrBoth, Itertools};
|
||||
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::types::Truthiness;
|
||||
use crate::types::class::{ClassType, KnownClass};
|
||||
use crate::types::{
|
||||
BoundTypeVarInstance, Type, TypeMapping, TypeRelation, TypeTransformer, TypeVarVariance,
|
||||
UnionBuilder, UnionType, cyclic::PairVisitor,
|
||||
};
|
||||
use crate::types::{SubclassOfType, Truthiness};
|
||||
use crate::util::subscript::{Nth, OutOfBoundsError, PyIndex, PySlice, StepSizeZeroError};
|
||||
use crate::{Db, FxOrderSet};
|
||||
|
||||
|
@ -146,13 +146,6 @@ pub(super) fn walk_tuple_type<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>
|
|||
impl get_size2::GetSize for TupleType<'_> {}
|
||||
|
||||
impl<'db> Type<'db> {
|
||||
pub(crate) fn tuple(tuple: Option<TupleType<'db>>) -> Self {
|
||||
let Some(tuple) = tuple else {
|
||||
return Type::Never;
|
||||
};
|
||||
Self::Tuple(tuple)
|
||||
}
|
||||
|
||||
pub(crate) fn homogeneous_tuple(db: &'db dyn Db, element: Type<'db>) -> Self {
|
||||
Type::tuple(TupleType::homogeneous(db, element))
|
||||
}
|
||||
|
@ -169,7 +162,7 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
|
||||
pub(crate) fn empty_tuple(db: &'db dyn Db) -> Self {
|
||||
Type::Tuple(TupleType::empty(db))
|
||||
Type::tuple(Some(TupleType::empty(db)))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -227,23 +220,22 @@ impl<'db> TupleType<'db> {
|
|||
TupleType::new(db, TupleSpec::homogeneous(element))
|
||||
}
|
||||
|
||||
// N.B. If this method is not Salsa-tracked, we take 10 minutes to check
|
||||
// `static-frame` as part of a mypy_primer run! This is because it's called
|
||||
// from `NominalInstanceType::class()`, which is a very hot method.
|
||||
#[salsa::tracked]
|
||||
pub(crate) fn to_class_type(self, db: &'db dyn Db) -> Option<ClassType<'db>> {
|
||||
KnownClass::Tuple
|
||||
pub(crate) fn to_class_type(self, db: &'db dyn Db) -> ClassType<'db> {
|
||||
let tuple_class = KnownClass::Tuple
|
||||
.try_to_class_literal(db)
|
||||
.and_then(|class_literal| match class_literal.generic_context(db) {
|
||||
None => Some(ClassType::NonGeneric(class_literal)),
|
||||
Some(generic_context) if generic_context.variables(db).len() != 1 => None,
|
||||
Some(generic_context) => Some(
|
||||
class_literal
|
||||
.apply_specialization(db, |_| generic_context.specialize_tuple(db, self)),
|
||||
),
|
||||
})
|
||||
}
|
||||
.expect("Typeshed should always have a `tuple` class in `builtins.pyi`");
|
||||
|
||||
pub(crate) fn to_subclass_of(self, db: &'db dyn Db) -> Option<Type<'db>> {
|
||||
self.to_class_type(db)
|
||||
.map(|class| SubclassOfType::from(db, class))
|
||||
tuple_class.apply_specialization(db, |generic_context| {
|
||||
if generic_context.variables(db).len() == 1 {
|
||||
generic_context.specialize_tuple(db, self)
|
||||
} else {
|
||||
generic_context.default_specialization(db, Some(KnownClass::Tuple))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Return a normalized version of `self`.
|
||||
|
@ -294,23 +286,9 @@ impl<'db> TupleType<'db> {
|
|||
self.tuple(db).is_equivalent_to(db, other.tuple(db))
|
||||
}
|
||||
|
||||
pub(crate) fn is_disjoint_from_impl(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
other: Self,
|
||||
visitor: &mut PairVisitor<'db>,
|
||||
) -> bool {
|
||||
self.tuple(db)
|
||||
.is_disjoint_from_impl(db, other.tuple(db), visitor)
|
||||
}
|
||||
|
||||
pub(crate) fn is_single_valued(self, db: &'db dyn Db) -> bool {
|
||||
self.tuple(db).is_single_valued(db)
|
||||
}
|
||||
|
||||
pub(crate) fn truthiness(self, db: &'db dyn Db) -> Truthiness {
|
||||
self.tuple(db).truthiness()
|
||||
}
|
||||
}
|
||||
|
||||
/// A tuple spec describes the contents of a tuple type, which might be fixed- or variable-length.
|
||||
|
@ -361,10 +339,6 @@ impl<T> FixedLengthTuple<T> {
|
|||
self.0.len()
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
|
||||
pub(crate) fn push(&mut self, element: T) {
|
||||
self.0.push(element);
|
||||
}
|
||||
|
@ -1029,10 +1003,6 @@ impl<T> Tuple<T> {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn is_variadic(&self) -> bool {
|
||||
matches!(self, Tuple::Variable(_))
|
||||
}
|
||||
|
||||
/// Returns the length of this tuple.
|
||||
pub(crate) fn len(&self) -> TupleLength {
|
||||
match self {
|
||||
|
@ -1052,13 +1022,6 @@ impl<T> Tuple<T> {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_empty(&self) -> bool {
|
||||
match self {
|
||||
Tuple::Fixed(tuple) => tuple.is_empty(),
|
||||
Tuple::Variable(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn push(&mut self, element: T) {
|
||||
match self {
|
||||
Tuple::Fixed(tuple) => tuple.push(element),
|
||||
|
@ -1154,10 +1117,10 @@ impl<'db> Tuple<Type<'db>> {
|
|||
}
|
||||
}
|
||||
|
||||
fn is_disjoint_from_impl(
|
||||
&'db self,
|
||||
pub(super) fn is_disjoint_from_impl(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
other: &'db Self,
|
||||
other: &Self,
|
||||
visitor: &mut PairVisitor<'db>,
|
||||
) -> bool {
|
||||
// Two tuples with an incompatible number of required elements must always be disjoint.
|
||||
|
@ -1172,12 +1135,15 @@ impl<'db> Tuple<Type<'db>> {
|
|||
|
||||
// If any of the required elements are pairwise disjoint, the tuples are disjoint as well.
|
||||
#[allow(clippy::items_after_statements)]
|
||||
fn any_disjoint<'db>(
|
||||
fn any_disjoint<'s, 'db>(
|
||||
db: &'db dyn Db,
|
||||
a: impl IntoIterator<Item = &'db Type<'db>>,
|
||||
b: impl IntoIterator<Item = &'db Type<'db>>,
|
||||
a: impl IntoIterator<Item = &'s Type<'db>>,
|
||||
b: impl IntoIterator<Item = &'s Type<'db>>,
|
||||
visitor: &mut PairVisitor<'db>,
|
||||
) -> bool {
|
||||
) -> bool
|
||||
where
|
||||
'db: 's,
|
||||
{
|
||||
a.into_iter().zip(b).any(|(self_element, other_element)| {
|
||||
self_element.is_disjoint_from_impl(db, *other_element, visitor)
|
||||
})
|
||||
|
@ -1231,7 +1197,7 @@ impl<'db> Tuple<Type<'db>> {
|
|||
false
|
||||
}
|
||||
|
||||
fn is_single_valued(&self, db: &'db dyn Db) -> bool {
|
||||
pub(crate) fn is_single_valued(&self, db: &'db dyn Db) -> bool {
|
||||
match self {
|
||||
Tuple::Fixed(tuple) => tuple.is_single_valued(db),
|
||||
Tuple::Variable(_) => false,
|
||||
|
|
|
@ -100,10 +100,6 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
|
|||
(Type::Callable(_), _) => Ordering::Less,
|
||||
(_, Type::Callable(_)) => Ordering::Greater,
|
||||
|
||||
(Type::Tuple(left), Type::Tuple(right)) => left.cmp(right),
|
||||
(Type::Tuple(_), _) => Ordering::Less,
|
||||
(_, Type::Tuple(_)) => Ordering::Greater,
|
||||
|
||||
(Type::ModuleLiteral(left), Type::ModuleLiteral(right)) => left.cmp(right),
|
||||
(Type::ModuleLiteral(_), _) => Ordering::Less,
|
||||
(_, Type::ModuleLiteral(_)) => Ordering::Greater,
|
||||
|
@ -134,7 +130,9 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
|
|||
(Type::TypeIs(_), _) => Ordering::Less,
|
||||
(_, Type::TypeIs(_)) => Ordering::Greater,
|
||||
|
||||
(Type::NominalInstance(left), Type::NominalInstance(right)) => left.class.cmp(&right.class),
|
||||
(Type::NominalInstance(left), Type::NominalInstance(right)) => {
|
||||
left.class(db).cmp(&right.class(db))
|
||||
}
|
||||
(Type::NominalInstance(_), _) => Ordering::Less,
|
||||
(_, Type::NominalInstance(_)) => Ordering::Greater,
|
||||
|
||||
|
@ -178,7 +176,7 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
|
|||
(SuperOwnerKind::Class(_), _) => Ordering::Less,
|
||||
(_, SuperOwnerKind::Class(_)) => Ordering::Greater,
|
||||
(SuperOwnerKind::Instance(left), SuperOwnerKind::Instance(right)) => {
|
||||
left.class.cmp(&right.class)
|
||||
left.class(db).cmp(&right.class(db))
|
||||
}
|
||||
(SuperOwnerKind::Instance(_), _) => Ordering::Less,
|
||||
(_, SuperOwnerKind::Instance(_)) => Ordering::Greater,
|
||||
|
|
|
@ -9,7 +9,6 @@ use crate::{
|
|||
function::{FunctionType, walk_function_type},
|
||||
instance::{walk_nominal_instance_type, walk_protocol_instance_type},
|
||||
subclass_of::walk_subclass_of_type,
|
||||
tuple::{TupleType, walk_tuple_type},
|
||||
walk_bound_method_type, walk_bound_super_type, walk_bound_type_var_type,
|
||||
walk_callable_type, walk_intersection_type, walk_known_instance_type,
|
||||
walk_method_wrapper_type, walk_property_instance_type, walk_type_alias_type,
|
||||
|
@ -33,10 +32,6 @@ pub(crate) trait TypeVisitor<'db> {
|
|||
walk_intersection_type(db, intersection, self);
|
||||
}
|
||||
|
||||
fn visit_tuple_type(&mut self, db: &'db dyn Db, tuple: TupleType<'db>) {
|
||||
walk_tuple_type(db, tuple, self);
|
||||
}
|
||||
|
||||
fn visit_callable_type(&mut self, db: &'db dyn Db, callable: CallableType<'db>) {
|
||||
walk_callable_type(db, callable, self);
|
||||
}
|
||||
|
@ -127,7 +122,6 @@ pub(crate) trait TypeVisitor<'db> {
|
|||
enum NonAtomicType<'db> {
|
||||
Union(UnionType<'db>),
|
||||
Intersection(IntersectionType<'db>),
|
||||
Tuple(TupleType<'db>),
|
||||
FunctionLiteral(FunctionType<'db>),
|
||||
BoundMethod(BoundMethodType<'db>),
|
||||
BoundSuper(BoundSuperType<'db>),
|
||||
|
@ -177,7 +171,6 @@ impl<'db> From<Type<'db>> for TypeKind<'db> {
|
|||
TypeKind::NonAtomic(NonAtomicType::Intersection(intersection))
|
||||
}
|
||||
Type::Union(union) => TypeKind::NonAtomic(NonAtomicType::Union(union)),
|
||||
Type::Tuple(tuple) => TypeKind::NonAtomic(NonAtomicType::Tuple(tuple)),
|
||||
Type::BoundMethod(method) => TypeKind::NonAtomic(NonAtomicType::BoundMethod(method)),
|
||||
Type::BoundSuper(bound_super) => {
|
||||
TypeKind::NonAtomic(NonAtomicType::BoundSuper(bound_super))
|
||||
|
@ -224,7 +217,6 @@ fn walk_non_atomic_type<'db, V: TypeVisitor<'db> + ?Sized>(
|
|||
visitor.visit_intersection_type(db, intersection);
|
||||
}
|
||||
NonAtomicType::Union(union) => visitor.visit_union_type(db, union),
|
||||
NonAtomicType::Tuple(tuple) => visitor.visit_tuple_type(db, tuple),
|
||||
NonAtomicType::BoundMethod(method) => visitor.visit_bound_method_type(db, method),
|
||||
NonAtomicType::BoundSuper(bound_super) => visitor.visit_bound_super_type(db, bound_super),
|
||||
NonAtomicType::MethodWrapper(method_wrapper) => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue