Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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