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
31 changes: 15 additions & 16 deletions crates/ty_ide/src/hover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,14 +312,14 @@ mod tests {
This is such a great class!!

Don't you know?

Everyone loves my class!!

'''
def __init__(self, val):
"""initializes MyClass (perfectly)"""
self.val = val

def my_method(self, a, b):
'''This is such a great func!!

Expand Down Expand Up @@ -379,14 +379,14 @@ mod tests {
This is such a great class!!

Don't you know?

Everyone loves my class!!

'''
def __init__(self, val):
"""initializes MyClass (perfectly)"""
self.val = val

def my_method(self, a, b):
'''This is such a great func!!

Expand Down Expand Up @@ -444,14 +444,14 @@ mod tests {
This is such a great class!!

Don't you know?

Everyone loves my class!!

'''
def __init__(self, val):
"""initializes MyClass (perfectly)"""
self.val = val

def my_method(self, a, b):
'''This is such a great func!!

Expand Down Expand Up @@ -505,7 +505,7 @@ mod tests {
This is such a great class!!

Don't you know?

Everyone loves my class!!

'''
Expand Down Expand Up @@ -562,13 +562,13 @@ mod tests {
This is such a great class!!

Don't you know?

Everyone loves my class!!

'''
def __init__(self, val):
self.val = val

def my_method(self, a, b):
'''This is such a great func!!

Expand Down Expand Up @@ -628,14 +628,14 @@ mod tests {
This is such a great class!!

Don't you know?

Everyone loves my class!!

'''
def __init__(self, val):
"""initializes MyClass (perfectly)"""
self.val = val

def my_method(self, a, b):
'''This is such a great func!!

Expand Down Expand Up @@ -1589,12 +1589,11 @@ def ab(a: int, *, c: int):
"#,
);

// TODO: This should render T@Alias once we create GenericContexts for type alias scopes.
assert_snapshot!(test.hover(), @r"
typing.TypeVar
T@Alias
---------------------------------------------
```python
typing.TypeVar
T@Alias
```
---------------------------------------------
info[hover]: Hovered content is
Expand Down Expand Up @@ -1875,9 +1874,9 @@ def ab(a: int, *, c: int):
def foo(a: str | None, b):
'''
My cool func

Args:
a: hopefully a string, right?!
a: hopefully a string, right?!
'''
if a is not None:
print(a<CURSOR>)
Expand Down
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also have resources/mdtest/pep695_type_aliases.md. Should these two files be merged?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly, although this file is specific to generic PEP 695 aliases.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. And I don't care too much about the structure/layout of the tests. Just wanted do let you know that there are some generics-related tests in that other file as well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I think we can look at this as a follow-up.

Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# Generic type aliases: PEP 695 syntax

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

## Defining a generic alias

At its simplest, to define a type alias using PEP 695 syntax, you add a list of `TypeVar`s,
`ParamSpec`s or `TypeVarTuple`s after the alias name.

```py
from ty_extensions import generic_context

type SingleTypevar[T] = ...
type MultipleTypevars[T, S] = ...
type SingleParamSpec[**P] = ...
type TypeVarAndParamSpec[T, **P] = ...
type SingleTypeVarTuple[*Ts] = ...
type TypeVarAndTypeVarTuple[T, *Ts] = ...

# revealed: tuple[T@SingleTypevar]
reveal_type(generic_context(SingleTypevar))
# revealed: tuple[T@MultipleTypevars, S@MultipleTypevars]
reveal_type(generic_context(MultipleTypevars))

# TODO: support `ParamSpec`/`TypeVarTuple` properly
# (these should include the `ParamSpec`s and `TypeVarTuple`s in their generic contexts)
reveal_type(generic_context(SingleParamSpec)) # revealed: tuple[()]
reveal_type(generic_context(TypeVarAndParamSpec)) # revealed: tuple[T@TypeVarAndParamSpec]
reveal_type(generic_context(SingleTypeVarTuple)) # revealed: tuple[()]
reveal_type(generic_context(TypeVarAndTypeVarTuple)) # revealed: tuple[T@TypeVarAndTypeVarTuple]
```

You cannot use the same typevar more than once.

```py
# error: [invalid-syntax] "duplicate type parameter"
type RepeatedTypevar[T, T] = ...
```

## Specializing type aliases explicitly

The type parameter can be specified explicitly:

```py
from typing import Literal

type C[T] = T

def _(a: C[int], b: C[Literal[5]]):
reveal_type(a) # revealed: int
reveal_type(b) # revealed: Literal[5]
```

The specialization must match the generic types:

```py
# error: [too-many-positional-arguments] "Too many positional arguments: expected 1, got 2"
reveal_type(C[int, int]) # revealed: Unknown
```

And non-generic types cannot be specialized:

```py
type B = ...

# error: [non-subscriptable] "Cannot subscript non-generic type alias"
reveal_type(B[int]) # revealed: Unknown

# error: [non-subscriptable] "Cannot subscript non-generic type alias"
def _(b: B[int]): ...
```

If the type variable has an upper bound, the specialized type must satisfy that bound:

```py
type Bounded[T: int] = ...
type BoundedByUnion[T: int | str] = ...

class IntSubclass(int): ...

reveal_type(Bounded[int]) # revealed: Bounded[int]
reveal_type(Bounded[IntSubclass]) # revealed: Bounded[IntSubclass]

# TODO: update this diagnostic to talk about type parameters and specializations
# error: [invalid-argument-type] "Argument is incorrect: Expected `int`, found `str`"
reveal_type(Bounded[str]) # revealed: Unknown

# TODO: update this diagnostic to talk about type parameters and specializations
# error: [invalid-argument-type] "Argument is incorrect: Expected `int`, found `int | str`"
reveal_type(Bounded[int | str]) # revealed: Unknown

reveal_type(BoundedByUnion[int]) # revealed: BoundedByUnion[int]
reveal_type(BoundedByUnion[IntSubclass]) # revealed: BoundedByUnion[IntSubclass]
reveal_type(BoundedByUnion[str]) # revealed: BoundedByUnion[str]
reveal_type(BoundedByUnion[int | str]) # revealed: BoundedByUnion[int | str]
```

If the type variable is constrained, the specialized type must satisfy those constraints:

```py
type Constrained[T: (int, str)] = ...

reveal_type(Constrained[int]) # revealed: Constrained[int]

# TODO: error: [invalid-argument-type]
# TODO: revealed: Constrained[Unknown]
reveal_type(Constrained[IntSubclass]) # revealed: Constrained[IntSubclass]

reveal_type(Constrained[str]) # revealed: Constrained[str]

# TODO: error: [invalid-argument-type]
# TODO: revealed: Unknown
reveal_type(Constrained[int | str]) # revealed: Constrained[int | str]

# TODO: update this diagnostic to talk about type parameters and specializations
# error: [invalid-argument-type] "Argument is incorrect: Expected `int | str`, found `object`"
reveal_type(Constrained[object]) # revealed: Unknown
```

If the type variable has a default, it can be omitted:

```py
type WithDefault[T, U = int] = ...

reveal_type(WithDefault[str, str]) # revealed: WithDefault[str, str]
reveal_type(WithDefault[str]) # revealed: WithDefault[str, int]
```

If the type alias is not specialized explicitly, it is implicitly specialized to `Unknown`:

```py
type G[T] = list[T]

def _(g: G):
reveal_type(g) # revealed: list[Unknown]
```

Unless a type default was provided:

```py
type G[T = int] = list[T]

def _(g: G):
reveal_type(g) # revealed: list[int]
```

## Aliases are not callable

```py
type A = int
type B[T] = T

# error: [call-non-callable] "Object of type `TypeAliasType` is not callable"
reveal_type(A()) # revealed: Unknown

# error: [call-non-callable] "Object of type `GenericAlias` is not callable"
reveal_type(B[int]()) # revealed: Unknown
```

## Recursive Truthiness

Make sure we handle cycles correctly when computing the truthiness of a generic type alias:

```py
type X[T: X] = T

def _(x: X):
assert x
```
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,6 @@ You cannot use the same typevar more than once.
class RepeatedTypevar[T, T]: ...
```

You can only use typevars (TODO: or param specs or typevar tuples) in the class's generic context.

```py
# TODO: error
class GenericOfType[int]: ...
```

You can also define a generic class by inheriting from some _other_ generic class, and specializing
it with typevars. With PEP 695 syntax, you must explicitly list all of the typevars that you use in
your base classes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ T = TypeVar("T")
IntAnd = TypeAliasType("IntAndT", tuple[int, T], type_params=(T,))

def f(x: IntAnd[str]) -> None:
reveal_type(x) # revealed: @Todo(Generic PEP-695 type alias)
reveal_type(x) # revealed: @Todo(Generic manual PEP-695 type alias)
```

### Error cases
Expand Down
Loading
Loading