mirror of https://github.com/astral-sh/ruff
[ty] support PEP 613 type aliases (#21394)
Refs https://github.com/astral-sh/ty/issues/544 ## Summary Takes a more incremental approach to PEP 613 type alias support (vs https://github.com/astral-sh/ruff/pull/20107). Instead of eagerly inferring the RHS of a PEP 613 type alias as a type expression, infer it as a value expression, just like we do for implicit type aliases, taking advantage of the same support for e.g. unions and other type special forms. The main reason I'm following this path instead of the one in https://github.com/astral-sh/ruff/pull/20107 is that we've realized that people do sometimes use PEP 613 type aliases as values, not just as types (because they are just a normal runtime assignment, unlike PEP 695 type aliases which create an opaque `TypeAliasType`). This PR doesn't yet provide full support for recursive type aliases (they don't panic, but they just fall back to `Unknown` at the recursion point). This is future work. ## Test Plan Added mdtests. Many new ecosystem diagnostics, mostly because we understand new types in lots of places. Conformance suite changes are correct. Performance regression is due to understanding lots of new types; nothing we do in this PR is inherently expensive.
This commit is contained in:
parent
06941c1987
commit
6b7adb0537
|
|
@ -143,7 +143,7 @@ static FREQTRADE: Benchmark = Benchmark::new(
|
||||||
max_dep_date: "2025-06-17",
|
max_dep_date: "2025-06-17",
|
||||||
python_version: PythonVersion::PY312,
|
python_version: PythonVersion::PY312,
|
||||||
},
|
},
|
||||||
525,
|
600,
|
||||||
);
|
);
|
||||||
|
|
||||||
static PANDAS: Benchmark = Benchmark::new(
|
static PANDAS: Benchmark = Benchmark::new(
|
||||||
|
|
@ -163,7 +163,7 @@ static PANDAS: Benchmark = Benchmark::new(
|
||||||
max_dep_date: "2025-06-17",
|
max_dep_date: "2025-06-17",
|
||||||
python_version: PythonVersion::PY312,
|
python_version: PythonVersion::PY312,
|
||||||
},
|
},
|
||||||
3000,
|
4000,
|
||||||
);
|
);
|
||||||
|
|
||||||
static PYDANTIC: Benchmark = Benchmark::new(
|
static PYDANTIC: Benchmark = Benchmark::new(
|
||||||
|
|
@ -181,7 +181,7 @@ static PYDANTIC: Benchmark = Benchmark::new(
|
||||||
max_dep_date: "2025-06-17",
|
max_dep_date: "2025-06-17",
|
||||||
python_version: PythonVersion::PY39,
|
python_version: PythonVersion::PY39,
|
||||||
},
|
},
|
||||||
5000,
|
7000,
|
||||||
);
|
);
|
||||||
|
|
||||||
static SYMPY: Benchmark = Benchmark::new(
|
static SYMPY: Benchmark = Benchmark::new(
|
||||||
|
|
|
||||||
|
|
@ -2136,10 +2136,10 @@ C.<CURSOR>
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_snapshot!(
|
assert_snapshot!(
|
||||||
builder.skip_keywords().skip_builtins().type_signatures().build().snapshot(), @r"
|
builder.skip_keywords().skip_builtins().type_signatures().build().snapshot(), @r###"
|
||||||
meta_attr :: int
|
meta_attr :: int
|
||||||
mro :: bound method <class 'C'>.mro() -> list[type]
|
mro :: bound method <class 'C'>.mro() -> list[type]
|
||||||
__annotate__ :: @Todo | None
|
__annotate__ :: (() -> dict[str, Any]) | None
|
||||||
__annotations__ :: dict[str, Any]
|
__annotations__ :: dict[str, Any]
|
||||||
__base__ :: type | None
|
__base__ :: type | None
|
||||||
__bases__ :: tuple[type, ...]
|
__bases__ :: tuple[type, ...]
|
||||||
|
|
@ -2182,7 +2182,7 @@ C.<CURSOR>
|
||||||
__text_signature__ :: str | None
|
__text_signature__ :: str | None
|
||||||
__type_params__ :: tuple[TypeVar | ParamSpec | TypeVarTuple, ...]
|
__type_params__ :: tuple[TypeVar | ParamSpec | TypeVarTuple, ...]
|
||||||
__weakrefoffset__ :: int
|
__weakrefoffset__ :: int
|
||||||
");
|
"###);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -2331,14 +2331,14 @@ Quux.<CURSOR>
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_snapshot!(
|
assert_snapshot!(
|
||||||
builder.skip_keywords().skip_builtins().type_signatures().build().snapshot(), @r"
|
builder.skip_keywords().skip_builtins().type_signatures().build().snapshot(), @r###"
|
||||||
mro :: bound method <class 'Quux'>.mro() -> list[type]
|
mro :: bound method <class 'Quux'>.mro() -> list[type]
|
||||||
some_attribute :: int
|
some_attribute :: int
|
||||||
some_class_method :: bound method <class 'Quux'>.some_class_method() -> int
|
some_class_method :: bound method <class 'Quux'>.some_class_method() -> int
|
||||||
some_method :: def some_method(self) -> int
|
some_method :: def some_method(self) -> int
|
||||||
some_property :: property
|
some_property :: property
|
||||||
some_static_method :: def some_static_method(self) -> int
|
some_static_method :: def some_static_method(self) -> int
|
||||||
__annotate__ :: @Todo | None
|
__annotate__ :: (() -> dict[str, Any]) | None
|
||||||
__annotations__ :: dict[str, Any]
|
__annotations__ :: dict[str, Any]
|
||||||
__base__ :: type | None
|
__base__ :: type | None
|
||||||
__bases__ :: tuple[type, ...]
|
__bases__ :: tuple[type, ...]
|
||||||
|
|
@ -2381,7 +2381,7 @@ Quux.<CURSOR>
|
||||||
__text_signature__ :: str | None
|
__text_signature__ :: str | None
|
||||||
__type_params__ :: tuple[TypeVar | ParamSpec | TypeVarTuple, ...]
|
__type_params__ :: tuple[TypeVar | ParamSpec | TypeVarTuple, ...]
|
||||||
__weakrefoffset__ :: int
|
__weakrefoffset__ :: int
|
||||||
");
|
"###);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,8 @@ P = ParamSpec("P")
|
||||||
Ts = TypeVarTuple("Ts")
|
Ts = TypeVarTuple("Ts")
|
||||||
R_co = TypeVar("R_co", covariant=True)
|
R_co = TypeVar("R_co", covariant=True)
|
||||||
|
|
||||||
Alias: TypeAlias = int
|
|
||||||
|
|
||||||
def f(*args: Unpack[Ts]) -> tuple[Unpack[Ts]]:
|
def f(*args: Unpack[Ts]) -> tuple[Unpack[Ts]]:
|
||||||
reveal_type(args) # revealed: tuple[@Todo(`Unpack[]` special form), ...]
|
reveal_type(args) # revealed: tuple[@Todo(`Unpack[]` special form), ...]
|
||||||
reveal_type(Alias) # revealed: @Todo(Support for `typing.TypeAlias`)
|
|
||||||
return args
|
return args
|
||||||
|
|
||||||
def g() -> TypeGuard[int]: ...
|
def g() -> TypeGuard[int]: ...
|
||||||
|
|
|
||||||
|
|
@ -2208,9 +2208,9 @@ reveal_type(False.real) # revealed: Literal[0]
|
||||||
All attribute access on literal `bytes` types is currently delegated to `builtins.bytes`:
|
All attribute access on literal `bytes` types is currently delegated to `builtins.bytes`:
|
||||||
|
|
||||||
```py
|
```py
|
||||||
# revealed: bound method Literal[b"foo"].join(iterable_of_bytes: Iterable[@Todo(Support for `typing.TypeAlias`)], /) -> bytes
|
# revealed: bound method Literal[b"foo"].join(iterable_of_bytes: Iterable[Buffer], /) -> bytes
|
||||||
reveal_type(b"foo".join)
|
reveal_type(b"foo".join)
|
||||||
# revealed: bound method Literal[b"foo"].endswith(suffix: @Todo(Support for `typing.TypeAlias`) | tuple[@Todo(Support for `typing.TypeAlias`), ...], start: SupportsIndex | None = None, end: SupportsIndex | None = None, /) -> bool
|
# revealed: bound method Literal[b"foo"].endswith(suffix: Buffer | tuple[Buffer, ...], start: SupportsIndex | None = None, end: SupportsIndex | None = None, /) -> bool
|
||||||
reveal_type(b"foo".endswith)
|
reveal_type(b"foo".endswith)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -313,8 +313,7 @@ reveal_type(A() + "foo") # revealed: A
|
||||||
reveal_type("foo" + A()) # revealed: A
|
reveal_type("foo" + A()) # revealed: A
|
||||||
|
|
||||||
reveal_type(A() + b"foo") # revealed: A
|
reveal_type(A() + b"foo") # revealed: A
|
||||||
# TODO should be `A` since `bytes.__add__` doesn't support `A` instances
|
reveal_type(b"foo" + A()) # revealed: A
|
||||||
reveal_type(b"foo" + A()) # revealed: bytes
|
|
||||||
|
|
||||||
reveal_type(A() + ()) # revealed: A
|
reveal_type(A() + ()) # revealed: A
|
||||||
reveal_type(() + A()) # revealed: A
|
reveal_type(() + A()) # revealed: A
|
||||||
|
|
|
||||||
|
|
@ -54,10 +54,8 @@ reveal_type(2**largest_u32) # revealed: int
|
||||||
|
|
||||||
def variable(x: int):
|
def variable(x: int):
|
||||||
reveal_type(x**2) # revealed: int
|
reveal_type(x**2) # revealed: int
|
||||||
# TODO: should be `Any` (overload 5 on `__pow__`), requires correct overload matching
|
reveal_type(2**x) # revealed: Any
|
||||||
reveal_type(2**x) # revealed: int
|
reveal_type(x**x) # revealed: Any
|
||||||
# TODO: should be `Any` (overload 5 on `__pow__`), requires correct overload matching
|
|
||||||
reveal_type(x**x) # revealed: int
|
|
||||||
```
|
```
|
||||||
|
|
||||||
If the second argument is \<0, a `float` is returned at runtime. If the first argument is \<0 but
|
If the second argument is \<0, a `float` is returned at runtime. If the first argument is \<0 but
|
||||||
|
|
|
||||||
|
|
@ -598,9 +598,9 @@ from typing_extensions import Self
|
||||||
|
|
||||||
reveal_type(object.__new__) # revealed: def __new__(cls) -> Self@__new__
|
reveal_type(object.__new__) # revealed: def __new__(cls) -> Self@__new__
|
||||||
reveal_type(object().__new__) # revealed: def __new__(cls) -> Self@__new__
|
reveal_type(object().__new__) # revealed: def __new__(cls) -> Self@__new__
|
||||||
# revealed: Overload[(cls, x: @Todo(Support for `typing.TypeAlias`) = Literal[0], /) -> Self@__new__, (cls, x: str | bytes | bytearray, /, base: SupportsIndex) -> Self@__new__]
|
# revealed: Overload[(cls, x: str | Buffer | SupportsInt | SupportsIndex | SupportsTrunc = Literal[0], /) -> Self@__new__, (cls, x: str | bytes | bytearray, /, base: SupportsIndex) -> Self@__new__]
|
||||||
reveal_type(int.__new__)
|
reveal_type(int.__new__)
|
||||||
# revealed: Overload[(cls, x: @Todo(Support for `typing.TypeAlias`) = Literal[0], /) -> Self@__new__, (cls, x: str | bytes | bytearray, /, base: SupportsIndex) -> Self@__new__]
|
# revealed: Overload[(cls, x: str | Buffer | SupportsInt | SupportsIndex | SupportsTrunc = Literal[0], /) -> Self@__new__, (cls, x: str | bytes | bytearray, /, base: SupportsIndex) -> Self@__new__]
|
||||||
reveal_type((42).__new__)
|
reveal_type((42).__new__)
|
||||||
|
|
||||||
class X:
|
class X:
|
||||||
|
|
|
||||||
|
|
@ -10,13 +10,13 @@ import pickle
|
||||||
|
|
||||||
reveal_type(open("")) # revealed: TextIOWrapper[_WrappedBuffer]
|
reveal_type(open("")) # revealed: TextIOWrapper[_WrappedBuffer]
|
||||||
reveal_type(open("", "r")) # revealed: TextIOWrapper[_WrappedBuffer]
|
reveal_type(open("", "r")) # revealed: TextIOWrapper[_WrappedBuffer]
|
||||||
reveal_type(open("", "rb")) # revealed: @Todo(`builtins.open` return type)
|
reveal_type(open("", "rb")) # revealed: BufferedReader[_BufferedReaderStream]
|
||||||
|
|
||||||
with open("foo.pickle", "rb") as f:
|
with open("foo.pickle", "rb") as f:
|
||||||
x = pickle.load(f) # fine
|
x = pickle.load(f) # fine
|
||||||
|
|
||||||
def _(mode: str):
|
def _(mode: str):
|
||||||
reveal_type(open("", mode)) # revealed: @Todo(`builtins.open` return type)
|
reveal_type(open("", mode)) # revealed: IO[Any]
|
||||||
```
|
```
|
||||||
|
|
||||||
## `os.fdopen`
|
## `os.fdopen`
|
||||||
|
|
@ -29,7 +29,7 @@ import os
|
||||||
|
|
||||||
reveal_type(os.fdopen(0)) # revealed: TextIOWrapper[_WrappedBuffer]
|
reveal_type(os.fdopen(0)) # revealed: TextIOWrapper[_WrappedBuffer]
|
||||||
reveal_type(os.fdopen(0, "r")) # revealed: TextIOWrapper[_WrappedBuffer]
|
reveal_type(os.fdopen(0, "r")) # revealed: TextIOWrapper[_WrappedBuffer]
|
||||||
reveal_type(os.fdopen(0, "rb")) # revealed: @Todo(`os.fdopen` return type)
|
reveal_type(os.fdopen(0, "rb")) # revealed: BufferedReader[_BufferedReaderStream]
|
||||||
|
|
||||||
with os.fdopen(0, "rb") as f:
|
with os.fdopen(0, "rb") as f:
|
||||||
x = pickle.load(f) # fine
|
x = pickle.load(f) # fine
|
||||||
|
|
@ -43,9 +43,9 @@ And similarly for `Path.open()`:
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import pickle
|
import pickle
|
||||||
|
|
||||||
reveal_type(Path("").open()) # revealed: @Todo(`Path.open` return type)
|
reveal_type(Path("").open()) # revealed: TextIOWrapper[_WrappedBuffer]
|
||||||
reveal_type(Path("").open("r")) # revealed: @Todo(`Path.open` return type)
|
reveal_type(Path("").open("r")) # revealed: TextIOWrapper[_WrappedBuffer]
|
||||||
reveal_type(Path("").open("rb")) # revealed: @Todo(`Path.open` return type)
|
reveal_type(Path("").open("rb")) # revealed: BufferedReader[_BufferedReaderStream]
|
||||||
|
|
||||||
with Path("foo.pickle").open("rb") as f:
|
with Path("foo.pickle").open("rb") as f:
|
||||||
x = pickle.load(f) # fine
|
x = pickle.load(f) # fine
|
||||||
|
|
@ -61,7 +61,7 @@ import pickle
|
||||||
|
|
||||||
reveal_type(NamedTemporaryFile()) # revealed: _TemporaryFileWrapper[bytes]
|
reveal_type(NamedTemporaryFile()) # revealed: _TemporaryFileWrapper[bytes]
|
||||||
reveal_type(NamedTemporaryFile("r")) # revealed: _TemporaryFileWrapper[str]
|
reveal_type(NamedTemporaryFile("r")) # revealed: _TemporaryFileWrapper[str]
|
||||||
reveal_type(NamedTemporaryFile("rb")) # revealed: @Todo(`tempfile.NamedTemporaryFile` return type)
|
reveal_type(NamedTemporaryFile("rb")) # revealed: _TemporaryFileWrapper[bytes]
|
||||||
|
|
||||||
with NamedTemporaryFile("rb") as f:
|
with NamedTemporaryFile("rb") as f:
|
||||||
x = pickle.load(f) # fine
|
x = pickle.load(f) # fine
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,7 @@ x = lambda y: y
|
||||||
reveal_type(x.__code__) # revealed: CodeType
|
reveal_type(x.__code__) # revealed: CodeType
|
||||||
reveal_type(x.__name__) # revealed: str
|
reveal_type(x.__name__) # revealed: str
|
||||||
reveal_type(x.__defaults__) # revealed: tuple[Any, ...] | None
|
reveal_type(x.__defaults__) # revealed: tuple[Any, ...] | None
|
||||||
reveal_type(x.__annotations__) # revealed: dict[str, @Todo(Support for `typing.TypeAlias`)]
|
reveal_type(x.__annotations__) # revealed: dict[str, Any]
|
||||||
reveal_type(x.__dict__) # revealed: dict[str, Any]
|
reveal_type(x.__dict__) # revealed: dict[str, Any]
|
||||||
reveal_type(x.__doc__) # revealed: str | None
|
reveal_type(x.__doc__) # revealed: str | None
|
||||||
reveal_type(x.__kwdefaults__) # revealed: dict[str, Any] | None
|
reveal_type(x.__kwdefaults__) # revealed: dict[str, Any] | None
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,147 @@
|
||||||
# PEP 613 type aliases
|
# PEP 613 type aliases
|
||||||
|
|
||||||
## No panics
|
PEP 613 type aliases are simple assignment statements, annotated with `typing.TypeAlias` to mark
|
||||||
|
them as a type alias. At runtime, they behave the same as implicit type aliases. Our support for
|
||||||
|
them is currently the same as for implicit type aliases, but we don't reproduce the full
|
||||||
|
implicit-type-alias test suite here, just some particularly interesting cases.
|
||||||
|
|
||||||
We do not fully support PEP 613 type aliases yet. For now, just make sure that we don't panic:
|
## Basic
|
||||||
|
|
||||||
|
### as `TypeAlias`
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import TypeAlias
|
||||||
|
|
||||||
|
IntOrStr: TypeAlias = int | str
|
||||||
|
|
||||||
|
def _(x: IntOrStr):
|
||||||
|
reveal_type(x) # revealed: int | str
|
||||||
|
```
|
||||||
|
|
||||||
|
### as `typing.TypeAlias`
|
||||||
|
|
||||||
|
```py
|
||||||
|
import typing
|
||||||
|
|
||||||
|
IntOrStr: typing.TypeAlias = int | str
|
||||||
|
|
||||||
|
def _(x: IntOrStr):
|
||||||
|
reveal_type(x) # revealed: int | str
|
||||||
|
```
|
||||||
|
|
||||||
|
## Can be used as value
|
||||||
|
|
||||||
|
Because PEP 613 type aliases are just annotated assignments, they can be used as values, like a
|
||||||
|
legacy type expression (and unlike a PEP 695 type alias). We might prefer this wasn't allowed, but
|
||||||
|
people do use it.
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import TypeAlias
|
||||||
|
|
||||||
|
MyExc: TypeAlias = Exception
|
||||||
|
|
||||||
|
try:
|
||||||
|
raise MyExc("error")
|
||||||
|
except MyExc as e:
|
||||||
|
reveal_type(e) # revealed: Exception
|
||||||
|
```
|
||||||
|
|
||||||
|
## Can inherit from an alias
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import TypeAlias
|
||||||
|
from ty_extensions import is_subtype_of, static_assert
|
||||||
|
|
||||||
|
MyList: TypeAlias = list["int"]
|
||||||
|
|
||||||
|
class Foo(MyList): ...
|
||||||
|
|
||||||
|
static_assert(is_subtype_of(Foo, list[int]))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Cannot inherit from a stringified alias
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import TypeAlias
|
||||||
|
|
||||||
|
MyList: TypeAlias = "list[int]"
|
||||||
|
|
||||||
|
# error: [invalid-base] "Invalid class base with type `str`"
|
||||||
|
class Foo(MyList): ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Unknown type in PEP 604 union
|
||||||
|
|
||||||
|
If we run into an unknown type in a PEP 604 union in the right-hand side of a PEP 613 type alias, we
|
||||||
|
still understand it as a union type, just with an unknown element.
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import TypeAlias
|
||||||
|
from nonexistent import unknown_type # error: [unresolved-import]
|
||||||
|
|
||||||
|
MyAlias: TypeAlias = int | unknown_type | str
|
||||||
|
|
||||||
|
def _(x: MyAlias):
|
||||||
|
reveal_type(x) # revealed: int | Unknown | str
|
||||||
|
```
|
||||||
|
|
||||||
|
## Callable type in union
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import TypeAlias, Callable
|
||||||
|
|
||||||
|
MyAlias: TypeAlias = int | Callable[[str], int]
|
||||||
|
|
||||||
|
def _(x: MyAlias):
|
||||||
|
reveal_type(x) # revealed: int | ((str, /) -> int)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Subscripted generic alias in union
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import TypeAlias, TypeVar
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
Alias1: TypeAlias = list[T] | set[T]
|
||||||
|
MyAlias: TypeAlias = int | Alias1[str]
|
||||||
|
|
||||||
|
def _(x: MyAlias):
|
||||||
|
# TODO: int | list[str] | set[str]
|
||||||
|
reveal_type(x) # revealed: int | @Todo(Specialization of union type alias)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Imported
|
||||||
|
|
||||||
|
`alias.py`:
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import TypeAlias
|
||||||
|
|
||||||
|
MyAlias: TypeAlias = int | str
|
||||||
|
```
|
||||||
|
|
||||||
|
`main.py`:
|
||||||
|
|
||||||
|
```py
|
||||||
|
from alias import MyAlias
|
||||||
|
|
||||||
|
def _(x: MyAlias):
|
||||||
|
reveal_type(x) # revealed: int | str
|
||||||
|
```
|
||||||
|
|
||||||
|
## String literal in right-hand side
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import TypeAlias
|
||||||
|
|
||||||
|
IntOrStr: TypeAlias = "int | str"
|
||||||
|
|
||||||
|
def _(x: IntOrStr):
|
||||||
|
reveal_type(x) # revealed: int | str
|
||||||
|
```
|
||||||
|
|
||||||
|
## Cyclic
|
||||||
|
|
||||||
```py
|
```py
|
||||||
from typing import TypeAlias
|
from typing import TypeAlias
|
||||||
|
|
@ -18,6 +157,26 @@ def _(rec: RecursiveHomogeneousTuple):
|
||||||
reveal_type(rec) # revealed: tuple[Divergent, ...]
|
reveal_type(rec) # revealed: tuple[Divergent, ...]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Conditionally imported on Python < 3.10
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[environment]
|
||||||
|
python-version = "3.9"
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
try:
|
||||||
|
# error: [unresolved-import]
|
||||||
|
from typing import TypeAlias
|
||||||
|
except ImportError:
|
||||||
|
from typing_extensions import TypeAlias
|
||||||
|
|
||||||
|
MyAlias: TypeAlias = int
|
||||||
|
|
||||||
|
def _(x: MyAlias):
|
||||||
|
reveal_type(x) # revealed: int
|
||||||
|
```
|
||||||
|
|
||||||
## PEP-613 aliases in stubs are deferred
|
## PEP-613 aliases in stubs are deferred
|
||||||
|
|
||||||
Although the right-hand side of a PEP-613 alias is a value expression, inference of this value is
|
Although the right-hand side of a PEP-613 alias is a value expression, inference of this value is
|
||||||
|
|
@ -46,7 +205,31 @@ f(stub.B())
|
||||||
|
|
||||||
class Unrelated: ...
|
class Unrelated: ...
|
||||||
|
|
||||||
# TODO: we should emit `[invalid-argument-type]` here
|
# error: [invalid-argument-type]
|
||||||
# (the alias is a `@Todo` because it's imported from another file)
|
|
||||||
f(Unrelated())
|
f(Unrelated())
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Invalid position
|
||||||
|
|
||||||
|
`typing.TypeAlias` must be used as the sole annotation in an annotated assignment. Use in any other
|
||||||
|
context is an error.
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import TypeAlias
|
||||||
|
|
||||||
|
# error: [invalid-type-form]
|
||||||
|
def _(x: TypeAlias):
|
||||||
|
reveal_type(x) # revealed: Unknown
|
||||||
|
|
||||||
|
# error: [invalid-type-form]
|
||||||
|
y: list[TypeAlias] = []
|
||||||
|
```
|
||||||
|
|
||||||
|
## Right-hand side is required
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import TypeAlias
|
||||||
|
|
||||||
|
# error: [invalid-type-form]
|
||||||
|
Empty: TypeAlias
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ def f() -> None:
|
||||||
```py
|
```py
|
||||||
type IntOrStr = int | str
|
type IntOrStr = int | str
|
||||||
|
|
||||||
reveal_type(IntOrStr.__value__) # revealed: @Todo(Support for `typing.TypeAlias`)
|
reveal_type(IntOrStr.__value__) # revealed: Any
|
||||||
```
|
```
|
||||||
|
|
||||||
## Invalid assignment
|
## Invalid assignment
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,7 @@ properties on instance types:
|
||||||
|
|
||||||
```py
|
```py
|
||||||
reveal_type(sys.version_info.micro) # revealed: int
|
reveal_type(sys.version_info.micro) # revealed: int
|
||||||
reveal_type(sys.version_info.releaselevel) # revealed: @Todo(Support for `typing.TypeAlias`)
|
reveal_type(sys.version_info.releaselevel) # revealed: Literal["alpha", "beta", "candidate", "final"]
|
||||||
reveal_type(sys.version_info.serial) # revealed: int
|
reveal_type(sys.version_info.serial) # revealed: int
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -863,6 +863,10 @@ impl<'db> Type<'db> {
|
||||||
.is_some()
|
.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_typealias_special_form(&self) -> bool {
|
||||||
|
matches!(self, Type::SpecialForm(SpecialFormType::TypeAlias))
|
||||||
|
}
|
||||||
|
|
||||||
/// Return true if this type overrides __eq__ or __ne__ methods
|
/// Return true if this type overrides __eq__ or __ne__ methods
|
||||||
fn overrides_equality(&self, db: &'db dyn Db) -> bool {
|
fn overrides_equality(&self, db: &'db dyn Db) -> bool {
|
||||||
let check_dunder = |dunder_name, allowed_return_value| {
|
let check_dunder = |dunder_name, allowed_return_value| {
|
||||||
|
|
@ -4403,11 +4407,6 @@ impl<'db> Type<'db> {
|
||||||
Type::KnownBoundMethod(KnownBoundMethodType::StrStartswith(literal)),
|
Type::KnownBoundMethod(KnownBoundMethodType::StrStartswith(literal)),
|
||||||
)
|
)
|
||||||
.into(),
|
.into(),
|
||||||
Type::NominalInstance(instance)
|
|
||||||
if instance.has_known_class(db, KnownClass::Path) && name == "open" =>
|
|
||||||
{
|
|
||||||
Place::bound(Type::KnownBoundMethod(KnownBoundMethodType::PathOpen)).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
Type::ClassLiteral(class)
|
Type::ClassLiteral(class)
|
||||||
if name == "range" && class.is_known(db, KnownClass::ConstraintSet) =>
|
if name == "range" && class.is_known(db, KnownClass::ConstraintSet) =>
|
||||||
|
|
@ -6781,6 +6780,7 @@ impl<'db> Type<'db> {
|
||||||
Ok(ty.inner(db).to_meta_type(db))
|
Ok(ty.inner(db).to_meta_type(db))
|
||||||
}
|
}
|
||||||
KnownInstanceType::Callable(callable) => Ok(Type::Callable(*callable)),
|
KnownInstanceType::Callable(callable) => Ok(Type::Callable(*callable)),
|
||||||
|
KnownInstanceType::LiteralStringAlias(ty) => Ok(ty.inner(db)),
|
||||||
},
|
},
|
||||||
|
|
||||||
Type::SpecialForm(special_form) => match special_form {
|
Type::SpecialForm(special_form) => match special_form {
|
||||||
|
|
@ -6835,7 +6835,15 @@ impl<'db> Type<'db> {
|
||||||
|
|
||||||
Ok(typing_self(db, scope_id, typevar_binding_context, class).unwrap_or(*self))
|
Ok(typing_self(db, scope_id, typevar_binding_context, class).unwrap_or(*self))
|
||||||
}
|
}
|
||||||
SpecialFormType::TypeAlias => Ok(Type::Dynamic(DynamicType::TodoTypeAlias)),
|
// We ensure that `typing.TypeAlias` used in the expected position (annotating an
|
||||||
|
// annotated assignment statement) doesn't reach here. Using it in any other type
|
||||||
|
// expression is an error.
|
||||||
|
SpecialFormType::TypeAlias => Err(InvalidTypeExpressionError {
|
||||||
|
invalid_expressions: smallvec::smallvec_inline![
|
||||||
|
InvalidTypeExpression::TypeAlias
|
||||||
|
],
|
||||||
|
fallback_type: Type::unknown(),
|
||||||
|
}),
|
||||||
SpecialFormType::TypedDict => Err(InvalidTypeExpressionError {
|
SpecialFormType::TypedDict => Err(InvalidTypeExpressionError {
|
||||||
invalid_expressions: smallvec::smallvec_inline![
|
invalid_expressions: smallvec::smallvec_inline![
|
||||||
InvalidTypeExpression::TypedDict
|
InvalidTypeExpression::TypedDict
|
||||||
|
|
@ -7313,7 +7321,6 @@ impl<'db> Type<'db> {
|
||||||
| Type::WrapperDescriptor(_)
|
| Type::WrapperDescriptor(_)
|
||||||
| Type::KnownBoundMethod(
|
| Type::KnownBoundMethod(
|
||||||
KnownBoundMethodType::StrStartswith(_)
|
KnownBoundMethodType::StrStartswith(_)
|
||||||
| KnownBoundMethodType::PathOpen
|
|
||||||
| KnownBoundMethodType::ConstraintSetRange
|
| KnownBoundMethodType::ConstraintSetRange
|
||||||
| KnownBoundMethodType::ConstraintSetAlways
|
| KnownBoundMethodType::ConstraintSetAlways
|
||||||
| KnownBoundMethodType::ConstraintSetNever
|
| KnownBoundMethodType::ConstraintSetNever
|
||||||
|
|
@ -7474,7 +7481,6 @@ impl<'db> Type<'db> {
|
||||||
| Type::WrapperDescriptor(_)
|
| Type::WrapperDescriptor(_)
|
||||||
| Type::KnownBoundMethod(
|
| Type::KnownBoundMethod(
|
||||||
KnownBoundMethodType::StrStartswith(_)
|
KnownBoundMethodType::StrStartswith(_)
|
||||||
| KnownBoundMethodType::PathOpen
|
|
||||||
| KnownBoundMethodType::ConstraintSetRange
|
| KnownBoundMethodType::ConstraintSetRange
|
||||||
| KnownBoundMethodType::ConstraintSetAlways
|
| KnownBoundMethodType::ConstraintSetAlways
|
||||||
| KnownBoundMethodType::ConstraintSetNever
|
| KnownBoundMethodType::ConstraintSetNever
|
||||||
|
|
@ -7640,7 +7646,7 @@ impl<'db> Type<'db> {
|
||||||
Self::AlwaysFalsy => Type::SpecialForm(SpecialFormType::AlwaysFalsy).definition(db),
|
Self::AlwaysFalsy => Type::SpecialForm(SpecialFormType::AlwaysFalsy).definition(db),
|
||||||
|
|
||||||
// These types have no definition
|
// These types have no definition
|
||||||
Self::Dynamic(DynamicType::Divergent(_) | DynamicType::Todo(_) | DynamicType::TodoTypeAlias | DynamicType::TodoUnpack)
|
Self::Dynamic(DynamicType::Divergent(_) | DynamicType::Todo(_) | DynamicType::TodoUnpack)
|
||||||
| Self::Callable(_)
|
| Self::Callable(_)
|
||||||
| Self::TypeIs(_) => None,
|
| Self::TypeIs(_) => None,
|
||||||
}
|
}
|
||||||
|
|
@ -8044,6 +8050,9 @@ pub enum KnownInstanceType<'db> {
|
||||||
/// An instance of `typing.GenericAlias` representing a `Callable[...]` expression.
|
/// An instance of `typing.GenericAlias` representing a `Callable[...]` expression.
|
||||||
Callable(CallableType<'db>),
|
Callable(CallableType<'db>),
|
||||||
|
|
||||||
|
/// A literal string which is the right-hand side of a PEP 613 `TypeAlias`.
|
||||||
|
LiteralStringAlias(InternedType<'db>),
|
||||||
|
|
||||||
/// An identity callable created with `typing.NewType(name, base)`, which behaves like a
|
/// An identity callable created with `typing.NewType(name, base)`, which behaves like a
|
||||||
/// subtype of `base` in type expressions. See the `struct NewType` payload for an example.
|
/// subtype of `base` in type expressions. See the `struct NewType` payload for an example.
|
||||||
NewType(NewType<'db>),
|
NewType(NewType<'db>),
|
||||||
|
|
@ -8083,7 +8092,8 @@ fn walk_known_instance_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
|
||||||
}
|
}
|
||||||
KnownInstanceType::Literal(ty)
|
KnownInstanceType::Literal(ty)
|
||||||
| KnownInstanceType::Annotated(ty)
|
| KnownInstanceType::Annotated(ty)
|
||||||
| KnownInstanceType::TypeGenericAlias(ty) => {
|
| KnownInstanceType::TypeGenericAlias(ty)
|
||||||
|
| KnownInstanceType::LiteralStringAlias(ty) => {
|
||||||
visitor.visit_type(db, ty.inner(db));
|
visitor.visit_type(db, ty.inner(db));
|
||||||
}
|
}
|
||||||
KnownInstanceType::Callable(callable) => {
|
KnownInstanceType::Callable(callable) => {
|
||||||
|
|
@ -8127,6 +8137,9 @@ impl<'db> KnownInstanceType<'db> {
|
||||||
Self::Annotated(ty) => Self::Annotated(ty.normalized_impl(db, visitor)),
|
Self::Annotated(ty) => Self::Annotated(ty.normalized_impl(db, visitor)),
|
||||||
Self::TypeGenericAlias(ty) => Self::TypeGenericAlias(ty.normalized_impl(db, visitor)),
|
Self::TypeGenericAlias(ty) => Self::TypeGenericAlias(ty.normalized_impl(db, visitor)),
|
||||||
Self::Callable(callable) => Self::Callable(callable.normalized_impl(db, visitor)),
|
Self::Callable(callable) => Self::Callable(callable.normalized_impl(db, visitor)),
|
||||||
|
Self::LiteralStringAlias(ty) => {
|
||||||
|
Self::LiteralStringAlias(ty.normalized_impl(db, visitor))
|
||||||
|
}
|
||||||
Self::NewType(newtype) => Self::NewType(
|
Self::NewType(newtype) => Self::NewType(
|
||||||
newtype
|
newtype
|
||||||
.map_base_class_type(db, |class_type| class_type.normalized_impl(db, visitor)),
|
.map_base_class_type(db, |class_type| class_type.normalized_impl(db, visitor)),
|
||||||
|
|
@ -8162,6 +8175,7 @@ impl<'db> KnownInstanceType<'db> {
|
||||||
| Self::Annotated(_)
|
| Self::Annotated(_)
|
||||||
| Self::TypeGenericAlias(_)
|
| Self::TypeGenericAlias(_)
|
||||||
| Self::Callable(_) => KnownClass::GenericAlias,
|
| Self::Callable(_) => KnownClass::GenericAlias,
|
||||||
|
Self::LiteralStringAlias(_) => KnownClass::Str,
|
||||||
Self::NewType(_) => KnownClass::NewType,
|
Self::NewType(_) => KnownClass::NewType,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -8265,6 +8279,7 @@ impl<'db> KnownInstanceType<'db> {
|
||||||
KnownInstanceType::TypeGenericAlias(_) | KnownInstanceType::Callable(_) => {
|
KnownInstanceType::TypeGenericAlias(_) | KnownInstanceType::Callable(_) => {
|
||||||
f.write_str("GenericAlias")
|
f.write_str("GenericAlias")
|
||||||
}
|
}
|
||||||
|
KnownInstanceType::LiteralStringAlias(_) => f.write_str("str"),
|
||||||
KnownInstanceType::NewType(declaration) => {
|
KnownInstanceType::NewType(declaration) => {
|
||||||
write!(f, "<NewType pseudo-class '{}'>", declaration.name(self.db))
|
write!(f, "<NewType pseudo-class '{}'>", declaration.name(self.db))
|
||||||
}
|
}
|
||||||
|
|
@ -8306,9 +8321,6 @@ pub enum DynamicType<'db> {
|
||||||
///
|
///
|
||||||
/// This variant should be created with the `todo_type!` macro.
|
/// This variant should be created with the `todo_type!` macro.
|
||||||
Todo(TodoType),
|
Todo(TodoType),
|
||||||
/// A special Todo-variant for type aliases declared using `typing.TypeAlias`.
|
|
||||||
/// A temporary variant to detect and special-case the handling of these aliases in autocomplete suggestions.
|
|
||||||
TodoTypeAlias,
|
|
||||||
/// A special Todo-variant for `Unpack[Ts]`, so that we can treat it specially in `Generic[Unpack[Ts]]`
|
/// A special Todo-variant for `Unpack[Ts]`, so that we can treat it specially in `Generic[Unpack[Ts]]`
|
||||||
TodoUnpack,
|
TodoUnpack,
|
||||||
/// A type that is determined to be divergent during type inference for a recursive function.
|
/// A type that is determined to be divergent during type inference for a recursive function.
|
||||||
|
|
@ -8340,13 +8352,6 @@ impl std::fmt::Display for DynamicType<'_> {
|
||||||
f.write_str("@Todo")
|
f.write_str("@Todo")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DynamicType::TodoTypeAlias => {
|
|
||||||
if cfg!(debug_assertions) {
|
|
||||||
f.write_str("@Todo(Support for `typing.TypeAlias`)")
|
|
||||||
} else {
|
|
||||||
f.write_str("@Todo")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DynamicType::Divergent(_) => f.write_str("Divergent"),
|
DynamicType::Divergent(_) => f.write_str("Divergent"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -8509,6 +8514,9 @@ enum InvalidTypeExpression<'db> {
|
||||||
Specialization,
|
Specialization,
|
||||||
/// Same for `typing.TypedDict`
|
/// Same for `typing.TypedDict`
|
||||||
TypedDict,
|
TypedDict,
|
||||||
|
/// Same for `typing.TypeAlias`, anywhere except for as the sole annotation on an annotated
|
||||||
|
/// assignment
|
||||||
|
TypeAlias,
|
||||||
/// Type qualifiers are always invalid in *type expressions*,
|
/// Type qualifiers are always invalid in *type expressions*,
|
||||||
/// but these ones are okay with 0 arguments in *annotation expressions*
|
/// but these ones are okay with 0 arguments in *annotation expressions*
|
||||||
TypeQualifier(SpecialFormType),
|
TypeQualifier(SpecialFormType),
|
||||||
|
|
@ -8570,6 +8578,11 @@ impl<'db> InvalidTypeExpression<'db> {
|
||||||
"The special form `typing.TypedDict` is not allowed in type expressions. \
|
"The special form `typing.TypedDict` is not allowed in type expressions. \
|
||||||
Did you mean to use a concrete TypedDict or `collections.abc.Mapping[str, object]` instead?")
|
Did you mean to use a concrete TypedDict or `collections.abc.Mapping[str, object]` instead?")
|
||||||
}
|
}
|
||||||
|
InvalidTypeExpression::TypeAlias => {
|
||||||
|
f.write_str(
|
||||||
|
"`typing.TypeAlias` is only allowed as the sole annotation on an annotated assignment",
|
||||||
|
)
|
||||||
|
}
|
||||||
InvalidTypeExpression::TypeQualifier(qualifier) => write!(
|
InvalidTypeExpression::TypeQualifier(qualifier) => write!(
|
||||||
f,
|
f,
|
||||||
"Type qualifier `{qualifier}` is not allowed in type expressions \
|
"Type qualifier `{qualifier}` is not allowed in type expressions \
|
||||||
|
|
@ -11077,8 +11090,6 @@ pub enum KnownBoundMethodType<'db> {
|
||||||
/// this allows us to understand statically known branches for common tests such as
|
/// this allows us to understand statically known branches for common tests such as
|
||||||
/// `if sys.platform.startswith("freebsd")`.
|
/// `if sys.platform.startswith("freebsd")`.
|
||||||
StrStartswith(StringLiteralType<'db>),
|
StrStartswith(StringLiteralType<'db>),
|
||||||
/// Method wrapper for `Path.open`,
|
|
||||||
PathOpen,
|
|
||||||
|
|
||||||
// ConstraintSet methods
|
// ConstraintSet methods
|
||||||
ConstraintSetRange,
|
ConstraintSetRange,
|
||||||
|
|
@ -11113,8 +11124,7 @@ pub(super) fn walk_method_wrapper_type<'db, V: visitor::TypeVisitor<'db> + ?Size
|
||||||
KnownBoundMethodType::StrStartswith(string_literal) => {
|
KnownBoundMethodType::StrStartswith(string_literal) => {
|
||||||
visitor.visit_type(db, Type::StringLiteral(string_literal));
|
visitor.visit_type(db, Type::StringLiteral(string_literal));
|
||||||
}
|
}
|
||||||
KnownBoundMethodType::PathOpen
|
KnownBoundMethodType::ConstraintSetRange
|
||||||
| KnownBoundMethodType::ConstraintSetRange
|
|
||||||
| KnownBoundMethodType::ConstraintSetAlways
|
| KnownBoundMethodType::ConstraintSetAlways
|
||||||
| KnownBoundMethodType::ConstraintSetNever
|
| KnownBoundMethodType::ConstraintSetNever
|
||||||
| KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_)
|
| KnownBoundMethodType::ConstraintSetImpliesSubtypeOf(_)
|
||||||
|
|
@ -11172,8 +11182,7 @@ impl<'db> KnownBoundMethodType<'db> {
|
||||||
ConstraintSet::from(self == other)
|
ConstraintSet::from(self == other)
|
||||||
}
|
}
|
||||||
|
|
||||||
(KnownBoundMethodType::PathOpen, KnownBoundMethodType::PathOpen)
|
(
|
||||||
| (
|
|
||||||
KnownBoundMethodType::ConstraintSetRange,
|
KnownBoundMethodType::ConstraintSetRange,
|
||||||
KnownBoundMethodType::ConstraintSetRange,
|
KnownBoundMethodType::ConstraintSetRange,
|
||||||
)
|
)
|
||||||
|
|
@ -11208,7 +11217,6 @@ impl<'db> KnownBoundMethodType<'db> {
|
||||||
| KnownBoundMethodType::PropertyDunderGet(_)
|
| KnownBoundMethodType::PropertyDunderGet(_)
|
||||||
| KnownBoundMethodType::PropertyDunderSet(_)
|
| KnownBoundMethodType::PropertyDunderSet(_)
|
||||||
| KnownBoundMethodType::StrStartswith(_)
|
| KnownBoundMethodType::StrStartswith(_)
|
||||||
| KnownBoundMethodType::PathOpen
|
|
||||||
| KnownBoundMethodType::ConstraintSetRange
|
| KnownBoundMethodType::ConstraintSetRange
|
||||||
| KnownBoundMethodType::ConstraintSetAlways
|
| KnownBoundMethodType::ConstraintSetAlways
|
||||||
| KnownBoundMethodType::ConstraintSetNever
|
| KnownBoundMethodType::ConstraintSetNever
|
||||||
|
|
@ -11221,7 +11229,6 @@ impl<'db> KnownBoundMethodType<'db> {
|
||||||
| KnownBoundMethodType::PropertyDunderGet(_)
|
| KnownBoundMethodType::PropertyDunderGet(_)
|
||||||
| KnownBoundMethodType::PropertyDunderSet(_)
|
| KnownBoundMethodType::PropertyDunderSet(_)
|
||||||
| KnownBoundMethodType::StrStartswith(_)
|
| KnownBoundMethodType::StrStartswith(_)
|
||||||
| KnownBoundMethodType::PathOpen
|
|
||||||
| KnownBoundMethodType::ConstraintSetRange
|
| KnownBoundMethodType::ConstraintSetRange
|
||||||
| KnownBoundMethodType::ConstraintSetAlways
|
| KnownBoundMethodType::ConstraintSetAlways
|
||||||
| KnownBoundMethodType::ConstraintSetNever
|
| KnownBoundMethodType::ConstraintSetNever
|
||||||
|
|
@ -11264,8 +11271,7 @@ impl<'db> KnownBoundMethodType<'db> {
|
||||||
ConstraintSet::from(self == other)
|
ConstraintSet::from(self == other)
|
||||||
}
|
}
|
||||||
|
|
||||||
(KnownBoundMethodType::PathOpen, KnownBoundMethodType::PathOpen)
|
(
|
||||||
| (
|
|
||||||
KnownBoundMethodType::ConstraintSetRange,
|
KnownBoundMethodType::ConstraintSetRange,
|
||||||
KnownBoundMethodType::ConstraintSetRange,
|
KnownBoundMethodType::ConstraintSetRange,
|
||||||
)
|
)
|
||||||
|
|
@ -11304,7 +11310,6 @@ impl<'db> KnownBoundMethodType<'db> {
|
||||||
| KnownBoundMethodType::PropertyDunderGet(_)
|
| KnownBoundMethodType::PropertyDunderGet(_)
|
||||||
| KnownBoundMethodType::PropertyDunderSet(_)
|
| KnownBoundMethodType::PropertyDunderSet(_)
|
||||||
| KnownBoundMethodType::StrStartswith(_)
|
| KnownBoundMethodType::StrStartswith(_)
|
||||||
| KnownBoundMethodType::PathOpen
|
|
||||||
| KnownBoundMethodType::ConstraintSetRange
|
| KnownBoundMethodType::ConstraintSetRange
|
||||||
| KnownBoundMethodType::ConstraintSetAlways
|
| KnownBoundMethodType::ConstraintSetAlways
|
||||||
| KnownBoundMethodType::ConstraintSetNever
|
| KnownBoundMethodType::ConstraintSetNever
|
||||||
|
|
@ -11317,7 +11322,6 @@ impl<'db> KnownBoundMethodType<'db> {
|
||||||
| KnownBoundMethodType::PropertyDunderGet(_)
|
| KnownBoundMethodType::PropertyDunderGet(_)
|
||||||
| KnownBoundMethodType::PropertyDunderSet(_)
|
| KnownBoundMethodType::PropertyDunderSet(_)
|
||||||
| KnownBoundMethodType::StrStartswith(_)
|
| KnownBoundMethodType::StrStartswith(_)
|
||||||
| KnownBoundMethodType::PathOpen
|
|
||||||
| KnownBoundMethodType::ConstraintSetRange
|
| KnownBoundMethodType::ConstraintSetRange
|
||||||
| KnownBoundMethodType::ConstraintSetAlways
|
| KnownBoundMethodType::ConstraintSetAlways
|
||||||
| KnownBoundMethodType::ConstraintSetNever
|
| KnownBoundMethodType::ConstraintSetNever
|
||||||
|
|
@ -11344,7 +11348,6 @@ impl<'db> KnownBoundMethodType<'db> {
|
||||||
KnownBoundMethodType::PropertyDunderSet(property.normalized_impl(db, visitor))
|
KnownBoundMethodType::PropertyDunderSet(property.normalized_impl(db, visitor))
|
||||||
}
|
}
|
||||||
KnownBoundMethodType::StrStartswith(_)
|
KnownBoundMethodType::StrStartswith(_)
|
||||||
| KnownBoundMethodType::PathOpen
|
|
||||||
| KnownBoundMethodType::ConstraintSetRange
|
| KnownBoundMethodType::ConstraintSetRange
|
||||||
| KnownBoundMethodType::ConstraintSetAlways
|
| KnownBoundMethodType::ConstraintSetAlways
|
||||||
| KnownBoundMethodType::ConstraintSetNever
|
| KnownBoundMethodType::ConstraintSetNever
|
||||||
|
|
@ -11363,7 +11366,6 @@ impl<'db> KnownBoundMethodType<'db> {
|
||||||
| KnownBoundMethodType::PropertyDunderGet(_)
|
| KnownBoundMethodType::PropertyDunderGet(_)
|
||||||
| KnownBoundMethodType::PropertyDunderSet(_) => KnownClass::MethodWrapperType,
|
| KnownBoundMethodType::PropertyDunderSet(_) => KnownClass::MethodWrapperType,
|
||||||
KnownBoundMethodType::StrStartswith(_) => KnownClass::BuiltinFunctionType,
|
KnownBoundMethodType::StrStartswith(_) => KnownClass::BuiltinFunctionType,
|
||||||
KnownBoundMethodType::PathOpen => KnownClass::MethodType,
|
|
||||||
KnownBoundMethodType::ConstraintSetRange
|
KnownBoundMethodType::ConstraintSetRange
|
||||||
| KnownBoundMethodType::ConstraintSetAlways
|
| KnownBoundMethodType::ConstraintSetAlways
|
||||||
| KnownBoundMethodType::ConstraintSetNever
|
| KnownBoundMethodType::ConstraintSetNever
|
||||||
|
|
@ -11469,9 +11471,6 @@ impl<'db> KnownBoundMethodType<'db> {
|
||||||
Some(KnownClass::Bool.to_instance(db)),
|
Some(KnownClass::Bool.to_instance(db)),
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
KnownBoundMethodType::PathOpen => {
|
|
||||||
Either::Right(std::iter::once(Signature::todo("`Path.open` return type")))
|
|
||||||
}
|
|
||||||
|
|
||||||
KnownBoundMethodType::ConstraintSetRange => {
|
KnownBoundMethodType::ConstraintSetRange => {
|
||||||
Either::Right(std::iter::once(Signature::new(
|
Either::Right(std::iter::once(Signature::new(
|
||||||
|
|
|
||||||
|
|
@ -48,9 +48,7 @@ impl<'db> ClassBase<'db> {
|
||||||
ClassBase::Class(class) => class.name(db),
|
ClassBase::Class(class) => class.name(db),
|
||||||
ClassBase::Dynamic(DynamicType::Any) => "Any",
|
ClassBase::Dynamic(DynamicType::Any) => "Any",
|
||||||
ClassBase::Dynamic(DynamicType::Unknown) => "Unknown",
|
ClassBase::Dynamic(DynamicType::Unknown) => "Unknown",
|
||||||
ClassBase::Dynamic(
|
ClassBase::Dynamic(DynamicType::Todo(_) | DynamicType::TodoUnpack) => "@Todo",
|
||||||
DynamicType::Todo(_) | DynamicType::TodoTypeAlias | DynamicType::TodoUnpack,
|
|
||||||
) => "@Todo",
|
|
||||||
ClassBase::Dynamic(DynamicType::Divergent(_)) => "Divergent",
|
ClassBase::Dynamic(DynamicType::Divergent(_)) => "Divergent",
|
||||||
ClassBase::Protocol => "Protocol",
|
ClassBase::Protocol => "Protocol",
|
||||||
ClassBase::Generic => "Generic",
|
ClassBase::Generic => "Generic",
|
||||||
|
|
@ -179,6 +177,7 @@ impl<'db> ClassBase<'db> {
|
||||||
| KnownInstanceType::Specialization(_)
|
| KnownInstanceType::Specialization(_)
|
||||||
| KnownInstanceType::UnionType(_)
|
| KnownInstanceType::UnionType(_)
|
||||||
| KnownInstanceType::Literal(_)
|
| KnownInstanceType::Literal(_)
|
||||||
|
| KnownInstanceType::LiteralStringAlias(_)
|
||||||
// A class inheriting from a newtype would make intuitive sense, but newtype
|
// A class inheriting from a newtype would make intuitive sense, but newtype
|
||||||
// wrappers are just identity callables at runtime, so this sort of inheritance
|
// wrappers are just identity callables at runtime, so this sort of inheritance
|
||||||
// doesn't work and isn't allowed.
|
// doesn't work and isn't allowed.
|
||||||
|
|
|
||||||
|
|
@ -804,9 +804,6 @@ impl<'db> FmtDetailed<'db> for DisplayRepresentation<'db> {
|
||||||
Type::KnownBoundMethod(KnownBoundMethodType::StrStartswith(_)) => {
|
Type::KnownBoundMethod(KnownBoundMethodType::StrStartswith(_)) => {
|
||||||
f.write_str("<method-wrapper `startswith` of `str` object>")
|
f.write_str("<method-wrapper `startswith` of `str` object>")
|
||||||
}
|
}
|
||||||
Type::KnownBoundMethod(KnownBoundMethodType::PathOpen) => {
|
|
||||||
f.write_str("bound method `Path.open`")
|
|
||||||
}
|
|
||||||
Type::KnownBoundMethod(KnownBoundMethodType::ConstraintSetRange) => {
|
Type::KnownBoundMethod(KnownBoundMethodType::ConstraintSetRange) => {
|
||||||
f.write_str("bound method `ConstraintSet.range`")
|
f.write_str("bound method `ConstraintSet.range`")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ use crate::types::{
|
||||||
ClassLiteral, ClassType, DeprecatedInstance, DynamicType, FindLegacyTypeVarsVisitor,
|
ClassLiteral, ClassType, DeprecatedInstance, DynamicType, FindLegacyTypeVarsVisitor,
|
||||||
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, KnownClass, KnownInstanceType,
|
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, KnownClass, KnownInstanceType,
|
||||||
NormalizedVisitor, SpecialFormType, Truthiness, Type, TypeContext, TypeMapping, TypeRelation,
|
NormalizedVisitor, SpecialFormType, Truthiness, Type, TypeContext, TypeMapping, TypeRelation,
|
||||||
UnionBuilder, binding_type, todo_type, walk_signature,
|
UnionBuilder, binding_type, walk_signature,
|
||||||
};
|
};
|
||||||
use crate::{Db, FxOrderSet, ModuleName, resolve_module};
|
use crate::{Db, FxOrderSet, ModuleName, resolve_module};
|
||||||
|
|
||||||
|
|
@ -1152,70 +1152,6 @@ fn is_instance_truthiness<'db>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return true, if the type passed as `mode` would require us to pick a non-trivial overload of
|
|
||||||
/// `builtins.open` / `os.fdopen` / `Path.open`.
|
|
||||||
fn is_mode_with_nontrivial_return_type<'db>(db: &'db dyn Db, mode: Type<'db>) -> bool {
|
|
||||||
// Return true for any mode that doesn't match typeshed's
|
|
||||||
// `OpenTextMode` type alias (<https://github.com/python/typeshed/blob/6937a9b193bfc2f0696452d58aad96d7627aa29a/stdlib/_typeshed/__init__.pyi#L220>).
|
|
||||||
mode.as_string_literal().is_none_or(|mode| {
|
|
||||||
!matches!(
|
|
||||||
mode.value(db),
|
|
||||||
"r+" | "+r"
|
|
||||||
| "rt+"
|
|
||||||
| "r+t"
|
|
||||||
| "+rt"
|
|
||||||
| "tr+"
|
|
||||||
| "t+r"
|
|
||||||
| "+tr"
|
|
||||||
| "w+"
|
|
||||||
| "+w"
|
|
||||||
| "wt+"
|
|
||||||
| "w+t"
|
|
||||||
| "+wt"
|
|
||||||
| "tw+"
|
|
||||||
| "t+w"
|
|
||||||
| "+tw"
|
|
||||||
| "a+"
|
|
||||||
| "+a"
|
|
||||||
| "at+"
|
|
||||||
| "a+t"
|
|
||||||
| "+at"
|
|
||||||
| "ta+"
|
|
||||||
| "t+a"
|
|
||||||
| "+ta"
|
|
||||||
| "x+"
|
|
||||||
| "+x"
|
|
||||||
| "xt+"
|
|
||||||
| "x+t"
|
|
||||||
| "+xt"
|
|
||||||
| "tx+"
|
|
||||||
| "t+x"
|
|
||||||
| "+tx"
|
|
||||||
| "w"
|
|
||||||
| "wt"
|
|
||||||
| "tw"
|
|
||||||
| "a"
|
|
||||||
| "at"
|
|
||||||
| "ta"
|
|
||||||
| "x"
|
|
||||||
| "xt"
|
|
||||||
| "tx"
|
|
||||||
| "r"
|
|
||||||
| "rt"
|
|
||||||
| "tr"
|
|
||||||
| "U"
|
|
||||||
| "rU"
|
|
||||||
| "Ur"
|
|
||||||
| "rtU"
|
|
||||||
| "rUt"
|
|
||||||
| "Urt"
|
|
||||||
| "trU"
|
|
||||||
| "tUr"
|
|
||||||
| "Utr"
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature_cycle_initial<'db>(
|
fn signature_cycle_initial<'db>(
|
||||||
_db: &'db dyn Db,
|
_db: &'db dyn Db,
|
||||||
_id: salsa::Id,
|
_id: salsa::Id,
|
||||||
|
|
@ -1268,16 +1204,6 @@ pub enum KnownFunction {
|
||||||
DunderImport,
|
DunderImport,
|
||||||
/// `importlib.import_module`, which returns the submodule.
|
/// `importlib.import_module`, which returns the submodule.
|
||||||
ImportModule,
|
ImportModule,
|
||||||
/// `builtins.open`
|
|
||||||
Open,
|
|
||||||
|
|
||||||
/// `os.fdopen`
|
|
||||||
Fdopen,
|
|
||||||
|
|
||||||
/// `tempfile.NamedTemporaryFile`
|
|
||||||
#[strum(serialize = "NamedTemporaryFile")]
|
|
||||||
NamedTemporaryFile,
|
|
||||||
|
|
||||||
/// `typing(_extensions).final`
|
/// `typing(_extensions).final`
|
||||||
Final,
|
Final,
|
||||||
/// `typing(_extensions).disjoint_base`
|
/// `typing(_extensions).disjoint_base`
|
||||||
|
|
@ -1376,7 +1302,6 @@ impl KnownFunction {
|
||||||
| Self::HasAttr
|
| Self::HasAttr
|
||||||
| Self::Len
|
| Self::Len
|
||||||
| Self::Repr
|
| Self::Repr
|
||||||
| Self::Open
|
|
||||||
| Self::DunderImport => module.is_builtins(),
|
| Self::DunderImport => module.is_builtins(),
|
||||||
Self::AssertType
|
Self::AssertType
|
||||||
| Self::AssertNever
|
| Self::AssertNever
|
||||||
|
|
@ -1396,12 +1321,6 @@ impl KnownFunction {
|
||||||
Self::AbstractMethod => {
|
Self::AbstractMethod => {
|
||||||
matches!(module, KnownModule::Abc)
|
matches!(module, KnownModule::Abc)
|
||||||
}
|
}
|
||||||
Self::Fdopen => {
|
|
||||||
matches!(module, KnownModule::Os)
|
|
||||||
}
|
|
||||||
Self::NamedTemporaryFile => {
|
|
||||||
matches!(module, KnownModule::Tempfile)
|
|
||||||
}
|
|
||||||
Self::Dataclass | Self::Field => {
|
Self::Dataclass | Self::Field => {
|
||||||
matches!(module, KnownModule::Dataclasses)
|
matches!(module, KnownModule::Dataclasses)
|
||||||
}
|
}
|
||||||
|
|
@ -1889,38 +1808,6 @@ impl KnownFunction {
|
||||||
|
|
||||||
overload.set_return_type(Type::module_literal(db, file, module));
|
overload.set_return_type(Type::module_literal(db, file, module));
|
||||||
}
|
}
|
||||||
|
|
||||||
KnownFunction::Open => {
|
|
||||||
// TODO: Temporary special-casing for `builtins.open` to avoid an excessive number of
|
|
||||||
// false positives in lieu of proper support for PEP-613 type aliases.
|
|
||||||
if let [_, Some(mode), ..] = parameter_types
|
|
||||||
&& is_mode_with_nontrivial_return_type(db, *mode)
|
|
||||||
{
|
|
||||||
overload.set_return_type(todo_type!("`builtins.open` return type"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
KnownFunction::Fdopen => {
|
|
||||||
// TODO: Temporary special-casing for `os.fdopen` to avoid an excessive number of
|
|
||||||
// false positives in lieu of proper support for PEP-613 type aliases.
|
|
||||||
if let [_, Some(mode), ..] = parameter_types
|
|
||||||
&& is_mode_with_nontrivial_return_type(db, *mode)
|
|
||||||
{
|
|
||||||
overload.set_return_type(todo_type!("`os.fdopen` return type"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
KnownFunction::NamedTemporaryFile => {
|
|
||||||
// TODO: Temporary special-casing for `tempfile.NamedTemporaryFile` to avoid an excessive number of
|
|
||||||
// false positives in lieu of proper support for PEP-613 type aliases.
|
|
||||||
if let [Some(mode), ..] = parameter_types
|
|
||||||
&& is_mode_with_nontrivial_return_type(db, *mode)
|
|
||||||
{
|
|
||||||
overload
|
|
||||||
.set_return_type(todo_type!("`tempfile.NamedTemporaryFile` return type"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1947,15 +1834,10 @@ pub(crate) mod tests {
|
||||||
| KnownFunction::IsInstance
|
| KnownFunction::IsInstance
|
||||||
| KnownFunction::HasAttr
|
| KnownFunction::HasAttr
|
||||||
| KnownFunction::IsSubclass
|
| KnownFunction::IsSubclass
|
||||||
| KnownFunction::Open
|
|
||||||
| KnownFunction::DunderImport => KnownModule::Builtins,
|
| KnownFunction::DunderImport => KnownModule::Builtins,
|
||||||
|
|
||||||
KnownFunction::AbstractMethod => KnownModule::Abc,
|
KnownFunction::AbstractMethod => KnownModule::Abc,
|
||||||
|
|
||||||
KnownFunction::Fdopen => KnownModule::Os,
|
|
||||||
|
|
||||||
KnownFunction::NamedTemporaryFile => KnownModule::Tempfile,
|
|
||||||
|
|
||||||
KnownFunction::Dataclass | KnownFunction::Field => KnownModule::Dataclasses,
|
KnownFunction::Dataclass | KnownFunction::Field => KnownModule::Dataclasses,
|
||||||
|
|
||||||
KnownFunction::GetattrStatic => KnownModule::Inspect,
|
KnownFunction::GetattrStatic => KnownModule::Inspect,
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ use crate::types::generics::Specialization;
|
||||||
use crate::types::signatures::Signature;
|
use crate::types::signatures::Signature;
|
||||||
use crate::types::{CallDunderError, UnionType};
|
use crate::types::{CallDunderError, UnionType};
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
ClassBase, ClassLiteral, DynamicType, KnownClass, KnownInstanceType, Type, TypeContext,
|
ClassBase, ClassLiteral, KnownClass, KnownInstanceType, Type, TypeContext,
|
||||||
TypeVarBoundOrConstraints, class::CodeGeneratorKind,
|
TypeVarBoundOrConstraints, class::CodeGeneratorKind,
|
||||||
};
|
};
|
||||||
use crate::{Db, DisplaySettings, HasType, NameKind, SemanticModel};
|
use crate::{Db, DisplaySettings, HasType, NameKind, SemanticModel};
|
||||||
|
|
@ -299,9 +299,10 @@ impl<'db> AllMembers<'db> {
|
||||||
Type::KnownInstance(
|
Type::KnownInstance(
|
||||||
KnownInstanceType::TypeVar(_)
|
KnownInstanceType::TypeVar(_)
|
||||||
| KnownInstanceType::TypeAliasType(_)
|
| KnownInstanceType::TypeAliasType(_)
|
||||||
| KnownInstanceType::UnionType(_),
|
| KnownInstanceType::UnionType(_)
|
||||||
|
| KnownInstanceType::Literal(_)
|
||||||
|
| KnownInstanceType::Annotated(_),
|
||||||
) => continue,
|
) => continue,
|
||||||
Type::Dynamic(DynamicType::TodoTypeAlias) => continue,
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -377,6 +377,11 @@ impl<'db> TypeContext<'db> {
|
||||||
annotation: self.annotation.map(f),
|
annotation: self.annotation.map(f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_typealias(&self) -> bool {
|
||||||
|
self.annotation
|
||||||
|
.is_some_and(|ty| ty.is_typealias_special_form())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the statically-known truthiness of a given expression.
|
/// Returns the statically-known truthiness of a given expression.
|
||||||
|
|
|
||||||
|
|
@ -5407,7 +5407,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
let target = assignment.target(self.module());
|
let target = assignment.target(self.module());
|
||||||
let value = assignment.value(self.module());
|
let value = assignment.value(self.module());
|
||||||
|
|
||||||
let mut declared = self.infer_annotation_expression(
|
let mut declared = self.infer_annotation_expression_allow_pep_613(
|
||||||
annotation,
|
annotation,
|
||||||
DeferredExpressionState::from(self.defer_annotations()),
|
DeferredExpressionState::from(self.defer_annotations()),
|
||||||
);
|
);
|
||||||
|
|
@ -5459,6 +5459,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
declared.inner = Type::BooleanLiteral(true);
|
declared.inner = Type::BooleanLiteral(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if this is a PEP 613 `TypeAlias`. (This must come below the SpecialForm handling
|
||||||
|
// immediately below, since that can overwrite the type to be `TypeAlias`.)
|
||||||
|
let is_pep_613_type_alias = declared.inner_type().is_typealias_special_form();
|
||||||
|
|
||||||
// Handle various singletons.
|
// Handle various singletons.
|
||||||
if let Some(name_expr) = target.as_name_expr() {
|
if let Some(name_expr) = target.as_name_expr() {
|
||||||
if let Some(special_form) =
|
if let Some(special_form) =
|
||||||
|
|
@ -5504,20 +5508,16 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// We defer the r.h.s. of PEP-613 `TypeAlias` assignments in stub files.
|
// We defer the r.h.s. of PEP-613 `TypeAlias` assignments in stub files.
|
||||||
let declared_type = declared.inner_type();
|
|
||||||
let previous_deferred_state = self.deferred_state;
|
let previous_deferred_state = self.deferred_state;
|
||||||
|
|
||||||
if matches!(
|
if is_pep_613_type_alias && self.in_stub() {
|
||||||
declared_type,
|
|
||||||
Type::SpecialForm(SpecialFormType::TypeAlias)
|
|
||||||
| Type::Dynamic(DynamicType::TodoTypeAlias)
|
|
||||||
) && self.in_stub()
|
|
||||||
{
|
|
||||||
self.deferred_state = DeferredExpressionState::Deferred;
|
self.deferred_state = DeferredExpressionState::Deferred;
|
||||||
}
|
}
|
||||||
|
|
||||||
let inferred_ty = self
|
let inferred_ty = self.infer_maybe_standalone_expression(
|
||||||
.infer_maybe_standalone_expression(value, TypeContext::new(Some(declared_type)));
|
value,
|
||||||
|
TypeContext::new(Some(declared.inner_type())),
|
||||||
|
);
|
||||||
|
|
||||||
self.deferred_state = previous_deferred_state;
|
self.deferred_state = previous_deferred_state;
|
||||||
|
|
||||||
|
|
@ -5534,17 +5534,33 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
inferred_ty
|
inferred_ty
|
||||||
};
|
};
|
||||||
|
|
||||||
self.add_declaration_with_binding(
|
if is_pep_613_type_alias {
|
||||||
target.into(),
|
self.add_declaration_with_binding(
|
||||||
definition,
|
target.into(),
|
||||||
&DeclaredAndInferredType::MightBeDifferent {
|
definition,
|
||||||
declared_ty: declared,
|
&DeclaredAndInferredType::AreTheSame(TypeAndQualifiers::declared(inferred_ty)),
|
||||||
inferred_ty,
|
);
|
||||||
},
|
} else {
|
||||||
);
|
self.add_declaration_with_binding(
|
||||||
|
target.into(),
|
||||||
|
definition,
|
||||||
|
&DeclaredAndInferredType::MightBeDifferent {
|
||||||
|
declared_ty: declared,
|
||||||
|
inferred_ty,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
self.store_expression_type(target, inferred_ty);
|
self.store_expression_type(target, inferred_ty);
|
||||||
} else {
|
} else {
|
||||||
|
if is_pep_613_type_alias {
|
||||||
|
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, annotation) {
|
||||||
|
builder.into_diagnostic(
|
||||||
|
"`TypeAlias` must be assigned a value in annotated assignments",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
declared.inner = Type::unknown();
|
||||||
|
}
|
||||||
if self.in_stub() {
|
if self.in_stub() {
|
||||||
self.add_declaration_with_binding(
|
self.add_declaration_with_binding(
|
||||||
target.into(),
|
target.into(),
|
||||||
|
|
@ -6894,7 +6910,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
}) => Type::none(self.db()),
|
}) => Type::none(self.db()),
|
||||||
ast::Expr::NumberLiteral(literal) => self.infer_number_literal_expression(literal),
|
ast::Expr::NumberLiteral(literal) => self.infer_number_literal_expression(literal),
|
||||||
ast::Expr::BooleanLiteral(literal) => self.infer_boolean_literal_expression(literal),
|
ast::Expr::BooleanLiteral(literal) => self.infer_boolean_literal_expression(literal),
|
||||||
ast::Expr::StringLiteral(literal) => self.infer_string_literal_expression(literal),
|
ast::Expr::StringLiteral(literal) => self.infer_string_literal_expression(literal, tcx),
|
||||||
ast::Expr::BytesLiteral(bytes_literal) => {
|
ast::Expr::BytesLiteral(bytes_literal) => {
|
||||||
self.infer_bytes_literal_expression(bytes_literal)
|
self.infer_bytes_literal_expression(bytes_literal)
|
||||||
}
|
}
|
||||||
|
|
@ -6916,7 +6932,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
ast::Expr::Name(name) => self.infer_name_expression(name),
|
ast::Expr::Name(name) => self.infer_name_expression(name),
|
||||||
ast::Expr::Attribute(attribute) => self.infer_attribute_expression(attribute),
|
ast::Expr::Attribute(attribute) => self.infer_attribute_expression(attribute),
|
||||||
ast::Expr::UnaryOp(unary_op) => self.infer_unary_expression(unary_op),
|
ast::Expr::UnaryOp(unary_op) => self.infer_unary_expression(unary_op),
|
||||||
ast::Expr::BinOp(binary) => self.infer_binary_expression(binary),
|
ast::Expr::BinOp(binary) => self.infer_binary_expression(binary, tcx),
|
||||||
ast::Expr::BoolOp(bool_op) => self.infer_boolean_expression(bool_op),
|
ast::Expr::BoolOp(bool_op) => self.infer_boolean_expression(bool_op),
|
||||||
ast::Expr::Compare(compare) => self.infer_compare_expression(compare),
|
ast::Expr::Compare(compare) => self.infer_compare_expression(compare),
|
||||||
ast::Expr::Subscript(subscript) => self.infer_subscript_expression(subscript),
|
ast::Expr::Subscript(subscript) => self.infer_subscript_expression(subscript),
|
||||||
|
|
@ -7011,7 +7027,18 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
Type::BooleanLiteral(*value)
|
Type::BooleanLiteral(*value)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn infer_string_literal_expression(&mut self, literal: &ast::ExprStringLiteral) -> Type<'db> {
|
fn infer_string_literal_expression(
|
||||||
|
&mut self,
|
||||||
|
literal: &ast::ExprStringLiteral,
|
||||||
|
tcx: TypeContext<'db>,
|
||||||
|
) -> Type<'db> {
|
||||||
|
if tcx.is_typealias() {
|
||||||
|
let aliased_type = self.infer_string_type_expression(literal);
|
||||||
|
return Type::KnownInstance(KnownInstanceType::LiteralStringAlias(InternedType::new(
|
||||||
|
self.db(),
|
||||||
|
aliased_type,
|
||||||
|
)));
|
||||||
|
}
|
||||||
if literal.value.len() <= Self::MAX_STRING_LITERAL_SIZE {
|
if literal.value.len() <= Self::MAX_STRING_LITERAL_SIZE {
|
||||||
Type::string_literal(self.db(), literal.value.to_str())
|
Type::string_literal(self.db(), literal.value.to_str())
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -9212,7 +9239,15 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn infer_binary_expression(&mut self, binary: &ast::ExprBinOp) -> Type<'db> {
|
fn infer_binary_expression(
|
||||||
|
&mut self,
|
||||||
|
binary: &ast::ExprBinOp,
|
||||||
|
tcx: TypeContext<'db>,
|
||||||
|
) -> Type<'db> {
|
||||||
|
if tcx.is_typealias() {
|
||||||
|
return self.infer_pep_604_union_type_alias(binary, tcx);
|
||||||
|
}
|
||||||
|
|
||||||
let ast::ExprBinOp {
|
let ast::ExprBinOp {
|
||||||
left,
|
left,
|
||||||
op,
|
op,
|
||||||
|
|
@ -9250,6 +9285,45 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn infer_pep_604_union_type_alias(
|
||||||
|
&mut self,
|
||||||
|
node: &ast::ExprBinOp,
|
||||||
|
tcx: TypeContext<'db>,
|
||||||
|
) -> Type<'db> {
|
||||||
|
let ast::ExprBinOp {
|
||||||
|
left,
|
||||||
|
op,
|
||||||
|
right,
|
||||||
|
range: _,
|
||||||
|
node_index: _,
|
||||||
|
} = node;
|
||||||
|
|
||||||
|
if *op != ast::Operator::BitOr {
|
||||||
|
// TODO diagnostic?
|
||||||
|
return Type::unknown();
|
||||||
|
}
|
||||||
|
|
||||||
|
let left_ty = self.infer_expression(left, tcx);
|
||||||
|
let right_ty = self.infer_expression(right, tcx);
|
||||||
|
|
||||||
|
// TODO this is overly aggressive; if the operands' `__or__` does not actually return a
|
||||||
|
// `UnionType` at runtime, we should ideally not infer one here. But this is unlikely to be
|
||||||
|
// a problem in practice: it would require someone having an explicitly annotated
|
||||||
|
// `TypeAlias`, which uses `X | Y` syntax, where the returned type is not actually a union.
|
||||||
|
// And attempting to enforce this more tightly showed a lot of potential false positives in
|
||||||
|
// the ecosystem.
|
||||||
|
if left_ty.is_equivalent_to(self.db(), right_ty) {
|
||||||
|
left_ty
|
||||||
|
} else {
|
||||||
|
UnionTypeInstance::from_value_expression_types(
|
||||||
|
self.db(),
|
||||||
|
[left_ty, right_ty],
|
||||||
|
self.scope(),
|
||||||
|
self.typevar_binding_context,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn infer_binary_expression_type(
|
fn infer_binary_expression_type(
|
||||||
&mut self,
|
&mut self,
|
||||||
node: AnyNodeRef<'_>,
|
node: AnyNodeRef<'_>,
|
||||||
|
|
@ -9325,20 +9399,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
(unknown @ Type::Dynamic(DynamicType::Unknown), _, _)
|
(unknown @ Type::Dynamic(DynamicType::Unknown), _, _)
|
||||||
| (_, unknown @ Type::Dynamic(DynamicType::Unknown), _) => Some(unknown),
|
| (_, unknown @ Type::Dynamic(DynamicType::Unknown), _) => Some(unknown),
|
||||||
|
|
||||||
(
|
(todo @ Type::Dynamic(DynamicType::Todo(_) | DynamicType::TodoUnpack), _, _)
|
||||||
todo @ Type::Dynamic(
|
| (_, todo @ Type::Dynamic(DynamicType::Todo(_) | DynamicType::TodoUnpack), _) => {
|
||||||
DynamicType::Todo(_) | DynamicType::TodoUnpack | DynamicType::TodoTypeAlias,
|
Some(todo)
|
||||||
),
|
}
|
||||||
_,
|
|
||||||
_,
|
|
||||||
)
|
|
||||||
| (
|
|
||||||
_,
|
|
||||||
todo @ Type::Dynamic(
|
|
||||||
DynamicType::Todo(_) | DynamicType::TodoUnpack | DynamicType::TodoTypeAlias,
|
|
||||||
),
|
|
||||||
_,
|
|
||||||
) => Some(todo),
|
|
||||||
|
|
||||||
(Type::Never, _, _) | (_, Type::Never, _) => Some(Type::Never),
|
(Type::Never, _, _) | (_, Type::Never, _) => Some(Type::Never),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,12 @@ use crate::types::{
|
||||||
KnownClass, SpecialFormType, Type, TypeAndQualifiers, TypeContext, TypeQualifiers, todo_type,
|
KnownClass, SpecialFormType, Type, TypeAndQualifiers, TypeContext, TypeQualifiers, todo_type,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
enum PEP613Policy {
|
||||||
|
Allowed,
|
||||||
|
Disallowed,
|
||||||
|
}
|
||||||
|
|
||||||
/// Annotation expressions.
|
/// Annotation expressions.
|
||||||
impl<'db> TypeInferenceBuilder<'db, '_> {
|
impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||||
/// Infer the type of an annotation expression with the given [`DeferredExpressionState`].
|
/// Infer the type of an annotation expression with the given [`DeferredExpressionState`].
|
||||||
|
|
@ -18,21 +24,17 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||||
annotation: &ast::Expr,
|
annotation: &ast::Expr,
|
||||||
deferred_state: DeferredExpressionState,
|
deferred_state: DeferredExpressionState,
|
||||||
) -> TypeAndQualifiers<'db> {
|
) -> TypeAndQualifiers<'db> {
|
||||||
// `DeferredExpressionState::InStringAnnotation` takes precedence over other deferred states.
|
self.infer_annotation_expression_inner(annotation, deferred_state, PEP613Policy::Disallowed)
|
||||||
// However, if it's not a stringified annotation, we must still ensure that annotation expressions
|
}
|
||||||
// are always deferred in stub files.
|
|
||||||
let state = if deferred_state.in_string_annotation() {
|
|
||||||
deferred_state
|
|
||||||
} else if self.in_stub() {
|
|
||||||
DeferredExpressionState::Deferred
|
|
||||||
} else {
|
|
||||||
deferred_state
|
|
||||||
};
|
|
||||||
|
|
||||||
let previous_deferred_state = std::mem::replace(&mut self.deferred_state, state);
|
/// Infer the type of an annotation expression with the given [`DeferredExpressionState`],
|
||||||
let annotation_ty = self.infer_annotation_expression_impl(annotation);
|
/// allowing a PEP 613 `typing.TypeAlias` annotation.
|
||||||
self.deferred_state = previous_deferred_state;
|
pub(super) fn infer_annotation_expression_allow_pep_613(
|
||||||
annotation_ty
|
&mut self,
|
||||||
|
annotation: &ast::Expr,
|
||||||
|
deferred_state: DeferredExpressionState,
|
||||||
|
) -> TypeAndQualifiers<'db> {
|
||||||
|
self.infer_annotation_expression_inner(annotation, deferred_state, PEP613Policy::Allowed)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Similar to [`infer_annotation_expression`], but accepts an optional annotation expression
|
/// Similar to [`infer_annotation_expression`], but accepts an optional annotation expression
|
||||||
|
|
@ -47,17 +49,42 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||||
annotation.map(|expr| self.infer_annotation_expression(expr, deferred_state))
|
annotation.map(|expr| self.infer_annotation_expression(expr, deferred_state))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn infer_annotation_expression_inner(
|
||||||
|
&mut self,
|
||||||
|
annotation: &ast::Expr,
|
||||||
|
deferred_state: DeferredExpressionState,
|
||||||
|
pep_613_policy: PEP613Policy,
|
||||||
|
) -> TypeAndQualifiers<'db> {
|
||||||
|
// `DeferredExpressionState::InStringAnnotation` takes precedence over other deferred states.
|
||||||
|
// However, if it's not a stringified annotation, we must still ensure that annotation expressions
|
||||||
|
// are always deferred in stub files.
|
||||||
|
let state = if deferred_state.in_string_annotation() {
|
||||||
|
deferred_state
|
||||||
|
} else if self.in_stub() {
|
||||||
|
DeferredExpressionState::Deferred
|
||||||
|
} else {
|
||||||
|
deferred_state
|
||||||
|
};
|
||||||
|
|
||||||
|
let previous_deferred_state = std::mem::replace(&mut self.deferred_state, state);
|
||||||
|
let annotation_ty = self.infer_annotation_expression_impl(annotation, pep_613_policy);
|
||||||
|
self.deferred_state = previous_deferred_state;
|
||||||
|
annotation_ty
|
||||||
|
}
|
||||||
|
|
||||||
/// Implementation of [`infer_annotation_expression`].
|
/// Implementation of [`infer_annotation_expression`].
|
||||||
///
|
///
|
||||||
/// [`infer_annotation_expression`]: TypeInferenceBuilder::infer_annotation_expression
|
/// [`infer_annotation_expression`]: TypeInferenceBuilder::infer_annotation_expression
|
||||||
fn infer_annotation_expression_impl(
|
fn infer_annotation_expression_impl(
|
||||||
&mut self,
|
&mut self,
|
||||||
annotation: &ast::Expr,
|
annotation: &ast::Expr,
|
||||||
|
pep_613_policy: PEP613Policy,
|
||||||
) -> TypeAndQualifiers<'db> {
|
) -> TypeAndQualifiers<'db> {
|
||||||
fn infer_name_or_attribute<'db>(
|
fn infer_name_or_attribute<'db>(
|
||||||
ty: Type<'db>,
|
ty: Type<'db>,
|
||||||
annotation: &ast::Expr,
|
annotation: &ast::Expr,
|
||||||
builder: &TypeInferenceBuilder<'db, '_>,
|
builder: &TypeInferenceBuilder<'db, '_>,
|
||||||
|
pep_613_policy: PEP613Policy,
|
||||||
) -> TypeAndQualifiers<'db> {
|
) -> TypeAndQualifiers<'db> {
|
||||||
match ty {
|
match ty {
|
||||||
Type::SpecialForm(SpecialFormType::ClassVar) => TypeAndQualifiers::new(
|
Type::SpecialForm(SpecialFormType::ClassVar) => TypeAndQualifiers::new(
|
||||||
|
|
@ -85,6 +112,24 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||||
TypeOrigin::Declared,
|
TypeOrigin::Declared,
|
||||||
TypeQualifiers::READ_ONLY,
|
TypeQualifiers::READ_ONLY,
|
||||||
),
|
),
|
||||||
|
Type::SpecialForm(SpecialFormType::TypeAlias)
|
||||||
|
if pep_613_policy == PEP613Policy::Allowed =>
|
||||||
|
{
|
||||||
|
TypeAndQualifiers::declared(ty)
|
||||||
|
}
|
||||||
|
// Conditional import of `typing.TypeAlias` or `typing_extensions.TypeAlias` on a
|
||||||
|
// Python version where the former doesn't exist.
|
||||||
|
Type::Union(union)
|
||||||
|
if pep_613_policy == PEP613Policy::Allowed
|
||||||
|
&& union.elements(builder.db()).iter().all(|ty| {
|
||||||
|
matches!(
|
||||||
|
ty,
|
||||||
|
Type::SpecialForm(SpecialFormType::TypeAlias) | Type::Dynamic(_)
|
||||||
|
)
|
||||||
|
}) =>
|
||||||
|
{
|
||||||
|
TypeAndQualifiers::declared(Type::SpecialForm(SpecialFormType::TypeAlias))
|
||||||
|
}
|
||||||
Type::ClassLiteral(class) if class.is_known(builder.db(), KnownClass::InitVar) => {
|
Type::ClassLiteral(class) if class.is_known(builder.db(), KnownClass::InitVar) => {
|
||||||
if let Some(builder) =
|
if let Some(builder) =
|
||||||
builder.context.report_lint(&INVALID_TYPE_FORM, annotation)
|
builder.context.report_lint(&INVALID_TYPE_FORM, annotation)
|
||||||
|
|
@ -148,6 +193,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||||
self.infer_attribute_expression(attribute),
|
self.infer_attribute_expression(attribute),
|
||||||
annotation,
|
annotation,
|
||||||
self,
|
self,
|
||||||
|
pep_613_policy,
|
||||||
),
|
),
|
||||||
ast::ExprContext::Invalid => TypeAndQualifiers::declared(Type::unknown()),
|
ast::ExprContext::Invalid => TypeAndQualifiers::declared(Type::unknown()),
|
||||||
ast::ExprContext::Store | ast::ExprContext::Del => TypeAndQualifiers::declared(
|
ast::ExprContext::Store | ast::ExprContext::Del => TypeAndQualifiers::declared(
|
||||||
|
|
@ -156,9 +202,12 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||||
},
|
},
|
||||||
|
|
||||||
ast::Expr::Name(name) => match name.ctx {
|
ast::Expr::Name(name) => match name.ctx {
|
||||||
ast::ExprContext::Load => {
|
ast::ExprContext::Load => infer_name_or_attribute(
|
||||||
infer_name_or_attribute(self.infer_name_expression(name), annotation, self)
|
self.infer_name_expression(name),
|
||||||
}
|
annotation,
|
||||||
|
self,
|
||||||
|
pep_613_policy,
|
||||||
|
),
|
||||||
ast::ExprContext::Invalid => TypeAndQualifiers::declared(Type::unknown()),
|
ast::ExprContext::Invalid => TypeAndQualifiers::declared(Type::unknown()),
|
||||||
ast::ExprContext::Store | ast::ExprContext::Del => TypeAndQualifiers::declared(
|
ast::ExprContext::Store | ast::ExprContext::Del => TypeAndQualifiers::declared(
|
||||||
todo_type!("Name expression annotation in Store/Del context"),
|
todo_type!("Name expression annotation in Store/Del context"),
|
||||||
|
|
@ -188,8 +237,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||||
self.infer_expression(element, TypeContext::default());
|
self.infer_expression(element, TypeContext::default());
|
||||||
}
|
}
|
||||||
|
|
||||||
let inner_annotation_ty =
|
let inner_annotation_ty = self.infer_annotation_expression_impl(
|
||||||
self.infer_annotation_expression_impl(inner_annotation);
|
inner_annotation,
|
||||||
|
PEP613Policy::Disallowed,
|
||||||
|
);
|
||||||
|
|
||||||
self.store_expression_type(slice, inner_annotation_ty.inner_type());
|
self.store_expression_type(slice, inner_annotation_ty.inner_type());
|
||||||
inner_annotation_ty
|
inner_annotation_ty
|
||||||
|
|
@ -202,7 +253,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
report_invalid_arguments_to_annotated(&self.context, subscript);
|
report_invalid_arguments_to_annotated(&self.context, subscript);
|
||||||
self.infer_annotation_expression_impl(slice)
|
self.infer_annotation_expression_impl(slice, PEP613Policy::Disallowed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Type::SpecialForm(
|
Type::SpecialForm(
|
||||||
|
|
@ -219,8 +270,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||||
};
|
};
|
||||||
let num_arguments = arguments.len();
|
let num_arguments = arguments.len();
|
||||||
let type_and_qualifiers = if num_arguments == 1 {
|
let type_and_qualifiers = if num_arguments == 1 {
|
||||||
let mut type_and_qualifiers =
|
let mut type_and_qualifiers = self
|
||||||
self.infer_annotation_expression_impl(slice);
|
.infer_annotation_expression_impl(slice, PEP613Policy::Disallowed);
|
||||||
|
|
||||||
match type_qualifier {
|
match type_qualifier {
|
||||||
SpecialFormType::ClassVar => {
|
SpecialFormType::ClassVar => {
|
||||||
|
|
@ -243,7 +294,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||||
type_and_qualifiers
|
type_and_qualifiers
|
||||||
} else {
|
} else {
|
||||||
for element in arguments {
|
for element in arguments {
|
||||||
self.infer_annotation_expression_impl(element);
|
self.infer_annotation_expression_impl(
|
||||||
|
element,
|
||||||
|
PEP613Policy::Disallowed,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if let Some(builder) =
|
if let Some(builder) =
|
||||||
self.context.report_lint(&INVALID_TYPE_FORM, subscript)
|
self.context.report_lint(&INVALID_TYPE_FORM, subscript)
|
||||||
|
|
@ -268,13 +322,16 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||||
};
|
};
|
||||||
let num_arguments = arguments.len();
|
let num_arguments = arguments.len();
|
||||||
let type_and_qualifiers = if num_arguments == 1 {
|
let type_and_qualifiers = if num_arguments == 1 {
|
||||||
let mut type_and_qualifiers =
|
let mut type_and_qualifiers = self
|
||||||
self.infer_annotation_expression_impl(slice);
|
.infer_annotation_expression_impl(slice, PEP613Policy::Disallowed);
|
||||||
type_and_qualifiers.add_qualifier(TypeQualifiers::INIT_VAR);
|
type_and_qualifiers.add_qualifier(TypeQualifiers::INIT_VAR);
|
||||||
type_and_qualifiers
|
type_and_qualifiers
|
||||||
} else {
|
} else {
|
||||||
for element in arguments {
|
for element in arguments {
|
||||||
self.infer_annotation_expression_impl(element);
|
self.infer_annotation_expression_impl(
|
||||||
|
element,
|
||||||
|
PEP613Policy::Disallowed,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if let Some(builder) =
|
if let Some(builder) =
|
||||||
self.context.report_lint(&INVALID_TYPE_FORM, subscript)
|
self.context.report_lint(&INVALID_TYPE_FORM, subscript)
|
||||||
|
|
|
||||||
|
|
@ -147,7 +147,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||||
}
|
}
|
||||||
// anything else is an invalid annotation:
|
// anything else is an invalid annotation:
|
||||||
op => {
|
op => {
|
||||||
self.infer_binary_expression(binary);
|
self.infer_binary_expression(binary, TypeContext::default());
|
||||||
if let Some(mut diag) = self.report_invalid_type_expression(
|
if let Some(mut diag) = self.report_invalid_type_expression(
|
||||||
expression,
|
expression,
|
||||||
format_args!(
|
format_args!(
|
||||||
|
|
@ -518,7 +518,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Infer the type of a string type expression.
|
/// Infer the type of a string type expression.
|
||||||
fn infer_string_type_expression(&mut self, string: &ast::ExprStringLiteral) -> Type<'db> {
|
pub(super) fn infer_string_type_expression(
|
||||||
|
&mut self,
|
||||||
|
string: &ast::ExprStringLiteral,
|
||||||
|
) -> Type<'db> {
|
||||||
match parse_string_annotation(&self.context, string) {
|
match parse_string_annotation(&self.context, string) {
|
||||||
Some(parsed) => {
|
Some(parsed) => {
|
||||||
// String annotations are always evaluated in the deferred context.
|
// String annotations are always evaluated in the deferred context.
|
||||||
|
|
@ -843,6 +846,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||||
self.infer_type_expression(slice);
|
self.infer_type_expression(slice);
|
||||||
todo_type!("Generic manual PEP-695 type alias")
|
todo_type!("Generic manual PEP-695 type alias")
|
||||||
}
|
}
|
||||||
|
KnownInstanceType::LiteralStringAlias(_) => {
|
||||||
|
self.infer_type_expression(slice);
|
||||||
|
todo_type!("Generic stringified PEP-613 type alias")
|
||||||
|
}
|
||||||
KnownInstanceType::UnionType(_) => {
|
KnownInstanceType::UnionType(_) => {
|
||||||
self.infer_type_expression(slice);
|
self.infer_type_expression(slice);
|
||||||
todo_type!("Generic specialization of types.UnionType")
|
todo_type!("Generic specialization of types.UnionType")
|
||||||
|
|
|
||||||
|
|
@ -269,9 +269,6 @@ fn dynamic_elements_ordering(left: DynamicType, right: DynamicType) -> Ordering
|
||||||
(DynamicType::TodoUnpack, _) => Ordering::Less,
|
(DynamicType::TodoUnpack, _) => Ordering::Less,
|
||||||
(_, DynamicType::TodoUnpack) => Ordering::Greater,
|
(_, DynamicType::TodoUnpack) => Ordering::Greater,
|
||||||
|
|
||||||
(DynamicType::TodoTypeAlias, _) => Ordering::Less,
|
|
||||||
(_, DynamicType::TodoTypeAlias) => Ordering::Greater,
|
|
||||||
|
|
||||||
(DynamicType::Divergent(left), DynamicType::Divergent(right)) => {
|
(DynamicType::Divergent(left), DynamicType::Divergent(right)) => {
|
||||||
left.scope.cmp(&right.scope)
|
left.scope.cmp(&right.scope)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue