[ty] Infer types for key-based access on TypedDicts (#19763)

## Summary

This PR adds type inference for key-based access on `TypedDict`s and a
new diagnostic for invalid subscript accesses:

```py
class Person(TypedDict):
    name: str
    age: int | None

alice = Person(name="Alice", age=25)

reveal_type(alice["name"])  # revealed: str
reveal_type(alice["age"])  # revealed: int | None

alice["naem"]  # Unknown key "naem" - did you mean "name"?
```

## Test Plan

Updated Markdown tests
This commit is contained in:
David Peter 2025-08-06 09:36:33 +02:00 committed by GitHub
parent e917d309f1
commit 4887bdf205
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 489 additions and 158 deletions

1
Cargo.lock generated
View File

@ -4310,6 +4310,7 @@ dependencies = [
"serde", "serde",
"smallvec", "smallvec",
"static_assertions", "static_assertions",
"strsim",
"strum", "strum",
"strum_macros", "strum_macros",
"tempfile", "tempfile",

147
crates/ty/docs/rules.md generated
View File

@ -36,7 +36,7 @@ def test(): -> "int":
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L99) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L100)
</small> </small>
**What it does** **What it does**
@ -58,7 +58,7 @@ Calling a non-callable object will raise a `TypeError` at runtime.
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L143) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L144)
</small> </small>
**What it does** **What it does**
@ -88,7 +88,7 @@ f(int) # error
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L169) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L170)
</small> </small>
**What it does** **What it does**
@ -117,7 +117,7 @@ a = 1
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L194) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L195)
</small> </small>
**What it does** **What it does**
@ -147,7 +147,7 @@ class C(A, B): ...
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L220) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L221)
</small> </small>
**What it does** **What it does**
@ -177,7 +177,7 @@ class B(A): ...
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L285) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L286)
</small> </small>
**What it does** **What it does**
@ -202,7 +202,7 @@ class B(A, A): ...
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-kw-only) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-kw-only) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L306) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L307)
</small> </small>
**What it does** **What it does**
@ -306,7 +306,7 @@ def test(): -> "Literal[5]":
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L448) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L449)
</small> </small>
**What it does** **What it does**
@ -334,7 +334,7 @@ class C(A, B): ...
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L472) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L473)
</small> </small>
**What it does** **What it does**
@ -358,7 +358,7 @@ t[3] # IndexError: tuple index out of range
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20instance-layout-conflict) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20instance-layout-conflict) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L338) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L339)
</small> </small>
**What it does** **What it does**
@ -445,7 +445,7 @@ an atypical memory layout.
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L492) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L518)
</small> </small>
**What it does** **What it does**
@ -470,7 +470,7 @@ func("foo") # error: [invalid-argument-type]
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L532) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L558)
</small> </small>
**What it does** **What it does**
@ -496,7 +496,7 @@ a: int = ''
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1536) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1562)
</small> </small>
**What it does** **What it does**
@ -528,7 +528,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L554) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L580)
</small> </small>
**What it does** **What it does**
@ -550,7 +550,7 @@ class A(42): ... # error: [invalid-base]
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L605) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L631)
</small> </small>
**What it does** **What it does**
@ -575,7 +575,7 @@ with 1:
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L626) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L652)
</small> </small>
**What it does** **What it does**
@ -602,7 +602,7 @@ a: str
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L649) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L675)
</small> </small>
**What it does** **What it does**
@ -644,7 +644,7 @@ except ZeroDivisionError:
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L685) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L711)
</small> </small>
**What it does** **What it does**
@ -670,12 +670,41 @@ class C[U](Generic[T]): ...
- [Typing spec: Generics](https://typing.python.org/en/latest/spec/generics.html#introduction) - [Typing spec: Generics](https://typing.python.org/en/latest/spec/generics.html#introduction)
## `invalid-key`
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-key) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L493)
</small>
**What it does**
Checks for subscript accesses with invalid keys.
**Why is this bad?**
Using an invalid key will raise a `KeyError` at runtime.
**Examples**
```python
from typing import TypedDict
class Person(TypedDict):
name: str
age: int
alice = Person(name="Alice", age=30)
alice["height"] # KeyError: 'height'
```
## `invalid-legacy-type-variable` ## `invalid-legacy-type-variable`
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L711) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L737)
</small> </small>
**What it does** **What it does**
@ -708,7 +737,7 @@ def f(t: TypeVar("U")): ...
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L760) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L786)
</small> </small>
**What it does** **What it does**
@ -740,7 +769,7 @@ class B(metaclass=f): ...
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L787) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L813)
</small> </small>
**What it does** **What it does**
@ -788,7 +817,7 @@ def foo(x: int) -> int: ...
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L830) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L856)
</small> </small>
**What it does** **What it does**
@ -812,7 +841,7 @@ def f(a: int = ''): ...
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L420) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L421)
</small> </small>
**What it does** **What it does**
@ -844,7 +873,7 @@ TypeError: Protocols can only inherit from other protocols, got <class 'int'>
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L850) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L876)
</small> </small>
Checks for `raise` statements that raise non-exceptions or use invalid Checks for `raise` statements that raise non-exceptions or use invalid
@ -891,7 +920,7 @@ def g():
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L513) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L539)
</small> </small>
**What it does** **What it does**
@ -914,7 +943,7 @@ def func() -> int:
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L893) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L919)
</small> </small>
**What it does** **What it does**
@ -968,7 +997,7 @@ TODO #14889
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L739) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L765)
</small> </small>
**What it does** **What it does**
@ -993,7 +1022,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L932) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L958)
</small> </small>
**What it does** **What it does**
@ -1021,7 +1050,7 @@ TYPE_CHECKING = ''
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L956) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L982)
</small> </small>
**What it does** **What it does**
@ -1049,7 +1078,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-call) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-call) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1008) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1034)
</small> </small>
**What it does** **What it does**
@ -1081,7 +1110,7 @@ f(10) # Error
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-definition) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-definition) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L980) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1006)
</small> </small>
**What it does** **What it does**
@ -1113,7 +1142,7 @@ class C:
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1036) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1062)
</small> </small>
**What it does** **What it does**
@ -1146,7 +1175,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1065) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1091)
</small> </small>
**What it does** **What it does**
@ -1169,7 +1198,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x'
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1084) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1110)
</small> </small>
**What it does** **What it does**
@ -1196,7 +1225,7 @@ func("string") # error: [no-matching-overload]
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1107) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1133)
</small> </small>
**What it does** **What it does**
@ -1218,7 +1247,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1125) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1151)
</small> </small>
**What it does** **What it does**
@ -1242,7 +1271,7 @@ for i in 34: # TypeError: 'int' object is not iterable
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1176) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1202)
</small> </small>
**What it does** **What it does**
@ -1296,7 +1325,7 @@ def test(): -> "int":
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1512) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1538)
</small> </small>
**What it does** **What it does**
@ -1324,7 +1353,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1267) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1293)
</small> </small>
**What it does** **What it does**
@ -1351,7 +1380,7 @@ class B(A): ... # Error raised here
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1312) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1338)
</small> </small>
**What it does** **What it does**
@ -1376,7 +1405,7 @@ f("foo") # Error raised here
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1290) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1316)
</small> </small>
**What it does** **What it does**
@ -1402,7 +1431,7 @@ def _(x: int):
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1333) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1359)
</small> </small>
**What it does** **What it does**
@ -1446,7 +1475,7 @@ class A:
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1390) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1416)
</small> </small>
**What it does** **What it does**
@ -1471,7 +1500,7 @@ f(x=1, y=2) # Error raised here
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1411) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1437)
</small> </small>
**What it does** **What it does**
@ -1497,7 +1526,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo'
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1433) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1459)
</small> </small>
**What it does** **What it does**
@ -1520,7 +1549,7 @@ import foo # ModuleNotFoundError: No module named 'foo'
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1452) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1478)
</small> </small>
**What it does** **What it does**
@ -1543,7 +1572,7 @@ print(x) # NameError: name 'x' is not defined
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1145) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1171)
</small> </small>
**What it does** **What it does**
@ -1578,7 +1607,7 @@ b1 < b2 < b1 # exception raised here
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1471) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1497)
</small> </small>
**What it does** **What it does**
@ -1604,7 +1633,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A'
<small> <small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") · Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1493) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1519)
</small> </small>
**What it does** **What it does**
@ -1627,7 +1656,7 @@ l[1:10:0] # ValueError: slice step cannot be zero
<small> <small>
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") · Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20deprecated) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20deprecated) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L264) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L265)
</small> </small>
**What it does** **What it does**
@ -1680,7 +1709,7 @@ a = 20 / 0 # type: ignore
<small> <small>
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") · Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-attribute) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-attribute) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1197) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1223)
</small> </small>
**What it does** **What it does**
@ -1706,7 +1735,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c'
<small> <small>
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") · Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-implicit-call) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-implicit-call) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L117) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L118)
</small> </small>
**What it does** **What it does**
@ -1736,7 +1765,7 @@ A()[0] # TypeError: 'A' object is not subscriptable
<small> <small>
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") · Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-import) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unbound-import) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1219) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1245)
</small> </small>
**What it does** **What it does**
@ -1766,7 +1795,7 @@ from module import a # ImportError: cannot import name 'a' from 'module'
<small> <small>
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") · Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1564) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1590)
</small> </small>
**What it does** **What it does**
@ -1791,7 +1820,7 @@ cast(int, f()) # Redundant
<small> <small>
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") · Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1372) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1398)
</small> </small>
**What it does** **What it does**
@ -1842,7 +1871,7 @@ a = 20 / 0 # ty: ignore[division-by-zero]
<small> <small>
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") · Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-global) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-global) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1585) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1611)
</small> </small>
**What it does** **What it does**
@ -1896,7 +1925,7 @@ def g():
<small> <small>
Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") · Default level: [`warn`](../rules.md#rule-levels "This lint has a default level of 'warn'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L572) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L598)
</small> </small>
**What it does** **What it does**
@ -1933,7 +1962,7 @@ class D(C): ... # error: [unsupported-base]
<small> <small>
Default level: [`ignore`](../rules.md#rule-levels "This lint has a default level of 'ignore'.") · Default level: [`ignore`](../rules.md#rule-levels "This lint has a default level of 'ignore'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L246) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L247)
</small> </small>
**What it does** **What it does**
@ -1955,7 +1984,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime.
<small> <small>
Default level: [`ignore`](../rules.md#rule-levels "This lint has a default level of 'ignore'.") · Default level: [`ignore`](../rules.md#rule-levels "This lint has a default level of 'ignore'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1245) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1271)
</small> </small>
**What it does** **What it does**

View File

@ -48,6 +48,7 @@ test-case = { workspace = true }
memchr = { workspace = true } memchr = { workspace = true }
strum = { workspace = true } strum = { workspace = true }
strum_macros = { workspace = true } strum_macros = { workspace = true }
strsim = "0.11.1"
[dev-dependencies] [dev-dependencies]
ruff_db = { workspace = true, features = ["testing", "os"] } ruff_db = { workspace = true, features = ["testing", "os"] }

View File

@ -244,8 +244,7 @@ class D(TypedDict):
td = D(x=1, label="a") td = D(x=1, label="a")
td["x"] = 0 td["x"] = 0
# TODO: should be Literal[0] reveal_type(td["x"]) # revealed: Literal[0]
reveal_type(td["x"]) # revealed: @Todo(Support for `TypedDict`)
# error: [unresolved-reference] # error: [unresolved-reference]
does["not"]["exist"] = 0 does["not"]["exist"] = 0

View File

@ -0,0 +1,77 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: typed_dict.md - `TypedDict` - Diagnostics
mdtest path: crates/ty_python_semantic/resources/mdtest/typed_dict.md
---
# Python source files
## mdtest_snippet.py
```
1 | from typing import TypedDict, Final
2 |
3 | class Person(TypedDict):
4 | name: str
5 | age: int | None
6 |
7 | def access_invalid_literal_string_key(person: Person):
8 | person["naem"] # error: [invalid-key]
9 |
10 | NAME_KEY: Final = "naem"
11 |
12 | def access_invalid_key(person: Person):
13 | person[NAME_KEY] # error: [invalid-key]
14 |
15 | def access_with_str_key(person: Person, str_key: str):
16 | person[str_key] # error: [invalid-key]
```
# Diagnostics
```
error[invalid-key]: Invalid key access on TypedDict `Person`
--> src/mdtest_snippet.py:8:5
|
7 | def access_invalid_literal_string_key(person: Person):
8 | person["naem"] # error: [invalid-key]
| ------ ^^^^^^ Unknown key "naem" - did you mean "name"?
| |
| TypedDict `Person`
9 |
10 | NAME_KEY: Final = "naem"
|
info: rule `invalid-key` is enabled by default
```
```
error[invalid-key]: Invalid key access on TypedDict `Person`
--> src/mdtest_snippet.py:13:5
|
12 | def access_invalid_key(person: Person):
13 | person[NAME_KEY] # error: [invalid-key]
| ------ ^^^^^^^^ Unknown key "naem" - did you mean "name"?
| |
| TypedDict `Person`
14 |
15 | def access_with_str_key(person: Person, str_key: str):
|
info: rule `invalid-key` is enabled by default
```
```
error[invalid-key]: TypedDict `Person` cannot be indexed with a key of type `str`
--> src/mdtest_snippet.py:16:12
|
15 | def access_with_str_key(person: Person, str_key: str):
16 | person[str_key] # error: [invalid-key]
| ^^^^^^^
|
info: rule `invalid-key` is enabled by default
```

View File

@ -25,12 +25,21 @@ alice: Person = {"name": "Alice", "age": 30}
reveal_type(alice["name"]) # revealed: Unknown reveal_type(alice["name"]) # revealed: Unknown
# TODO: this should be `int | None` # TODO: this should be `int | None`
reveal_type(alice["age"]) # revealed: Unknown reveal_type(alice["age"]) # revealed: Unknown
# TODO: this should reveal `Unknown`, and it should emit an error
reveal_type(alice["non_existing"]) # revealed: Unknown
``` ```
Inhabitants can also be created through a constructor call: Inhabitants can also be created through a constructor call:
```py ```py
bob = Person(name="Bob", age=25) bob = Person(name="Bob", age=25)
reveal_type(bob["name"]) # revealed: str
reveal_type(bob["age"]) # revealed: int | None
# error: [invalid-key] "Invalid key access on TypedDict `Person`: Unknown key "non_existing""
reveal_type(bob["non_existing"]) # revealed: Unknown
``` ```
Methods that are available on `dict`s are also available on `TypedDict`s: Methods that are available on `dict`s are also available on `TypedDict`s:
@ -127,6 +136,39 @@ dangerous(alice)
reveal_type(alice["name"]) # revealed: Unknown reveal_type(alice["name"]) # revealed: Unknown
``` ```
## Key-based access
```py
from typing import TypedDict, Final, Literal, Any
class Person(TypedDict):
name: str
age: int | None
NAME_FINAL: Final = "name"
AGE_FINAL: Final[Literal["age"]] = "age"
def _(person: Person, literal_key: Literal["age"], union_of_keys: Literal["age", "name"], str_key: str, unknown_key: Any) -> None:
reveal_type(person["name"]) # revealed: str
reveal_type(person["age"]) # revealed: int | None
reveal_type(person[NAME_FINAL]) # revealed: str
reveal_type(person[AGE_FINAL]) # revealed: int | None
reveal_type(person[literal_key]) # revealed: int | None
reveal_type(person[union_of_keys]) # revealed: int | None | str
# error: [invalid-key] "Invalid key access on TypedDict `Person`: Unknown key "non_existing""
reveal_type(person["non_existing"]) # revealed: Unknown
# error: [invalid-key] "TypedDict `Person` cannot be indexed with a key of type `str`"
reveal_type(person[str_key]) # revealed: Unknown
# No error here:
reveal_type(person[unknown_key]) # revealed: Unknown
```
## Methods on `TypedDict` ## Methods on `TypedDict`
```py ```py
@ -333,4 +375,29 @@ reveal_type(Message.__required_keys__) # revealed: @Todo(Support for `TypedDict
msg.content msg.content
``` ```
## Diagnostics
<!-- snapshot-diagnostics -->
Snapshot tests for diagnostic messages including suggestions:
```py
from typing import TypedDict, Final
class Person(TypedDict):
name: str
age: int | None
def access_invalid_literal_string_key(person: Person):
person["naem"] # error: [invalid-key]
NAME_KEY: Final = "naem"
def access_invalid_key(person: Person):
person[NAME_KEY] # error: [invalid-key]
def access_with_str_key(person: Person, str_key: str):
person[str_key] # error: [invalid-key]
```
[`typeddict`]: https://typing.python.org/en/latest/spec/typeddict.html [`typeddict`]: https://typing.python.org/en/latest/spec/typeddict.html

View File

@ -46,6 +46,7 @@ mod util;
#[cfg(feature = "testing")] #[cfg(feature = "testing")]
pub mod pull_types; pub mod pull_types;
type FxOrderMap<K, V> = ordermap::map::OrderMap<K, V, BuildHasherDefault<FxHasher>>;
type FxOrderSet<V> = ordermap::set::OrderSet<V, BuildHasherDefault<FxHasher>>; type FxOrderSet<V> = ordermap::set::OrderSet<V, BuildHasherDefault<FxHasher>>;
type FxIndexMap<K, V> = indexmap::IndexMap<K, V, BuildHasherDefault<FxHasher>>; type FxIndexMap<K, V> = indexmap::IndexMap<K, V, BuildHasherDefault<FxHasher>>;
type FxIndexSet<V> = indexmap::IndexSet<V, BuildHasherDefault<FxHasher>>; type FxIndexSet<V> = indexmap::IndexSet<V, BuildHasherDefault<FxHasher>>;

View File

@ -38,6 +38,7 @@ use crate::semantic_index::scope::ScopeId;
use crate::semantic_index::{imported_modules, place_table, semantic_index}; use crate::semantic_index::{imported_modules, place_table, semantic_index};
use crate::suppression::check_suppressions; use crate::suppression::check_suppressions;
use crate::types::call::{Binding, Bindings, CallArguments, CallableBinding}; use crate::types::call::{Binding, Bindings, CallArguments, CallableBinding};
use crate::types::class::{CodeGeneratorKind, Field};
pub(crate) use crate::types::class_base::ClassBase; pub(crate) use crate::types::class_base::ClassBase;
use crate::types::context::{LintDiagnosticGuard, LintDiagnosticGuardBuilder}; use crate::types::context::{LintDiagnosticGuard, LintDiagnosticGuardBuilder};
use crate::types::diagnostic::{INVALID_TYPE_FORM, UNSUPPORTED_BOOL_CONVERSION}; use crate::types::diagnostic::{INVALID_TYPE_FORM, UNSUPPORTED_BOOL_CONVERSION};
@ -61,7 +62,7 @@ use crate::types::signatures::{Parameter, ParameterForm, Parameters, walk_signat
use crate::types::tuple::{TupleSpec, TupleType}; use crate::types::tuple::{TupleSpec, TupleType};
use crate::unpack::EvaluationMode; use crate::unpack::EvaluationMode;
pub use crate::util::diagnostics::add_inferred_python_version_hint_to_diagnostic; pub use crate::util::diagnostics::add_inferred_python_version_hint_to_diagnostic;
use crate::{Db, FxOrderSet, Module, Program}; use crate::{Db, FxOrderMap, FxOrderSet, Module, Program};
pub(crate) use class::{ClassLiteral, ClassType, GenericAlias, KnownClass}; pub(crate) use class::{ClassLiteral, ClassType, GenericAlias, KnownClass};
use instance::Protocol; use instance::Protocol;
pub use instance::{NominalInstanceType, ProtocolInstanceType}; pub use instance::{NominalInstanceType, ProtocolInstanceType};
@ -669,10 +670,6 @@ impl<'db> Type<'db> {
matches!(self, Type::Dynamic(_)) matches!(self, Type::Dynamic(_))
} }
pub(crate) const fn is_typed_dict(&self) -> bool {
matches!(self, Type::TypedDict(..))
}
/// Returns the top materialization (or upper bound materialization) of this type, which is the /// Returns the top materialization (or upper bound materialization) of this type, which is the
/// most general form of the type that is fully static. /// most general form of the type that is fully static.
#[must_use] #[must_use]
@ -834,6 +831,17 @@ impl<'db> Type<'db> {
.expect("Expected a Type::EnumLiteral variant") .expect("Expected a Type::EnumLiteral variant")
} }
pub(crate) const fn is_typed_dict(&self) -> bool {
matches!(self, Type::TypedDict(..))
}
pub(crate) fn into_typed_dict(self) -> Option<TypedDictType<'db>> {
match self {
Type::TypedDict(typed_dict) => Some(typed_dict),
_ => None,
}
}
/// Turn a class literal (`Type::ClassLiteral` or `Type::GenericAlias`) into a `ClassType`. /// Turn a class literal (`Type::ClassLiteral` or `Type::GenericAlias`) into a `ClassType`.
/// Since a `ClassType` must be specialized, apply the default specialization to any /// Since a `ClassType` must be specialized, apply the default specialization to any
/// unspecialized generic class literal. /// unspecialized generic class literal.
@ -5289,15 +5297,15 @@ impl<'db> Type<'db> {
], ],
), ),
_ if class.is_typed_dict(db) => { _ if class.is_typed_dict(db) => {
Type::TypedDict(TypedDictType::new(db, ClassType::NonGeneric(*class))) TypedDictType::from(db, ClassType::NonGeneric(*class))
} }
_ => Type::instance(db, class.default_specialization(db)), _ => Type::instance(db, class.default_specialization(db)),
}; };
Ok(ty) Ok(ty)
} }
Type::GenericAlias(alias) if alias.is_typed_dict(db) => Ok(Type::TypedDict( Type::GenericAlias(alias) if alias.is_typed_dict(db) => {
TypedDictType::new(db, ClassType::from(*alias)), Ok(TypedDictType::from(db, ClassType::from(*alias)))
)), }
Type::GenericAlias(alias) => Ok(Type::instance(db, ClassType::from(*alias))), Type::GenericAlias(alias) => Ok(Type::instance(db, ClassType::from(*alias))),
Type::SubclassOf(_) Type::SubclassOf(_)
@ -5644,6 +5652,7 @@ impl<'db> Type<'db> {
return KnownClass::Dict return KnownClass::Dict
.to_specialized_class_type(db, [KnownClass::Str.to_instance(db), Type::object(db)]) .to_specialized_class_type(db, [KnownClass::Str.to_instance(db), Type::object(db)])
.map(Type::from) .map(Type::from)
// Guard against user-customized typesheds with a broken `dict` class
.unwrap_or_else(Type::unknown); .unwrap_or_else(Type::unknown);
} }
@ -9000,6 +9009,15 @@ pub struct TypedDictType<'db> {
impl get_size2::GetSize for TypedDictType<'_> {} impl get_size2::GetSize for TypedDictType<'_> {}
impl<'db> TypedDictType<'db> { impl<'db> TypedDictType<'db> {
pub(crate) fn from(db: &'db dyn Db, defining_class: ClassType<'db>) -> Type<'db> {
Type::TypedDict(Self::new(db, defining_class))
}
pub(crate) fn items(self, db: &'db dyn Db) -> FxOrderMap<Name, Field<'db>> {
let (class_literal, specialization) = self.defining_class(db).class_literal(db);
class_literal.fields(db, specialization, CodeGeneratorKind::TypedDict)
}
pub(crate) fn apply_type_mapping<'a>( pub(crate) fn apply_type_mapping<'a>(
self, self,
db: &'db dyn Db, db: &'db dyn Db,

View File

@ -1,4 +1,3 @@
use std::hash::BuildHasherDefault;
use std::sync::{LazyLock, Mutex}; use std::sync::{LazyLock, Mutex};
use super::TypeVarVariance; use super::TypeVarVariance;
@ -9,6 +8,7 @@ use super::{
function::{FunctionDecorators, FunctionType}, function::{FunctionDecorators, FunctionType},
infer_expression_type, infer_unpack_types, infer_expression_type, infer_unpack_types,
}; };
use crate::FxOrderMap;
use crate::module_resolver::KnownModule; use crate::module_resolver::KnownModule;
use crate::semantic_index::definition::{Definition, DefinitionState}; use crate::semantic_index::definition::{Definition, DefinitionState};
use crate::semantic_index::scope::NodeWithScopeKind; use crate::semantic_index::scope::NodeWithScopeKind;
@ -23,9 +23,9 @@ use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signatu
use crate::types::tuple::TupleSpec; use crate::types::tuple::TupleSpec;
use crate::types::{ use crate::types::{
BareTypeAliasType, Binding, BoundSuperError, BoundSuperType, CallableType, DataclassParams, BareTypeAliasType, Binding, BoundSuperError, BoundSuperType, CallableType, DataclassParams,
DeprecatedInstance, KnownInstanceType, TypeAliasType, TypeMapping, TypeRelation, DeprecatedInstance, KnownInstanceType, StringLiteralType, TypeAliasType, TypeMapping,
TypeTransformer, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, declaration_type, TypeRelation, TypeTransformer, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind,
infer_definition_types, todo_type, declaration_type, infer_definition_types, todo_type,
}; };
use crate::{ use crate::{
Db, FxIndexMap, FxOrderSet, Program, Db, FxIndexMap, FxOrderSet, Program,
@ -54,9 +54,7 @@ use ruff_db::parsed::{ParsedModuleRef, parsed_module};
use ruff_python_ast::name::Name; use ruff_python_ast::name::Name;
use ruff_python_ast::{self as ast, PythonVersion}; use ruff_python_ast::{self as ast, PythonVersion};
use ruff_text_size::{Ranged, TextRange}; use ruff_text_size::{Ranged, TextRange};
use rustc_hash::{FxHashSet, FxHasher}; use rustc_hash::FxHashSet;
type FxOrderMap<K, V> = ordermap::map::OrderMap<K, V, BuildHasherDefault<FxHasher>>;
fn explicit_bases_cycle_recover<'db>( fn explicit_bases_cycle_recover<'db>(
_db: &'db dyn Db, _db: &'db dyn Db,
@ -1097,11 +1095,11 @@ impl MethodDecorator {
} }
} }
/// Metadata regarding a dataclass field/attribute. /// Metadata regarding a dataclass field/attribute or a `TypedDict` "item" / key-value pair.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct DataclassField<'db> { pub(crate) struct Field<'db> {
/// The declared type of the field /// The declared type of the field
pub(crate) field_ty: Type<'db>, pub(crate) declared_ty: Type<'db>,
/// The type of the default value for this field /// The type of the default value for this field
pub(crate) default_ty: Option<Type<'db>>, pub(crate) default_ty: Option<Type<'db>>,
@ -1858,8 +1856,8 @@ impl<'db> ClassLiteral<'db> {
let mut kw_only_field_seen = false; let mut kw_only_field_seen = false;
for ( for (
field_name, field_name,
DataclassField { Field {
mut field_ty, declared_ty: mut field_ty,
mut default_ty, mut default_ty,
init_only: _, init_only: _,
init, init,
@ -2038,17 +2036,28 @@ impl<'db> ClassLiteral<'db> {
Some(CallableType::function_like(db, signature)) Some(CallableType::function_like(db, signature))
} }
(CodeGeneratorKind::TypedDict, "__getitem__") => { (CodeGeneratorKind::TypedDict, "__getitem__") => {
// TODO: synthesize a set of overloads with precise types let fields = self.fields(db, specialization, field_policy);
let signature = Signature::new(
Parameters::new([
Parameter::positional_only(Some(Name::new_static("self")))
.with_annotated_type(instance_ty),
Parameter::positional_only(Some(Name::new_static("key"))),
]),
Some(todo_type!("Support for `TypedDict`")),
);
Some(CallableType::function_like(db, signature)) // Add (key -> value type) overloads for all TypedDict items ("fields"):
let overloads = fields.iter().map(|(name, field)| {
let key_type = Type::StringLiteral(StringLiteralType::new(db, name.as_str()));
Signature::new(
Parameters::new([
Parameter::positional_only(Some(Name::new_static("self")))
.with_annotated_type(instance_ty),
Parameter::positional_only(Some(Name::new_static("key")))
.with_annotated_type(key_type),
]),
Some(field.declared_ty),
)
});
Some(Type::Callable(CallableType::new(
db,
CallableSignature::from_overloads(overloads),
true,
)))
} }
(CodeGeneratorKind::TypedDict, "get") => { (CodeGeneratorKind::TypedDict, "get") => {
// TODO: synthesize a set of overloads with precise types // TODO: synthesize a set of overloads with precise types
@ -2143,7 +2152,7 @@ impl<'db> ClassLiteral<'db> {
db: &'db dyn Db, db: &'db dyn Db,
specialization: Option<Specialization<'db>>, specialization: Option<Specialization<'db>>,
field_policy: CodeGeneratorKind, field_policy: CodeGeneratorKind,
) -> FxOrderMap<Name, DataclassField<'db>> { ) -> FxOrderMap<Name, Field<'db>> {
if field_policy == CodeGeneratorKind::NamedTuple { if field_policy == CodeGeneratorKind::NamedTuple {
// NamedTuples do not allow multiple inheritance, so it is sufficient to enumerate the // NamedTuples do not allow multiple inheritance, so it is sufficient to enumerate the
// fields of this class only. // fields of this class only.
@ -2190,7 +2199,7 @@ impl<'db> ClassLiteral<'db> {
self, self,
db: &'db dyn Db, db: &'db dyn Db,
specialization: Option<Specialization<'db>>, specialization: Option<Specialization<'db>>,
) -> FxOrderMap<Name, DataclassField<'db>> { ) -> FxOrderMap<Name, Field<'db>> {
let mut attributes = FxOrderMap::default(); let mut attributes = FxOrderMap::default();
let class_body_scope = self.body_scope(db); let class_body_scope = self.body_scope(db);
@ -2242,8 +2251,8 @@ impl<'db> ClassLiteral<'db> {
attributes.insert( attributes.insert(
symbol.name().clone(), symbol.name().clone(),
DataclassField { Field {
field_ty: attr_ty.apply_optional_specialization(db, specialization), declared_ty: attr_ty.apply_optional_specialization(db, specialization),
default_ty, default_ty,
init_only: attr.is_init_var(), init_only: attr.is_init_var(),
init, init,

View File

@ -40,6 +40,7 @@ pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) {
registry.register_lint(&INSTANCE_LAYOUT_CONFLICT); registry.register_lint(&INSTANCE_LAYOUT_CONFLICT);
registry.register_lint(&INCONSISTENT_MRO); registry.register_lint(&INCONSISTENT_MRO);
registry.register_lint(&INDEX_OUT_OF_BOUNDS); registry.register_lint(&INDEX_OUT_OF_BOUNDS);
registry.register_lint(&INVALID_KEY);
registry.register_lint(&INVALID_ARGUMENT_TYPE); registry.register_lint(&INVALID_ARGUMENT_TYPE);
registry.register_lint(&INVALID_RETURN_TYPE); registry.register_lint(&INVALID_RETURN_TYPE);
registry.register_lint(&INVALID_ASSIGNMENT); registry.register_lint(&INVALID_ASSIGNMENT);
@ -489,6 +490,31 @@ declare_lint! {
} }
} }
declare_lint! {
/// ## What it does
/// Checks for subscript accesses with invalid keys.
///
/// ## Why is this bad?
/// Using an invalid key will raise a `KeyError` at runtime.
///
/// ## Examples
/// ```python
/// from typing import TypedDict
///
/// class Person(TypedDict):
/// name: str
/// age: int
///
/// alice = Person(name="Alice", age=30)
/// alice["height"] # KeyError: 'height'
/// ```
pub(crate) static INVALID_KEY = {
summary: "detects invalid subscript accesses",
status: LintStatus::preview("1.0.0"),
default_level: Level::Error,
}
}
declare_lint! { declare_lint! {
/// ## What it does /// ## What it does
/// Detects call arguments whose type is not assignable to the corresponding typed parameter. /// Detects call arguments whose type is not assignable to the corresponding typed parameter.
@ -2591,3 +2617,29 @@ pub(super) fn hint_if_stdlib_submodule_exists_on_other_versions(
add_inferred_python_version_hint_to_diagnostic(db, &mut diagnostic, "resolving modules"); add_inferred_python_version_hint_to_diagnostic(db, &mut diagnostic, "resolving modules");
} }
/// Suggest a name from `existing_names` that is similar to `wrong_name`.
pub(super) fn did_you_mean<S: AsRef<str>, T: AsRef<str>>(
existing_names: impl Iterator<Item = S>,
wrong_name: T,
) -> Option<String> {
if wrong_name.as_ref().len() < 3 {
return None;
}
existing_names
.filter(|ref id| id.as_ref().len() >= 2)
.map(|ref id| {
(
id.as_ref().to_string(),
strsim::damerau_levenshtein(
&id.as_ref().to_lowercase(),
&wrong_name.as_ref().to_lowercase(),
),
)
})
.min_by_key(|(_, dist)| *dist)
// Heuristic to filter out bad matches
.filter(|(_, dist)| *dist <= 3)
.map(|(id, _)| id)
}

View File

@ -90,21 +90,21 @@ use crate::semantic_index::{
ApplicableConstraints, EnclosingSnapshotResult, SemanticIndex, place_table, semantic_index, ApplicableConstraints, EnclosingSnapshotResult, SemanticIndex, place_table, semantic_index,
}; };
use crate::types::call::{Binding, Bindings, CallArguments, CallError, CallErrorKind}; use crate::types::call::{Binding, Bindings, CallArguments, CallError, CallErrorKind};
use crate::types::class::{CodeGeneratorKind, DataclassField, MetaclassErrorKind, SliceLiteral}; use crate::types::class::{CodeGeneratorKind, Field, MetaclassErrorKind, SliceLiteral};
use crate::types::diagnostic::{ use crate::types::diagnostic::{
self, CALL_NON_CALLABLE, CONFLICTING_DECLARATIONS, CONFLICTING_METACLASS, self, CALL_NON_CALLABLE, CONFLICTING_DECLARATIONS, CONFLICTING_METACLASS,
CYCLIC_CLASS_DEFINITION, DIVISION_BY_ZERO, DUPLICATE_KW_ONLY, INCONSISTENT_MRO, CYCLIC_CLASS_DEFINITION, DIVISION_BY_ZERO, DUPLICATE_KW_ONLY, INCONSISTENT_MRO,
INVALID_ARGUMENT_TYPE, INVALID_ASSIGNMENT, INVALID_ATTRIBUTE_ACCESS, INVALID_BASE, INVALID_ARGUMENT_TYPE, INVALID_ASSIGNMENT, INVALID_ATTRIBUTE_ACCESS, INVALID_BASE,
INVALID_DECLARATION, INVALID_GENERIC_CLASS, INVALID_PARAMETER_DEFAULT, INVALID_TYPE_FORM, INVALID_DECLARATION, INVALID_GENERIC_CLASS, INVALID_KEY, INVALID_PARAMETER_DEFAULT,
INVALID_TYPE_GUARD_CALL, INVALID_TYPE_VARIABLE_CONSTRAINTS, IncompatibleBases, INVALID_TYPE_FORM, INVALID_TYPE_GUARD_CALL, INVALID_TYPE_VARIABLE_CONSTRAINTS,
POSSIBLY_UNBOUND_IMPLICIT_CALL, POSSIBLY_UNBOUND_IMPORT, TypeCheckDiagnostics, IncompatibleBases, POSSIBLY_UNBOUND_IMPLICIT_CALL, POSSIBLY_UNBOUND_IMPORT,
UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_GLOBAL, UNRESOLVED_IMPORT, TypeCheckDiagnostics, UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_GLOBAL,
UNRESOLVED_REFERENCE, UNSUPPORTED_OPERATOR, report_implicit_return_type, UNRESOLVED_IMPORT, UNRESOLVED_REFERENCE, UNSUPPORTED_OPERATOR, did_you_mean,
report_instance_layout_conflict, report_invalid_argument_number_to_special_form, report_implicit_return_type, report_instance_layout_conflict,
report_invalid_arguments_to_annotated, report_invalid_arguments_to_callable, report_invalid_argument_number_to_special_form, report_invalid_arguments_to_annotated,
report_invalid_assignment, report_invalid_attribute_assignment, report_invalid_arguments_to_callable, report_invalid_assignment,
report_invalid_generator_function_return_type, report_invalid_return_type, report_invalid_attribute_assignment, report_invalid_generator_function_return_type,
report_possibly_unbound_attribute, report_invalid_return_type, report_possibly_unbound_attribute,
}; };
use crate::types::enums::is_enum_class; use crate::types::enums::is_enum_class;
use crate::types::function::{ use crate::types::function::{
@ -1352,8 +1352,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
let specialization = None; let specialization = None;
let mut kw_only_field_names = vec![]; let mut kw_only_field_names = vec![];
for (name, DataclassField { field_ty, .. }) in for (
class.fields(self.db(), specialization, field_policy) name,
Field {
declared_ty: field_ty,
..
},
) in class.fields(self.db(), specialization, field_policy)
{ {
let Some(instance) = field_ty.into_nominal_instance() else { let Some(instance) = field_ty.into_nominal_instance() else {
continue; continue;
@ -1909,17 +1914,17 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
// TODO: also consider qualifiers on the attribute // TODO: also consider qualifiers on the attribute
return (ty, is_modifiable); return (ty, is_modifiable);
} }
} else if let AnyNodeRef::ExprSubscript(ast::ExprSubscript { } else if let AnyNodeRef::ExprSubscript(
value, subscript @ ast::ExprSubscript {
slice, value, slice, ctx, ..
ctx, },
.. ) = node
}) = node
{ {
let value_ty = self.infer_expression(value); let value_ty = self.infer_expression(value);
let slice_ty = self.infer_expression(slice); let slice_ty = self.infer_expression(slice);
let result_ty = self let result_ty = self.infer_subscript_expression_types(
.infer_subscript_expression_types(value, value_ty, slice_ty, *ctx); subscript, value_ty, slice_ty, *ctx,
);
return (result_ty, is_modifiable); return (result_ty, is_modifiable);
} }
} }
@ -2030,24 +2035,29 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
// pyright. TODO: Other standard library classes may also be considered safe. Also, // pyright. TODO: Other standard library classes may also be considered safe. Also,
// subclasses of these safe classes that do not override `__getitem__/__setitem__` // subclasses of these safe classes that do not override `__getitem__/__setitem__`
// may be considered safe. // may be considered safe.
let safe_mutable_classes = [ let is_safe_mutable_class = || {
KnownClass::List.to_instance(db), let safe_mutable_classes = [
KnownClass::Dict.to_instance(db), KnownClass::List.to_instance(db),
KnownClass::Bytearray.to_instance(db), KnownClass::Dict.to_instance(db),
KnownClass::DefaultDict.to_instance(db), KnownClass::Bytearray.to_instance(db),
SpecialFormType::ChainMap.instance_fallback(db), KnownClass::DefaultDict.to_instance(db),
SpecialFormType::Counter.instance_fallback(db), SpecialFormType::ChainMap.instance_fallback(db),
SpecialFormType::Deque.instance_fallback(db), SpecialFormType::Counter.instance_fallback(db),
SpecialFormType::OrderedDict.instance_fallback(db), SpecialFormType::Deque.instance_fallback(db),
SpecialFormType::TypedDict.instance_fallback(db), SpecialFormType::OrderedDict.instance_fallback(db),
]; SpecialFormType::TypedDict.instance_fallback(db),
if safe_mutable_classes.iter().all(|safe_mutable_class| { ];
!value_ty.is_equivalent_to(db, *safe_mutable_class)
&& value_ty safe_mutable_classes.iter().any(|safe_mutable_class| {
.generic_origin(db) value_ty.is_equivalent_to(db, *safe_mutable_class)
.zip(safe_mutable_class.generic_origin(db)) || value_ty
.is_none_or(|(l, r)| l != r) .generic_origin(db)
}) { .zip(safe_mutable_class.generic_origin(db))
.is_some_and(|(l, r)| l == r)
})
};
if !value_ty.is_typed_dict() && !is_safe_mutable_class() {
bound_ty = declared_ty; bound_ty = declared_ty;
} }
} }
@ -8454,7 +8464,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
ExprContext::Store => { ExprContext::Store => {
let value_ty = self.infer_expression(value); let value_ty = self.infer_expression(value);
let slice_ty = self.infer_expression(slice); let slice_ty = self.infer_expression(slice);
self.infer_subscript_expression_types(value, value_ty, slice_ty, *ctx); self.infer_subscript_expression_types(subscript, value_ty, slice_ty, *ctx);
Type::Never Type::Never
} }
ExprContext::Del => { ExprContext::Del => {
@ -8464,7 +8474,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
ExprContext::Invalid => { ExprContext::Invalid => {
let value_ty = self.infer_expression(value); let value_ty = self.infer_expression(value);
let slice_ty = self.infer_expression(slice); let slice_ty = self.infer_expression(slice);
self.infer_subscript_expression_types(value, value_ty, slice_ty, *ctx); self.infer_subscript_expression_types(subscript, value_ty, slice_ty, *ctx);
Type::unknown() Type::unknown()
} }
} }
@ -8493,7 +8503,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
// Even if we can obtain the subscript type based on the assignments, we still perform default type inference // Even if we can obtain the subscript type based on the assignments, we still perform default type inference
// (to store the expression type and to report errors). // (to store the expression type and to report errors).
let slice_ty = self.infer_expression(slice); let slice_ty = self.infer_expression(slice);
self.infer_subscript_expression_types(value, value_ty, slice_ty, *ctx); self.infer_subscript_expression_types(subscript, value_ty, slice_ty, *ctx);
return ty; return ty;
} }
} }
@ -8532,7 +8542,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
} }
let slice_ty = self.infer_expression(slice); let slice_ty = self.infer_expression(slice);
let result_ty = self.infer_subscript_expression_types(value, value_ty, slice_ty, *ctx); let result_ty = self.infer_subscript_expression_types(subscript, value_ty, slice_ty, *ctx);
self.narrow_expr_with_applicable_constraints(subscript, result_ty, &constraint_keys) self.narrow_expr_with_applicable_constraints(subscript, result_ty, &constraint_keys)
} }
@ -8583,7 +8593,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
fn infer_subscript_expression_types( fn infer_subscript_expression_types(
&self, &self,
value_node: &'ast ast::Expr, subscript: &ast::ExprSubscript,
value_ty: Type<'db>, value_ty: Type<'db>,
slice_ty: Type<'db>, slice_ty: Type<'db>,
expr_context: ExprContext, expr_context: ExprContext,
@ -8591,12 +8601,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
let db = self.db(); let db = self.db();
let context = &self.context; let context = &self.context;
let value_node = subscript.value.as_ref();
let inferred = match (value_ty, slice_ty) { let inferred = match (value_ty, slice_ty) {
(Type::NominalInstance(instance), _) (Type::NominalInstance(instance), _)
if instance.class.is_known(db, KnownClass::VersionInfo) => if instance.class.is_known(db, KnownClass::VersionInfo) =>
{ {
Some(self.infer_subscript_expression_types( Some(self.infer_subscript_expression_types(
value_node, subscript,
Type::version_info_tuple(db), Type::version_info_tuple(db),
slice_ty, slice_ty,
expr_context, expr_context,
@ -8604,7 +8616,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
} }
(Type::Union(union), _) => Some(union.map(db, |element| { (Type::Union(union), _) => Some(union.map(db, |element| {
self.infer_subscript_expression_types(value_node, *element, slice_ty, expr_context) self.infer_subscript_expression_types(subscript, *element, slice_ty, expr_context)
})), })),
// TODO: we can map over the intersection and fold the results back into an intersection, // TODO: we can map over the intersection and fold the results back into an intersection,
@ -8735,7 +8747,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
Type::Tuple(_) | Type::StringLiteral(_) | Type::BytesLiteral(_), Type::Tuple(_) | Type::StringLiteral(_) | Type::BytesLiteral(_),
Type::BooleanLiteral(bool), Type::BooleanLiteral(bool),
) => Some(self.infer_subscript_expression_types( ) => Some(self.infer_subscript_expression_types(
value_node, subscript,
value_ty, value_ty,
Type::IntLiteral(i64::from(bool)), Type::IntLiteral(i64::from(bool)),
expr_context, expr_context,
@ -8830,7 +8842,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
// //
// See: https://docs.python.org/3/reference/datamodel.html#class-getitem-versus-getitem // See: https://docs.python.org/3/reference/datamodel.html#class-getitem-versus-getitem
match value_ty.try_call_dunder(db, "__getitem__", CallArguments::positional([slice_ty])) { match value_ty.try_call_dunder(db, "__getitem__", CallArguments::positional([slice_ty])) {
Ok(outcome) => return outcome.return_type(db), Ok(outcome) => {
return outcome.return_type(db);
}
Err(err @ CallDunderError::PossiblyUnbound { .. }) => { Err(err @ CallDunderError::PossiblyUnbound { .. }) => {
if let Some(builder) = if let Some(builder) =
context.report_lint(&POSSIBLY_UNBOUND_IMPLICIT_CALL, value_node) context.report_lint(&POSSIBLY_UNBOUND_IMPLICIT_CALL, value_node)
@ -8856,16 +8870,61 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
} }
} }
CallErrorKind::BindingError => { CallErrorKind::BindingError => {
if let Some(builder) = if let Some(typed_dict) = value_ty.into_typed_dict() {
context.report_lint(&INVALID_ARGUMENT_TYPE, value_node) let slice_node = subscript.slice.as_ref();
{
builder.into_diagnostic(format_args!( if let Some(builder) = context.report_lint(&INVALID_KEY, slice_node) {
"Method `__getitem__` of type `{}` cannot be called with key of \ match slice_ty {
type `{}` on object of type `{}`", Type::StringLiteral(key) => {
bindings.callable_type().display(db), let key = key.value(db);
slice_ty.display(db), let typed_dict_name = value_ty.display(db);
value_ty.display(db),
)); let mut diagnostic = builder.into_diagnostic(format_args!(
"Invalid key access on TypedDict `{typed_dict_name}`",
));
diagnostic.annotate(
self.context.secondary(value_node).message(
format_args!("TypedDict `{typed_dict_name}`"),
),
);
let items = typed_dict.items(db);
let existing_keys =
items.iter().map(|(name, _)| name.as_str());
diagnostic.set_primary_message(format!(
"Unknown key \"{key}\"{hint}",
hint = if let Some(suggestion) =
did_you_mean(existing_keys, key)
{
format!(" - did you mean \"{suggestion}\"?")
} else {
String::new()
}
));
diagnostic
}
_ => builder.into_diagnostic(format_args!(
"TypedDict `{}` cannot be indexed with a key of type `{}`",
value_ty.display(db),
slice_ty.display(db),
)),
};
}
} else {
if let Some(builder) =
context.report_lint(&INVALID_ARGUMENT_TYPE, value_node)
{
builder.into_diagnostic(format_args!(
"Method `__getitem__` of type `{}` cannot be called with key of \
type `{}` on object of type `{}`",
bindings.callable_type().display(db),
slice_ty.display(db),
value_ty.display(db),
));
}
} }
} }
CallErrorKind::PossiblyNotCallable => { CallErrorKind::PossiblyNotCallable => {

View File

@ -9,7 +9,9 @@ use crate::types::cyclic::PairVisitor;
use crate::types::enums::is_single_member_enum; use crate::types::enums::is_single_member_enum;
use crate::types::protocol_class::walk_protocol_interface; use crate::types::protocol_class::walk_protocol_interface;
use crate::types::tuple::TupleType; use crate::types::tuple::TupleType;
use crate::types::{DynamicType, TypeMapping, TypeRelation, TypeTransformer, TypeVarInstance}; use crate::types::{
DynamicType, TypeMapping, TypeRelation, TypeTransformer, TypeVarInstance, TypedDictType,
};
use crate::{Db, FxOrderSet}; use crate::{Db, FxOrderSet};
pub(super) use synthesized_protocol::SynthesizedProtocolType; pub(super) use synthesized_protocol::SynthesizedProtocolType;
@ -24,10 +26,16 @@ impl<'db> Type<'db> {
(ClassType::Generic(alias), Some(KnownClass::Tuple)) => { (ClassType::Generic(alias), Some(KnownClass::Tuple)) => {
Self::tuple(TupleType::new(db, alias.specialization(db).tuple(db))) Self::tuple(TupleType::new(db, alias.specialization(db).tuple(db)))
} }
_ if class.class_literal(db).0.is_protocol(db) => { _ => {
Self::ProtocolInstance(ProtocolInstanceType::from_class(class)) let class_literal = class.class_literal(db).0;
if class_literal.is_protocol(db) {
Self::ProtocolInstance(ProtocolInstanceType::from_class(class))
} else if class_literal.is_typed_dict(db) {
TypedDictType::from(db, class)
} else {
Self::NominalInstance(NominalInstanceType::from_class(class))
}
} }
_ => Self::NominalInstance(NominalInstanceType::from_class(class)),
} }
} }

10
ty.schema.json generated
View File

@ -521,6 +521,16 @@
} }
] ]
}, },
"invalid-key": {
"title": "detects invalid subscript accesses",
"description": "## What it does\nChecks for subscript accesses with invalid keys.\n\n## Why is this bad?\nUsing an invalid key will raise a `KeyError` at runtime.\n\n## Examples\n```python\nfrom typing import TypedDict\n\nclass Person(TypedDict):\n name: str\n age: int\n\nalice = Person(name=\"Alice\", age=30)\nalice[\"height\"] # KeyError: 'height'\n```",
"default": "error",
"oneOf": [
{
"$ref": "#/definitions/Level"
}
]
},
"invalid-legacy-type-variable": { "invalid-legacy-type-variable": {
"title": "detects invalid legacy type variables", "title": "detects invalid legacy type variables",
"description": "## What it does\nChecks for the creation of invalid legacy `TypeVar`s\n\n## Why is this bad?\nThere are several requirements that you must follow when creating a legacy `TypeVar`.\n\n## Examples\n```python\nfrom typing import TypeVar\n\nT = TypeVar(\"T\") # okay\nQ = TypeVar(\"S\") # error: TypeVar name must match the variable it's assigned to\nT = TypeVar(\"T\") # error: TypeVars should not be redefined\n\n# error: TypeVar must be immediately assigned to a variable\ndef f(t: TypeVar(\"U\")): ...\n```\n\n## References\n- [Typing spec: Generics](https://typing.python.org/en/latest/spec/generics.html#introduction)", "description": "## What it does\nChecks for the creation of invalid legacy `TypeVar`s\n\n## Why is this bad?\nThere are several requirements that you must follow when creating a legacy `TypeVar`.\n\n## Examples\n```python\nfrom typing import TypeVar\n\nT = TypeVar(\"T\") # okay\nQ = TypeVar(\"S\") # error: TypeVar name must match the variable it's assigned to\nT = TypeVar(\"T\") # error: TypeVars should not be redefined\n\n# error: TypeVar must be immediately assigned to a variable\ndef f(t: TypeVar(\"U\")): ...\n```\n\n## References\n- [Typing spec: Generics](https://typing.python.org/en/latest/spec/generics.html#introduction)",