Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions crates/ty_ide/src/hover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -728,11 +728,11 @@ mod tests {
);

// TODO: This should render T@Alias once we create GenericContexts for type alias scopes.
assert_snapshot!(test.hover(), @r#"
typing.TypeVar("T", bound=int, default=bool)
assert_snapshot!(test.hover(), @r###"
typing.TypeVar
---------------------------------------------
```python
typing.TypeVar("T", bound=int, default=bool)
typing.TypeVar
```
---------------------------------------------
info[hover]: Hovered content is
Expand All @@ -743,7 +743,7 @@ mod tests {
| |
| source
|
"#);
"###);
}

#[test]
Expand Down
6 changes: 3 additions & 3 deletions crates/ty_ide/src/inlay_hints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -793,17 +793,17 @@ mod tests {
identity('hello')",
);

assert_snapshot!(test.inlay_hints(), @r#"
assert_snapshot!(test.inlay_hints(), @r###"
from typing import TypeVar, Generic

T[: typing.TypeVar("T")] = TypeVar([name=]'T')
T[: typing.TypeVar] = TypeVar([name=]'T')

def identity(x: T) -> T:
return x

identity([x=]42)
identity([x=]'hello')
"#);
"###);
}

#[test]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
def name_1[name_0: name_0](name_2: name_0):
try:
pass
except name_2:
pass
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
def name_1[name_0: name_0](name_2: name_0):
try:
pass
except name_2:
pass

from typing import Any

def name_2[T: Any](x: T):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ from typing import TypeVar

T = TypeVar("T")
reveal_type(type(T)) # revealed: <class 'TypeVar'>
reveal_type(T) # revealed: typing.TypeVar("T")
reveal_type(T) # revealed: typing.TypeVar
reveal_type(T.__name__) # revealed: Literal["T"]
```

Expand Down Expand Up @@ -80,7 +80,7 @@ from typing import TypeVar

T = TypeVar("T", default=int)
reveal_type(type(T)) # revealed: <class 'TypeVar'>
reveal_type(T) # revealed: typing.TypeVar("T", default=int)
reveal_type(T) # revealed: typing.TypeVar
reveal_type(T.__default__) # revealed: int
reveal_type(T.__bound__) # revealed: None
reveal_type(T.__constraints__) # revealed: tuple[()]
Expand Down Expand Up @@ -116,7 +116,7 @@ from typing import TypeVar

T = TypeVar("T", bound=int)
reveal_type(type(T)) # revealed: <class 'TypeVar'>
reveal_type(T) # revealed: typing.TypeVar("T", bound=int)
reveal_type(T) # revealed: typing.TypeVar
reveal_type(T.__bound__) # revealed: int
reveal_type(T.__constraints__) # revealed: tuple[()]

Expand All @@ -131,7 +131,7 @@ from typing import TypeVar

T = TypeVar("T", int, str)
reveal_type(type(T)) # revealed: <class 'TypeVar'>
reveal_type(T) # revealed: typing.TypeVar("T", int, str)
reveal_type(T) # revealed: typing.TypeVar
reveal_type(T.__constraints__) # revealed: tuple[int, str]

S = TypeVar("S")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,7 @@ wrong_innards: C[int] = C("five", 1)
### Some `__init__` overloads only apply to certain specializations

```py
from __future__ import annotations
from typing import overload

class C[T]:
Expand Down Expand Up @@ -541,6 +542,23 @@ class WithOverloadedMethod[T]:
reveal_type(WithOverloadedMethod[int].method)
```

## Scoping of typevars

### No back-references

Typevar bounds/constraints/defaults are lazy, but cannot refer to later typevars:

```py
# TODO error
class C[S: T, T]:
pass

class D[S: X]:
pass

X = int
```

## Cyclic class definitions

### F-bounded quantification
Expand Down Expand Up @@ -591,7 +609,7 @@ class Derived[T](list[Derived[T]]): ...

Inheritance that would result in a cyclic MRO is detected as an error.

```py
```pyi
# error: [cyclic-class-definition]
class C[T](C): ...

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

```toml
[environment]
python-version = "3.12"
python-version = "3.13"
```

[PEP 695] and Python 3.12 introduced new, more ergonomic syntax for type variables.
Expand All @@ -17,7 +17,7 @@ instances of `typing.TypeVar`, just like legacy type variables.
```py
def f[T]():
reveal_type(type(T)) # revealed: <class 'TypeVar'>
reveal_type(T) # revealed: typing.TypeVar("T")
reveal_type(T) # revealed: typing.TypeVar
reveal_type(T.__name__) # revealed: Literal["T"]
```

Expand All @@ -33,7 +33,7 @@ python-version = "3.13"
```py
def f[T = int]():
reveal_type(type(T)) # revealed: <class 'TypeVar'>
reveal_type(T) # revealed: typing.TypeVar("T", default=int)
reveal_type(T) # revealed: typing.TypeVar
reveal_type(T.__default__) # revealed: int
reveal_type(T.__bound__) # revealed: None
reveal_type(T.__constraints__) # revealed: tuple[()]
Expand Down Expand Up @@ -66,7 +66,7 @@ class Invalid[S = T]: ...
```py
def f[T: int]():
reveal_type(type(T)) # revealed: <class 'TypeVar'>
reveal_type(T) # revealed: typing.TypeVar("T", bound=int)
reveal_type(T) # revealed: typing.TypeVar
reveal_type(T.__bound__) # revealed: int
reveal_type(T.__constraints__) # revealed: tuple[()]

Expand All @@ -79,7 +79,7 @@ def g[S]():
```py
def f[T: (int, str)]():
reveal_type(type(T)) # revealed: <class 'TypeVar'>
reveal_type(T) # revealed: typing.TypeVar("T", int, str)
reveal_type(T) # revealed: typing.TypeVar
reveal_type(T.__constraints__) # revealed: tuple[int, str]
reveal_type(T.__bound__) # revealed: None

Expand Down Expand Up @@ -745,4 +745,88 @@ def constrained[T: (int, str)](x: T):
reveal_type(type(x)) # revealed: type[int] | type[str]
```

## Cycles

### Bounds and constraints

A typevar's bounds and constraints cannot be generic, cyclic or otherwise:

```py
from typing import Any

# TODO: error
def f[S, T: list[S]](x: S, y: T) -> S | T:
return x or y

# TODO: error
class C[S, T: list[S]]:
x: S
y: T

reveal_type(C[int, list[Any]]().x) # revealed: int
reveal_type(C[int, list[Any]]().y) # revealed: list[Any]

# TODO: error
def g[T: list[T]](x: T) -> T:
return x

# TODO: error
class D[T: list[T]]:
x: T

reveal_type(D[list[Any]]().x) # revealed: list[Any]

# TODO: error
def h[S, T: (list[S], str)](x: S, y: T) -> S | T:
return x or y

# TODO: error
class E[S, T: (list[S], str)]:
x: S
y: T

reveal_type(E[int, str]().x) # revealed: int
reveal_type(E[int, str]().y) # revealed: str

# TODO: error
def i[T: (list[T], str)](x: T) -> T:
return x

# TODO: error
class F[T: (list[T], str)]:
x: T

reveal_type(F[list[Any]]().x) # revealed: list[Any]
```

However, they are lazily evaluated and can cyclically refer to their own type:

```py
class G[T: list[G]]:
x: T

reveal_type(G[list[G]]().x) # revealed: list[G[Unknown]]
```

### Defaults

Defaults can be generic, but can only refer to earlier typevars:

```py
class C[T, U = T]:
x: T
y: U

reveal_type(C[int, str]().x) # revealed: int
reveal_type(C[int, str]().y) # revealed: str
reveal_type(C[int]().x) # revealed: int
reveal_type(C[int]().y) # revealed: int

# TODO: error
class D[T = T]:
x: T

reveal_type(D().x) # revealed: T@D
```

[pep 695]: https://peps.python.org/pep-0695/
1 change: 1 addition & 0 deletions crates/ty_python_semantic/resources/mdtest/protocols.md
Original file line number Diff line number Diff line change
Expand Up @@ -2051,6 +2051,7 @@ python-version = "3.12"
```

```py
from __future__ import annotations
from typing import cast, Protocol

class Iterator[T](Protocol):
Expand Down
43 changes: 43 additions & 0 deletions crates/ty_python_semantic/resources/mdtest/scopes/eager.md
Original file line number Diff line number Diff line change
Expand Up @@ -410,4 +410,47 @@ reveal_type(C.var) # revealed: int | str
x = str
```

### Annotation scopes

```toml
[environment]
python-version = "3.12"
```

#### Type alias annotation scopes are lazy

```py
type Foo = Bar

class Bar:
pass

def _(x: Foo):
if isinstance(x, Bar):
reveal_type(x) # revealed: Bar
else:
reveal_type(x) # revealed: Never
```

#### Type-param scopes are eager, but bounds/constraints are deferred

```py
# error: [unresolved-reference]
class D[T](Bar):
pass

class E[T: Bar]:
pass

# error: [unresolved-reference]
def g[T](x: Bar):
pass

def h[T: Bar](x: T):
pass

class Bar:
pass
```

[generators]: https://docs.python.org/3/reference/expressions.html#generator-expressions
Loading
Loading