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
Original file line number Diff line number Diff line change
Expand Up @@ -257,3 +257,16 @@ f(5) # error: [invalid-argument-type] "Argument to function `f` is incorrect: E
def g(x: float):
f(x) # error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `Number`, found `int | float`"
```

## Invariant generic classes

We show a special diagnostic hint for invariant generic classes. For more details, see the
[`invalid_assignment_details.md`](./invalid_assignment_details.md) test.

```py
def modify(xs: list[int]):
xs.append(42)

xs: list[bool] = [True, False]
modify(xs) # error: [invalid-argument-type]
```
Original file line number Diff line number Diff line change
Expand Up @@ -248,3 +248,95 @@ class Incompatible:
def _(source: Incompatible):
target: SupportsCheck = source # error: [invalid-assignment]
```

## Invariant generic classes

We show a special diagnostic hint for invariant generic classes. For example, if you try to assign a
`list[bool]` to a `list[int]`:

```py
def _(source: list[bool]):
target: list[int] = source # error: [invalid-assignment]
```

We do the same for other invariant generic classes:

```py
from collections import ChainMap, Counter, OrderedDict, defaultdict, deque
from collections.abc import MutableSequence, MutableMapping, MutableSet

def _(source: set[bool]):
target: set[int] = source # error: [invalid-assignment]

def _(source: dict[str, bool]):
target: dict[str, int] = source # error: [invalid-assignment]

def _(source: dict[bool, str]):
target: dict[int, str] = source # error: [invalid-assignment]

def _(source: dict[bool, bool]):
target: dict[int, int] = source # error: [invalid-assignment]

def _(source: defaultdict[str, bool]):
target: defaultdict[str, int] = source # error: [invalid-assignment]

def _(source: defaultdict[bool, str]):
target: defaultdict[int, str] = source # error: [invalid-assignment]

def _(source: OrderedDict[str, bool]):
target: OrderedDict[str, int] = source # error: [invalid-assignment]

def _(source: OrderedDict[bool, str]):
target: OrderedDict[int, str] = source # error: [invalid-assignment]

def _(source: ChainMap[str, bool]):
target: ChainMap[str, int] = source # error: [invalid-assignment]

def _(source: ChainMap[bool, str]):
target: ChainMap[int, str] = source # error: [invalid-assignment]

def _(source: deque[bool]):
target: deque[int] = source # error: [invalid-assignment]

def _(source: Counter[bool]):
target: Counter[int] = source # error: [invalid-assignment]

def _(source: MutableSequence[bool]):
target: MutableSequence[int] = source # error: [invalid-assignment]
```

We also show this hint for custom invariant generic classes:

```py
from typing import Generic, TypeVar

T = TypeVar("T")

class MyContainer(Generic[T]):
value: T

def _(source: MyContainer[bool]):
target: MyContainer[int] = source # error: [invalid-assignment]
```

We do *not* show this hint if the element types themselves wouldn't be assignable:

```py
def _(source: list[int]):
target: list[str] = source # error: [invalid-assignment]
```

We do not emit any error if the collection types are covariant:

```py
from collections.abc import Sequence

def _(source: list[bool]):
target: Sequence[int] = source

def _(source: frozenset[bool]):
target: frozenset[int] = source

def _(source: tuple[bool, bool]):
target: tuple[int, int] = source
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---

---
mdtest name: invalid_argument_type.md - Invalid argument type diagnostics - Invariant generic classes
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/invalid_argument_type.md
---

# Python source files

## mdtest_snippet.py

```
1 | def modify(xs: list[int]):
2 | xs.append(42)
3 |
4 | xs: list[bool] = [True, False]
5 | modify(xs) # error: [invalid-argument-type]
```

# Diagnostics

```
error[invalid-argument-type]: Argument to function `modify` is incorrect
--> src/mdtest_snippet.py:5:8
|
4 | xs: list[bool] = [True, False]
5 | modify(xs) # error: [invalid-argument-type]
| ^^ Expected `list[int]`, found `list[bool]`
|
info: Function defined here
--> src/mdtest_snippet.py:1:5
|
1 | def modify(xs: list[int]):
| ^^^^^^ ------------- Parameter declared here
2 | xs.append(42)
|
info: `list` is invariant in its type parameter
info: Consider using the covariant supertype `collections.abc.Sequence`
info: For more information, see https://docs.astral.sh/ty/reference/typing-faq/#invariant-generics
info: rule `invalid-argument-type` is enabled by default

```
Loading
Loading