[ty] implement `typing.NewType`

This commit is contained in:
Jack O'Connor 2025-08-21 07:53:29 -07:00 committed by Jack O'Connor
parent 18eaa659c1
commit 136dc15352
21 changed files with 832 additions and 292 deletions

153
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#L112) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L113)
</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#L156) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L157)
</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#L182) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L183)
</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#L207) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L208)
</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#L233) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L234)
</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#L298) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L299)
</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#L319) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L320)
</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#L522) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L523)
</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#L546) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L547)
</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#L351) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L352)
</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#L591) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L592)
</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#L631) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L632)
</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#L1665) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1687)
</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-await) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-await) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L653) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L654)
</small> </small>
**What it does** **What it does**
@ -562,7 +562,7 @@ asyncio.run(main())
<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#L683) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L684)
</small> </small>
**What it does** **What it does**
@ -584,7 +584,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#L734) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L735)
</small> </small>
**What it does** **What it does**
@ -609,7 +609,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#L755) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L756)
</small> </small>
**What it does** **What it does**
@ -636,7 +636,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#L778) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L779)
</small> </small>
**What it does** **What it does**
@ -678,7 +678,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#L814) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L815)
</small> </small>
**What it does** **What it does**
@ -709,7 +709,7 @@ class C[U](Generic[T]): ...
<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-key) · [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#L566) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L567)
</small> </small>
**What it does** **What it does**
@ -738,7 +738,7 @@ alice["height"] # KeyError: 'height'
<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#L840) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L841)
</small> </small>
**What it does** **What it does**
@ -771,7 +771,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#L889) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L911)
</small> </small>
**What it does** **What it does**
@ -803,7 +803,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-named-tuple) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-named-tuple) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L496) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L497)
</small> </small>
**What it does** **What it does**
@ -828,12 +828,37 @@ in a class's bases list.
TypeError: can only inherit from a NamedTuple type and Generic TypeError: can only inherit from a NamedTuple type and Generic
``` ```
## `invalid-newtype`
<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-newtype) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L890)
</small>
**What it does**
Checks for the creation of invalid `NewType`s
**Why is this bad?**
There are several requirements that you must follow when creating a `NewType`.
**Examples**
```python
from typing import NewType
Foo = NewType("Foo", int) # okay
Bar = NewType(get_name(), int) # error: NewType name must be a string literal
```
## `invalid-overload` ## `invalid-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%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#L916) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L938)
</small> </small>
**What it does** **What it does**
@ -881,7 +906,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#L959) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L981)
</small> </small>
**What it does** **What it does**
@ -905,7 +930,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#L433) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L434)
</small> </small>
**What it does** **What it does**
@ -937,7 +962,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#L979) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1001)
</small> </small>
Checks for `raise` statements that raise non-exceptions or use invalid Checks for `raise` statements that raise non-exceptions or use invalid
@ -984,7 +1009,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#L612) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L613)
</small> </small>
**What it does** **What it does**
@ -1007,7 +1032,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#L1022) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1044)
</small> </small>
**What it does** **What it does**
@ -1061,7 +1086,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#L868) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L869)
</small> </small>
**What it does** **What it does**
@ -1086,7 +1111,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#L1061) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1083)
</small> </small>
**What it does** **What it does**
@ -1114,7 +1139,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#L1085) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1107)
</small> </small>
**What it does** **What it does**
@ -1142,7 +1167,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#L1137) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1159)
</small> </small>
**What it does** **What it does**
@ -1174,7 +1199,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#L1109) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1131)
</small> </small>
**What it does** **What it does**
@ -1206,7 +1231,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#L1165) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1187)
</small> </small>
**What it does** **What it does**
@ -1239,7 +1264,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#L1194) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1216)
</small> </small>
**What it does** **What it does**
@ -1262,7 +1287,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%20missing-typed-dict-key) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-typed-dict-key) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1764) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1786)
</small> </small>
**What it does** **What it does**
@ -1293,7 +1318,7 @@ alice["age"] # KeyError
<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#L1213) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1235)
</small> </small>
**What it does** **What it does**
@ -1320,7 +1345,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#L1236) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1258)
</small> </small>
**What it does** **What it does**
@ -1342,7 +1367,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#L1254) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1276)
</small> </small>
**What it does** **What it does**
@ -1366,7 +1391,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#L1305) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1327)
</small> </small>
**What it does** **What it does**
@ -1420,7 +1445,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#L1641) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1663)
</small> </small>
**What it does** **What it does**
@ -1448,7 +1473,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#L1396) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1418)
</small> </small>
**What it does** **What it does**
@ -1475,7 +1500,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#L1441) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1463)
</small> </small>
**What it does** **What it does**
@ -1500,7 +1525,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#L1419) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1441)
</small> </small>
**What it does** **What it does**
@ -1526,7 +1551,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#L1462) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1484)
</small> </small>
**What it does** **What it does**
@ -1570,7 +1595,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#L1519) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1541)
</small> </small>
**What it does** **What it does**
@ -1595,7 +1620,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#L1540) [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**
@ -1621,7 +1646,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#L1562) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1584)
</small> </small>
**What it does** **What it does**
@ -1644,7 +1669,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#L1581) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1603)
</small> </small>
**What it does** **What it does**
@ -1667,7 +1692,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#L1274) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1296)
</small> </small>
**What it does** **What it does**
@ -1702,7 +1727,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#L1600) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1622)
</small> </small>
**What it does** **What it does**
@ -1728,7 +1753,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#L1622) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1644)
</small> </small>
**What it does** **What it does**
@ -1751,7 +1776,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%20ambiguous-protocol-member) · [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20ambiguous-protocol-member) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L461) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L462)
</small> </small>
**What it does** **What it does**
@ -1790,7 +1815,7 @@ class SubProto(BaseProto, Protocol):
<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#L277) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L278)
</small> </small>
**What it does** **What it does**
@ -1843,7 +1868,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#L1326) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1348)
</small> </small>
**What it does** **What it does**
@ -1869,7 +1894,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#L130) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L131)
</small> </small>
**What it does** **What it does**
@ -1899,7 +1924,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#L1348) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1370)
</small> </small>
**What it does** **What it does**
@ -1929,7 +1954,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#L1693) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1715)
</small> </small>
**What it does** **What it does**
@ -1954,7 +1979,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#L1501) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1523)
</small> </small>
**What it does** **What it does**
@ -2005,7 +2030,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#L1714) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1736)
</small> </small>
**What it does** **What it does**
@ -2059,7 +2084,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#L701) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L702)
</small> </small>
**What it does** **What it does**
@ -2096,7 +2121,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#L259) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L260)
</small> </small>
**What it does** **What it does**
@ -2118,7 +2143,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#L1374) [View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1396)
</small> </small>
**What it does** **What it does**

View File

@ -165,16 +165,11 @@ impl<'db> DefinitionsOrTargets<'db> {
ty_python_semantic::types::TypeDefinition::Module(module) => { ty_python_semantic::types::TypeDefinition::Module(module) => {
ResolvedDefinition::Module(module.file(db)?) ResolvedDefinition::Module(module.file(db)?)
} }
ty_python_semantic::types::TypeDefinition::Class(definition) => { ty_python_semantic::types::TypeDefinition::Class(definition)
ResolvedDefinition::Definition(definition) | ty_python_semantic::types::TypeDefinition::Function(definition)
} | ty_python_semantic::types::TypeDefinition::TypeVar(definition)
ty_python_semantic::types::TypeDefinition::Function(definition) => { | ty_python_semantic::types::TypeDefinition::TypeAlias(definition)
ResolvedDefinition::Definition(definition) | ty_python_semantic::types::TypeDefinition::NewType(definition) => {
}
ty_python_semantic::types::TypeDefinition::TypeVar(definition) => {
ResolvedDefinition::Definition(definition)
}
ty_python_semantic::types::TypeDefinition::TypeAlias(definition) => {
ResolvedDefinition::Definition(definition) ResolvedDefinition::Definition(definition)
} }
}; };

View File

@ -1,7 +1,5 @@
# NewType # NewType
Currently, ty doesn't support `typing.NewType` in type annotations.
## Valid forms ## Valid forms
```py ```py
@ -12,13 +10,44 @@ X = GenericAlias(type, ())
A = NewType("A", int) A = NewType("A", int)
# TODO: typeshed for `typing.GenericAlias` uses `type` for the first argument. `NewType` should be special-cased # TODO: typeshed for `typing.GenericAlias` uses `type` for the first argument. `NewType` should be special-cased
# to be compatible with `type` # to be compatible with `type`
# error: [invalid-argument-type] "Argument to function `__new__` is incorrect: Expected `type`, found `NewType`" # error: [invalid-argument-type] "Argument to function `__new__` is incorrect: Expected `type`, found `A`"
B = GenericAlias(A, ()) B = GenericAlias(A, ())
def _( def _(
a: A, a: A,
b: B, b: B,
): ):
reveal_type(a) # revealed: @Todo(Support for `typing.NewType` instances in type expressions) reveal_type(a) # revealed: A
reveal_type(b) # revealed: @Todo(Support for `typing.GenericAlias` instances in type expressions) reveal_type(b) # revealed: @Todo(Support for `typing.GenericAlias` instances in type expressions)
``` ```
## Subtyping
```py
from typing_extensions import NewType
Foo = NewType("Foo", int)
Bar = NewType("Bar", Foo)
Foo(42)
Foo(Foo(42)) # allowed: `Foo` is a subtype of `int`.
Foo(Bar(Foo(42))) # allowed: `Bar` is a subtype of `int`.
Foo(True) # allowed: `bool` is a subtype of `int`.
Foo("fourty-two") # error: [invalid-argument-type]
def f(_: int): ...
def g(_: Foo): ...
def h(_: Bar): ...
f(42)
f(Foo(42))
f(Bar(Foo(42)))
g(42) # error: [invalid-argument-type]
g(Foo(42))
g(Bar(Foo(42)))
h(42) # error: [invalid-argument-type]
h(Foo(42)) # error: [invalid-argument-type]
h(Bar(Foo(42)))
```

View File

@ -731,20 +731,20 @@ impl<'db> Type<'db> {
fn is_none(&self, db: &'db dyn Db) -> bool { fn is_none(&self, db: &'db dyn Db) -> bool {
self.into_nominal_instance() self.into_nominal_instance()
.is_some_and(|instance| instance.class(db).is_known(db, KnownClass::NoneType)) .and_then(|instance| instance.class_if_not_newtype(db))
.is_some_and(|class| class.is_known(db, KnownClass::NoneType))
} }
fn is_bool(&self, db: &'db dyn Db) -> bool { fn is_bool(&self, db: &'db dyn Db) -> bool {
self.into_nominal_instance() self.into_nominal_instance()
.is_some_and(|instance| instance.class(db).is_known(db, KnownClass::Bool)) .and_then(|instance| instance.class_if_not_newtype(db))
.is_some_and(|class| class.is_known(db, KnownClass::Bool))
} }
pub(crate) fn is_notimplemented(&self, db: &'db dyn Db) -> bool { pub(crate) fn is_notimplemented(&self, db: &'db dyn Db) -> bool {
self.into_nominal_instance().is_some_and(|instance| { self.into_nominal_instance()
instance .and_then(|instance| instance.class_if_not_newtype(db))
.class(db) .is_some_and(|class| class.is_known(db, KnownClass::NotImplementedType))
.is_known(db, KnownClass::NotImplementedType)
})
} }
pub(crate) fn is_object(&self, db: &'db dyn Db) -> bool { pub(crate) fn is_object(&self, db: &'db dyn Db) -> bool {
@ -1932,8 +1932,13 @@ impl<'db> Type<'db> {
return C::unsatisfiable(db); return C::unsatisfiable(db);
} }
let class_literal = instance.class(db).class_literal(db).0; C::from_bool(
C::from_bool(db, is_single_member_enum(db, class_literal)) db,
instance.class_if_not_newtype(db).is_some_and(|class| {
let class_literal = class.class_literal(db).0;
is_single_member_enum(db, class_literal)
}),
)
} }
_ => C::unsatisfiable(db), _ => C::unsatisfiable(db),
} }
@ -2232,7 +2237,7 @@ impl<'db> Type<'db> {
// (<https://github.com/rust-lang/rust/issues/129967>) // (<https://github.com/rust-lang/rust/issues/129967>)
(Type::ProtocolInstance(protocol), nominal @ Type::NominalInstance(n)) (Type::ProtocolInstance(protocol), nominal @ Type::NominalInstance(n))
| (nominal @ Type::NominalInstance(n), Type::ProtocolInstance(protocol)) | (nominal @ Type::NominalInstance(n), Type::ProtocolInstance(protocol))
if n.class(db).is_final(db) => if n.class_ignoring_newtype(db).is_final(db) =>
{ {
visitor.visit((self, other), || { visitor.visit((self, other), || {
any_protocol_members_absent_or_disjoint(db, protocol, nominal, visitor) any_protocol_members_absent_or_disjoint(db, protocol, nominal, visitor)
@ -2288,22 +2293,33 @@ impl<'db> Type<'db> {
}, },
(Type::SpecialForm(special_form), Type::NominalInstance(instance)) (Type::SpecialForm(special_form), Type::NominalInstance(instance))
| (Type::NominalInstance(instance), Type::SpecialForm(special_form)) => { | (Type::NominalInstance(instance), Type::SpecialForm(special_form)) => C::from_bool(
C::from_bool(db, !special_form.is_instance_of(db, instance.class(db))) db,
} !instance
.class_if_not_newtype(db)
.is_some_and(|class| special_form.is_instance_of(db, class)),
),
(Type::KnownInstance(known_instance), Type::NominalInstance(instance)) (Type::KnownInstance(known_instance), Type::NominalInstance(instance))
| (Type::NominalInstance(instance), Type::KnownInstance(known_instance)) => { | (Type::NominalInstance(instance), Type::KnownInstance(known_instance)) => {
C::from_bool(db, !known_instance.is_instance_of(db, instance.class(db))) C::from_bool(
db,
!instance
.class_if_not_newtype(db)
.is_some_and(|class| known_instance.is_instance_of(db, class)),
)
} }
(Type::BooleanLiteral(..) | Type::TypeIs(_), Type::NominalInstance(instance)) (Type::BooleanLiteral(..) | Type::TypeIs(_), Type::NominalInstance(instance))
| (Type::NominalInstance(instance), Type::BooleanLiteral(..) | Type::TypeIs(_)) => { | (Type::NominalInstance(instance), Type::BooleanLiteral(..) | Type::TypeIs(_)) => {
// A `Type::BooleanLiteral()` must be an instance of exactly `bool` // A `Type::BooleanLiteral()` must be an instance of exactly `bool`
// (it cannot be an instance of a `bool` subclass) // (it cannot be an instance of a `bool` subclass)
KnownClass::Bool C::from_bool(
.when_subclass_of::<C>(db, instance.class(db)) db,
.negate(db) !instance
.class_if_not_newtype(db)
.is_some_and(|class| KnownClass::Bool.is_subclass_of(db, class)),
)
} }
(Type::BooleanLiteral(..) | Type::TypeIs(_), _) (Type::BooleanLiteral(..) | Type::TypeIs(_), _)
@ -2313,9 +2329,12 @@ impl<'db> Type<'db> {
| (Type::NominalInstance(instance), Type::IntLiteral(..)) => { | (Type::NominalInstance(instance), Type::IntLiteral(..)) => {
// A `Type::IntLiteral()` must be an instance of exactly `int` // A `Type::IntLiteral()` must be an instance of exactly `int`
// (it cannot be an instance of an `int` subclass) // (it cannot be an instance of an `int` subclass)
KnownClass::Int C::from_bool(
.when_subclass_of::<C>(db, instance.class(db)) db,
.negate(db) !instance
.class_if_not_newtype(db)
.is_some_and(|class| KnownClass::Int.is_subclass_of(db, class)),
)
} }
(Type::IntLiteral(..), _) | (_, Type::IntLiteral(..)) => C::always_satisfiable(db), (Type::IntLiteral(..), _) | (_, Type::IntLiteral(..)) => C::always_satisfiable(db),
@ -2327,9 +2346,12 @@ impl<'db> Type<'db> {
| (Type::NominalInstance(instance), Type::StringLiteral(..) | Type::LiteralString) => { | (Type::NominalInstance(instance), Type::StringLiteral(..) | Type::LiteralString) => {
// A `Type::StringLiteral()` or a `Type::LiteralString` must be an instance of exactly `str` // A `Type::StringLiteral()` or a `Type::LiteralString` must be an instance of exactly `str`
// (it cannot be an instance of a `str` subclass) // (it cannot be an instance of a `str` subclass)
KnownClass::Str C::from_bool(
.when_subclass_of::<C>(db, instance.class(db)) db,
.negate(db) !instance
.class_if_not_newtype(db)
.is_some_and(|class| KnownClass::Str.is_subclass_of(db, class)),
)
} }
(Type::LiteralString, Type::LiteralString) => C::unsatisfiable(db), (Type::LiteralString, Type::LiteralString) => C::unsatisfiable(db),
@ -2339,9 +2361,12 @@ impl<'db> Type<'db> {
| (Type::NominalInstance(instance), Type::BytesLiteral(..)) => { | (Type::NominalInstance(instance), Type::BytesLiteral(..)) => {
// A `Type::BytesLiteral()` must be an instance of exactly `bytes` // A `Type::BytesLiteral()` must be an instance of exactly `bytes`
// (it cannot be an instance of a `bytes` subclass) // (it cannot be an instance of a `bytes` subclass)
KnownClass::Bytes C::from_bool(
.when_subclass_of::<C>(db, instance.class(db)) db,
.negate(db) !instance
.class_if_not_newtype(db)
.is_some_and(|class| KnownClass::Bytes.is_subclass_of(db, class)),
)
} }
(Type::EnumLiteral(enum_literal), instance @ Type::NominalInstance(_)) (Type::EnumLiteral(enum_literal), instance @ Type::NominalInstance(_))
@ -2373,9 +2398,12 @@ impl<'db> Type<'db> {
| (Type::NominalInstance(instance), Type::FunctionLiteral(..)) => { | (Type::NominalInstance(instance), Type::FunctionLiteral(..)) => {
// A `Type::FunctionLiteral()` must be an instance of exactly `types.FunctionType` // A `Type::FunctionLiteral()` must be an instance of exactly `types.FunctionType`
// (it cannot be an instance of a `types.FunctionType` subclass) // (it cannot be an instance of a `types.FunctionType` subclass)
KnownClass::FunctionType C::from_bool(
.when_subclass_of::<C>(db, instance.class(db)) db,
.negate(db) !instance
.class_if_not_newtype(db)
.is_some_and(|class| KnownClass::FunctionType.is_subclass_of(db, class)),
)
} }
(Type::BoundMethod(_), other) | (other, Type::BoundMethod(_)) => KnownClass::MethodType (Type::BoundMethod(_), other) | (other, Type::BoundMethod(_)) => KnownClass::MethodType
@ -2426,7 +2454,7 @@ impl<'db> Type<'db> {
| ( | (
instance @ Type::NominalInstance(nominal), instance @ Type::NominalInstance(nominal),
Type::Callable(_) | Type::DataclassDecorator(_) | Type::DataclassTransformer(_), Type::Callable(_) | Type::DataclassDecorator(_) | Type::DataclassTransformer(_),
) if nominal.class(db).is_final(db) => instance ) if nominal.class_ignoring_newtype(db).is_final(db) => instance
.member_lookup_with_policy( .member_lookup_with_policy(
db, db,
Name::new_static("__call__"), Name::new_static("__call__"),
@ -2788,7 +2816,9 @@ impl<'db> Type<'db> {
// `Type::NominalInstance(type)` is equivalent to looking up the name in the // `Type::NominalInstance(type)` is equivalent to looking up the name in the
// MRO of the class `object`. // MRO of the class `object`.
Type::NominalInstance(instance) Type::NominalInstance(instance)
if instance.class(db).is_known(db, KnownClass::Type) => if instance
.class_ignoring_newtype(db)
.is_known(db, KnownClass::Type) =>
{ {
if policy.mro_no_object_fallback() { if policy.mro_no_object_fallback() {
Some(Place::Unbound.into()) Some(Place::Unbound.into())
@ -2908,7 +2938,9 @@ impl<'db> Type<'db> {
Type::Dynamic(_) | Type::Never => Place::bound(self).into(), Type::Dynamic(_) | Type::Never => Place::bound(self).into(),
Type::NominalInstance(instance) => instance.class(db).instance_member(db, name), Type::NominalInstance(instance) => instance
.class_ignoring_newtype(db)
.instance_member(db, name),
Type::ProtocolInstance(protocol) => protocol.instance_member(db, name), Type::ProtocolInstance(protocol) => protocol.instance_member(db, name),
@ -3428,7 +3460,9 @@ impl<'db> Type<'db> {
Type::NominalInstance(instance) Type::NominalInstance(instance)
if matches!(name.as_str(), "major" | "minor") if matches!(name.as_str(), "major" | "minor")
&& instance.class(db).is_known(db, KnownClass::VersionInfo) => && instance
.class_ignoring_newtype(db)
.is_known(db, KnownClass::VersionInfo) =>
{ {
let python_version = Program::get(db).python_version(db); let python_version = Program::get(db).python_version(db);
let segment = if name == "major" { let segment = if name == "major" {
@ -3506,7 +3540,7 @@ impl<'db> Type<'db> {
// resolve the attribute. // resolve the attribute.
if matches!( if matches!(
self.into_nominal_instance() self.into_nominal_instance()
.and_then(|instance| instance.class(db).known(db)), .and_then(|instance| instance.class_ignoring_newtype(db).known(db)),
Some(KnownClass::ModuleType | KnownClass::GenericAlias) Some(KnownClass::ModuleType | KnownClass::GenericAlias)
) { ) {
return Place::Unbound.into(); return Place::Unbound.into();
@ -3826,7 +3860,7 @@ impl<'db> Type<'db> {
Type::TypeVar(_) => Truthiness::Ambiguous, Type::TypeVar(_) => Truthiness::Ambiguous,
Type::NominalInstance(instance) => instance Type::NominalInstance(instance) => instance
.class(db) .class_ignoring_newtype(db)
.known(db) .known(db)
.and_then(KnownClass::bool) .and_then(KnownClass::bool)
.map(Ok) .map(Ok)
@ -4407,6 +4441,21 @@ impl<'db> Type<'db> {
.into() .into()
} }
Some(KnownClass::NewType) => Binding::single(
self,
Signature::new(
Parameters::new([
Parameter::positional_or_keyword(Name::new_static("name"))
.with_annotated_type(Type::LiteralString),
Parameter::positional_or_keyword(Name::new_static("tp")),
]),
// The true return type is a KnownInstanceType::NewType, but each callsite
// gets its own unique...instance...of that type.
None,
),
)
.into(),
Some(KnownClass::Object) => { Some(KnownClass::Object) => {
// ```py // ```py
// class object: // class object:
@ -4801,6 +4850,16 @@ impl<'db> Type<'db> {
Type::EnumLiteral(enum_literal) => enum_literal.enum_class_instance(db).bindings(db), Type::EnumLiteral(enum_literal) => enum_literal.enum_class_instance(db).bindings(db),
Type::KnownInstance(KnownInstanceType::NewType(newtype)) => Binding::single(
self,
Signature::new(
Parameters::new([Parameter::positional_only(None)
.with_annotated_type(newtype.base(db).to_type(db))]),
Some(Type::newtype_nominal_instance(newtype)),
),
)
.into(),
Type::KnownInstance(known_instance) => { Type::KnownInstance(known_instance) => {
known_instance.instance_fallback(db).bindings(db) known_instance.instance_fallback(db).bindings(db)
} }
@ -5275,9 +5334,10 @@ impl<'db> Type<'db> {
}; };
match self { match self {
Type::NominalInstance(instance) => { Type::NominalInstance(instance) => instance
instance.class(db).iter_mro(db).find_map(from_class_base) .class_ignoring_newtype(db)
} .iter_mro(db)
.find_map(from_class_base),
Type::ProtocolInstance(instance) => { Type::ProtocolInstance(instance) => {
if let Protocol::FromClass(class) = instance.inner { if let Protocol::FromClass(class) = instance.inner {
class.iter_mro(db).find_map(from_class_base) class.iter_mro(db).find_map(from_class_base)
@ -5635,6 +5695,7 @@ impl<'db> Type<'db> {
invalid_expressions: smallvec::smallvec_inline![InvalidTypeExpression::Generic], invalid_expressions: smallvec::smallvec_inline![InvalidTypeExpression::Generic],
fallback_type: Type::unknown(), fallback_type: Type::unknown(),
}), }),
KnownInstanceType::NewType(newtype) => Ok(Type::newtype_nominal_instance(*newtype)),
}, },
Type::SpecialForm(special_form) => match special_form { Type::SpecialForm(special_form) => match special_form {
@ -5812,12 +5873,16 @@ impl<'db> Type<'db> {
Type::Dynamic(_) => Ok(*self), Type::Dynamic(_) => Ok(*self),
Type::NominalInstance(instance) => match instance.class(db).known(db) { Type::NominalInstance(instance) => {
// TODO: Should NewType wrapping be supported here?
match instance.class_ignoring_newtype(db).known(db) {
Some(KnownClass::TypeVar) => Ok(todo_type!( Some(KnownClass::TypeVar) => Ok(todo_type!(
"Support for `typing.TypeVar` instances in type expressions" "Support for `typing.TypeVar` instances in type expressions"
)), )),
Some( Some(
KnownClass::ParamSpec | KnownClass::ParamSpecArgs | KnownClass::ParamSpecKwargs, KnownClass::ParamSpec
| KnownClass::ParamSpecArgs
| KnownClass::ParamSpecKwargs,
) => Ok(todo_type!("Support for `typing.ParamSpec`")), ) => Ok(todo_type!("Support for `typing.ParamSpec`")),
Some(KnownClass::TypeVarTuple) => Ok(todo_type!( Some(KnownClass::TypeVarTuple) => Ok(todo_type!(
"Support for `typing.TypeVarTuple` instances in type expressions" "Support for `typing.TypeVarTuple` instances in type expressions"
@ -5837,7 +5902,8 @@ impl<'db> Type<'db> {
], ],
fallback_type: Type::unknown(), fallback_type: Type::unknown(),
}), }),
}, }
}
Type::Intersection(_) => Ok(todo_type!("Type::Intersection.in_type_expression")), Type::Intersection(_) => Ok(todo_type!("Type::Intersection.in_type_expression")),
@ -6342,7 +6408,12 @@ impl<'db> Type<'db> {
} }
Self::GenericAlias(alias) => Some(TypeDefinition::Class(alias.definition(db))), Self::GenericAlias(alias) => Some(TypeDefinition::Class(alias.definition(db))),
Self::NominalInstance(instance) => { Self::NominalInstance(instance) => {
Some(TypeDefinition::Class(instance.class(db).definition(db))) let (instance_class_type, instance_newtype) = instance.class_and_newtype(db);
if let Some(newtype) = instance_newtype {
Some(TypeDefinition::NewType(newtype.definition(db)))
} else {
Some(TypeDefinition::Class(instance_class_type.definition(db)))
}
} }
Self::KnownInstance(instance) => match instance { Self::KnownInstance(instance) => match instance {
KnownInstanceType::TypeVar(var) => { KnownInstanceType::TypeVar(var) => {
@ -6375,17 +6446,18 @@ impl<'db> Type<'db> {
| Self::PropertyInstance(_) | Self::PropertyInstance(_)
| Self::BoundSuper(_) => self.to_meta_type(db).definition(db), | Self::BoundSuper(_) => self.to_meta_type(db).definition(db),
Self::NonInferableTypeVar(bound_typevar) | Self::NonInferableTypeVar(bound_typevar) | Self::TypeVar(bound_typevar) => Some(
Self::TypeVar(bound_typevar) => Some(TypeDefinition::TypeVar(bound_typevar.typevar(db).definition(db)?)), TypeDefinition::TypeVar(bound_typevar.typevar(db).definition(db)?),
),
Self::ProtocolInstance(protocol) => match protocol.inner { Self::ProtocolInstance(protocol) => match protocol.inner {
Protocol::FromClass(class) => Some(TypeDefinition::Class(class.definition(db))), Protocol::FromClass(class) => Some(TypeDefinition::Class(class.definition(db))),
Protocol::Synthesized(_) => None, Protocol::Synthesized(_) => None,
}, },
Type::TypedDict(typed_dict) => { Type::TypedDict(typed_dict) => Some(TypeDefinition::Class(
Some(TypeDefinition::Class(typed_dict.defining_class().definition(db))) typed_dict.defining_class().definition(db),
} )),
Self::Union(_) | Self::Intersection(_) => None, Self::Union(_) | Self::Intersection(_) => None,
@ -6466,7 +6538,7 @@ impl<'db> Type<'db> {
match self { match self {
Type::GenericAlias(generic) => Some(generic.origin(db)), Type::GenericAlias(generic) => Some(generic.origin(db)),
Type::NominalInstance(instance) => { Type::NominalInstance(instance) => {
if let ClassType::Generic(generic) = instance.class(db) { if let ClassType::Generic(generic) = instance.class_ignoring_newtype(db) {
Some(generic.origin(db)) Some(generic.origin(db))
} else { } else {
None None
@ -6712,6 +6784,10 @@ pub enum KnownInstanceType<'db> {
/// A single instance of `dataclasses.Field` /// A single instance of `dataclasses.Field`
Field(FieldInstance<'db>), Field(FieldInstance<'db>),
/// An identity function created with `typing.NewType(name, base)`, which behaves like a
/// subclass of `base` in type expressions. See `NewTypeInstance` for an example.
NewType(NewTypeInstance<'db>),
} }
fn walk_known_instance_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>( fn walk_known_instance_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
@ -6736,6 +6812,11 @@ fn walk_known_instance_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
KnownInstanceType::Field(field) => { KnownInstanceType::Field(field) => {
visitor.visit_type(db, field.default_type(db)); visitor.visit_type(db, field.default_type(db));
} }
KnownInstanceType::NewType(newtype) => {
if let ClassType::Generic(generic_alias) = newtype.base_class_type(db) {
visitor.visit_generic_alias_type(db, generic_alias);
}
}
} }
} }
@ -6757,6 +6838,7 @@ impl<'db> KnownInstanceType<'db> {
Self::Deprecated(deprecated) Self::Deprecated(deprecated)
} }
Self::Field(field) => Self::Field(field.normalized_impl(db, visitor)), Self::Field(field) => Self::Field(field.normalized_impl(db, visitor)),
Self::NewType(newtype) => Self::NewType(newtype.normalized_impl(db, visitor)),
} }
} }
@ -6767,6 +6849,7 @@ impl<'db> KnownInstanceType<'db> {
Self::TypeAliasType(_) => KnownClass::TypeAliasType, Self::TypeAliasType(_) => KnownClass::TypeAliasType,
Self::Deprecated(_) => KnownClass::Deprecated, Self::Deprecated(_) => KnownClass::Deprecated,
Self::Field(_) => KnownClass::Field, Self::Field(_) => KnownClass::Field,
Self::NewType(_) => KnownClass::NewType,
} }
} }
@ -6817,6 +6900,9 @@ impl<'db> KnownInstanceType<'db> {
field.default_type(self.db).display(self.db).fmt(f)?; field.default_type(self.db).display(self.db).fmt(f)?;
f.write_str("]") f.write_str("]")
} }
KnownInstanceType::NewType(declaration) => {
f.write_str(declaration.name(self.db))
}
} }
} }
} }
@ -7185,6 +7271,158 @@ impl<'db> FieldInstance<'db> {
} }
} }
/// A `typing.NewType` declaration, either from the perspective of the
/// identity-function-that-acts-like-a-subclass-in-type-expressions returned by the call to
/// `NewType`, or from the perspective of instances of that subclass. For example:
///
/// ```py
/// from typing import NewType
/// Foo = NewType("Foo", int)
/// x = Foo(42)
/// ```
///
/// The revealed types there are:
/// - `NewType`: `Type::ClassLiteral(ClassLiteral)` with `KnownClass::NewType`.
/// - `Foo`: `Type::KnownInstance(KnownInstanceType::NewType(NewTypeInstance { .. }))`
/// - `x`: `Type::NominalInstance(...(NominalInstanceInner::NewType(NewTypeInstance { .. }))`
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct NewTypeInstance<'db> {
/// The name of this NewType (e.g. `"Foo"`)
#[returns(ref)]
pub name: ast::name::Name,
/// The binding where this NewType is first created.
pub definition: Definition<'db>,
// The base class of this NewType (e.g. `int`), which could be a (specialized) class type or
// could be another NewType.
pub base: NewTypeBase<'db>,
}
impl get_size2::GetSize for NewTypeInstance<'_> {}
impl<'db> NewTypeInstance<'db> {
// Walk the `NewTypeBase` chain to find the underlying `ClassType`.
fn base_class_type(self, db: &'db dyn Db) -> ClassType<'db> {
let mut base = self.base(db);
loop {
match base {
NewTypeBase::ClassType(class) => return class,
NewTypeBase::NewType(newtype) => base = newtype.base(db),
}
}
}
fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
let normalized_base = match self.base(db) {
NewTypeBase::ClassType(base_class) => {
NewTypeBase::ClassType(base_class.normalized_impl(db, visitor))
}
NewTypeBase::NewType(base_newtype) => {
NewTypeBase::NewType(base_newtype.normalized_impl(db, visitor))
}
};
Self::new(
db,
self.name(db).clone(),
self.definition(db),
normalized_base,
)
}
fn materialize(self, db: &'db dyn Db, materialization_kind: MaterializationKind) -> Self {
let materialized_base = match self.base(db) {
NewTypeBase::ClassType(base_class) => {
NewTypeBase::ClassType(base_class.materialize(db, materialization_kind))
}
NewTypeBase::NewType(base_newtype) => {
NewTypeBase::NewType(base_newtype.materialize(db, materialization_kind))
}
};
Self::new(
db,
self.name(db).clone(),
self.definition(db),
materialized_base,
)
}
fn apply_type_mapping_impl<'a>(
self,
db: &'db dyn Db,
type_mapping: &TypeMapping<'a, 'db>,
visitor: &ApplyTypeMappingVisitor<'db>,
) -> Self {
let mapped_base = match self.base(db) {
NewTypeBase::ClassType(base_class) => NewTypeBase::ClassType(
base_class.apply_type_mapping_impl(db, type_mapping, visitor),
),
NewTypeBase::NewType(base_newtype) => NewTypeBase::NewType(
base_newtype.apply_type_mapping_impl(db, type_mapping, visitor),
),
};
Self::new(db, self.name(db).clone(), self.definition(db), mapped_base)
}
fn is_subtype_of(self, db: &'db dyn Db, other: Self) -> bool {
let mut candidate_newtype = self;
loop {
if candidate_newtype == other {
// Either `self` is `other`, or it's a (transitive) NewType wrapper of it.
return true;
}
match candidate_newtype.base(db) {
NewTypeBase::NewType(base_newtype) => candidate_newtype = base_newtype,
// Classes can't inherit from NewTypes, so if we reach the base `ClassType` without
// seeing `other`, then we can't be a subtype of it.
NewTypeBase::ClassType(_) => return false,
}
}
}
fn has_relation_to_impl<C: Constraints<'db>>(
self,
db: &'db dyn Db,
other: Self,
_: TypeRelation,
_: &HasRelationToVisitor<'db, C>,
) -> C {
// TODO: Is this correct?
C::from_bool(db, self.is_subtype_of(db, other))
}
pub(super) fn is_disjoint_from_impl<C: Constraints<'db>>(
self,
db: &'db dyn Db,
other: Self,
_: &IsDisjointVisitor<'db, C>,
) -> C {
// Two NewTypes are disjoint if they're not equal and neither inherits from the other.
// (NewTypes have single inheritance, and a regular class can't inherit from a NewType, so
// it's not possible for some third type to multiply-inherit from both.)
C::from_bool(
db,
!self.is_subtype_of(db, other) && !other.is_subtype_of(db, self),
)
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, get_size2::GetSize)]
pub enum NewTypeBase<'db> {
ClassType(ClassType<'db>),
NewType(NewTypeInstance<'db>),
}
impl<'db> NewTypeBase<'db> {
fn to_type(self, db: &'db dyn Db) -> Type<'db> {
match self {
NewTypeBase::ClassType(base_class) => Type::instance(db, base_class),
NewTypeBase::NewType(base_newtype) => Type::newtype_nominal_instance(base_newtype),
}
}
}
/// Whether this typevar was created via the legacy `TypeVar` constructor, using PEP 695 syntax, /// Whether this typevar was created via the legacy `TypeVar` constructor, using PEP 695 syntax,
/// or an implicit typevar like `Self` was used. /// or an implicit typevar like `Self` was used.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, get_size2::GetSize)] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, get_size2::GetSize)]
@ -9976,7 +10214,9 @@ impl<'db> SuperOwnerKind<'db> {
Either::Left(ClassBase::Dynamic(dynamic).mro(db, None)) Either::Left(ClassBase::Dynamic(dynamic).mro(db, None))
} }
SuperOwnerKind::Class(class) => Either::Right(class.iter_mro(db)), SuperOwnerKind::Class(class) => Either::Right(class.iter_mro(db)),
SuperOwnerKind::Instance(instance) => Either::Right(instance.class(db).iter_mro(db)), SuperOwnerKind::Instance(instance) => {
Either::Right(instance.class_ignoring_newtype(db).iter_mro(db))
}
} }
} }
@ -9988,11 +10228,11 @@ impl<'db> SuperOwnerKind<'db> {
} }
} }
fn into_class(self, db: &'db dyn Db) -> Option<ClassType<'db>> { fn into_class_ignoring_newtype(self, db: &'db dyn Db) -> Option<ClassType<'db>> {
match self { match self {
SuperOwnerKind::Dynamic(_) => None, SuperOwnerKind::Dynamic(_) => None,
SuperOwnerKind::Class(class) => Some(class), SuperOwnerKind::Class(class) => Some(class),
SuperOwnerKind::Instance(instance) => Some(instance.class(db)), SuperOwnerKind::Instance(instance) => Some(instance.class_ignoring_newtype(db)),
} }
} }
@ -10111,7 +10351,7 @@ impl<'db> BoundSuperType<'db> {
let Some(pivot_class) = pivot_class.into_class() else { let Some(pivot_class) = pivot_class.into_class() else {
return Some(owner); return Some(owner);
}; };
let Some(owner_class) = owner.into_class(db) else { let Some(owner_class) = owner.into_class_ignoring_newtype(db) else {
return Some(owner); return Some(owner);
}; };
if owner_class.is_subclass_of(db, pivot_class) { if owner_class.is_subclass_of(db, pivot_class) {
@ -10212,7 +10452,7 @@ impl<'db> BoundSuperType<'db> {
.expect("Calling `find_name_in_mro` on dynamic type should return `Some`"); .expect("Calling `find_name_in_mro` on dynamic type should return `Some`");
} }
SuperOwnerKind::Class(class) => class, SuperOwnerKind::Class(class) => class,
SuperOwnerKind::Instance(instance) => instance.class(db), SuperOwnerKind::Instance(instance) => instance.class_ignoring_newtype(db),
}; };
let (class_literal, _) = class.class_literal(db); let (class_literal, _) = class.class_literal(db);

View File

@ -631,7 +631,14 @@ impl<'db> IntersectionBuilder<'db> {
self self
} }
Type::NominalInstance(instance) Type::NominalInstance(instance)
if enum_metadata(self.db, instance.class(self.db).class_literal(self.db).0) if !instance.is_newtype()
&& enum_metadata(
self.db,
instance
.class_ignoring_newtype(self.db)
.class_literal(self.db)
.0,
)
.is_some() => .is_some() =>
{ {
let mut contains_enum_literal_as_negative_element = false; let mut contains_enum_literal_as_negative_element = false;
@ -657,7 +664,11 @@ impl<'db> IntersectionBuilder<'db> {
self.add_positive_impl( self.add_positive_impl(
Type::Union(UnionType::new( Type::Union(UnionType::new(
db, db,
enum_member_literals(db, instance.class(db).class_literal(db).0, None) enum_member_literals(
db,
instance.class_ignoring_newtype(db).class_literal(db).0,
None,
)
.expect("Calling `enum_member_literals` on an enum class") .expect("Calling `enum_member_literals` on an enum class")
.collect::<Box<[_]>>(), .collect::<Box<[_]>>(),
)), )),
@ -854,7 +865,8 @@ impl<'db> InnerIntersectionBuilder<'db> {
_ => { _ => {
let known_instance = new_positive let known_instance = new_positive
.into_nominal_instance() .into_nominal_instance()
.and_then(|instance| instance.class(db).known(db)); .and_then(|instance| instance.class_if_not_newtype(db))
.and_then(|class| class.known(db));
if known_instance == Some(KnownClass::Object) { if known_instance == Some(KnownClass::Object) {
// `object & T` -> `T`; it is always redundant to add `object` to an intersection // `object & T` -> `T`; it is always redundant to add `object` to an intersection
@ -874,7 +886,9 @@ impl<'db> InnerIntersectionBuilder<'db> {
new_positive = Type::BooleanLiteral(false); new_positive = Type::BooleanLiteral(false);
} }
Type::NominalInstance(instance) Type::NominalInstance(instance)
if instance.class(db).is_known(db, KnownClass::Bool) => if instance
.class_if_not_newtype(db)
.is_some_and(|class| class.is_known(db, KnownClass::Bool)) =>
{ {
match new_positive { match new_positive {
// `bool & AlwaysTruthy` -> `Literal[True]` // `bool & AlwaysTruthy` -> `Literal[True]`
@ -968,7 +982,8 @@ impl<'db> InnerIntersectionBuilder<'db> {
self.positive self.positive
.iter() .iter()
.filter_map(|ty| ty.into_nominal_instance()) .filter_map(|ty| ty.into_nominal_instance())
.filter_map(|instance| instance.class(db).known(db)) .filter_map(|instance| instance.class_if_not_newtype(db))
.filter_map(|class| class.known(db))
.any(KnownClass::is_bool) .any(KnownClass::is_bool)
}; };

View File

@ -260,7 +260,9 @@ impl<'a, 'db> FromIterator<(Argument<'a>, Option<Type<'db>>)> for CallArguments<
pub(crate) fn is_expandable_type<'db>(db: &'db dyn Db, ty: Type<'db>) -> bool { pub(crate) fn is_expandable_type<'db>(db: &'db dyn Db, ty: Type<'db>) -> bool {
match ty { match ty {
Type::NominalInstance(instance) => { Type::NominalInstance(instance) => {
let class = instance.class(db); let Some(class) = instance.class_if_not_newtype(db) else {
return false;
};
class.is_known(db, KnownClass::Bool) class.is_known(db, KnownClass::Bool)
|| instance.tuple_spec(db).is_some_and(|spec| match &*spec { || instance.tuple_spec(db).is_some_and(|spec| match &*spec {
Tuple::Fixed(fixed_length_tuple) => fixed_length_tuple Tuple::Fixed(fixed_length_tuple) => fixed_length_tuple
@ -282,7 +284,7 @@ fn expand_type<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option<Vec<Type<'db>>> {
// NOTE: Update `is_expandable_type` if this logic changes accordingly. // NOTE: Update `is_expandable_type` if this logic changes accordingly.
match ty { match ty {
Type::NominalInstance(instance) => { Type::NominalInstance(instance) => {
let class = instance.class(db); let class = instance.class_if_not_newtype(db)?;
if class.is_known(db, KnownClass::Bool) { if class.is_known(db, KnownClass::Bool) {
return Some(vec![ return Some(vec![

View File

@ -20,7 +20,9 @@ use crate::semantic_index::{
}; };
use crate::types::constraints::{Constraints, IteratorConstraintsExtension}; use crate::types::constraints::{Constraints, IteratorConstraintsExtension};
use crate::types::context::InferContext; use crate::types::context::InferContext;
use crate::types::diagnostic::{INVALID_LEGACY_TYPE_VARIABLE, INVALID_TYPE_ALIAS_TYPE}; use crate::types::diagnostic::{
INVALID_LEGACY_TYPE_VARIABLE, INVALID_NEWTYPE, INVALID_TYPE_ALIAS_TYPE,
};
use crate::types::enums::enum_metadata; use crate::types::enums::enum_metadata;
use crate::types::function::{DataclassTransformerParams, KnownFunction}; use crate::types::function::{DataclassTransformerParams, KnownFunction};
use crate::types::generics::{GenericContext, Specialization, walk_specialization}; use crate::types::generics::{GenericContext, Specialization, walk_specialization};
@ -31,10 +33,10 @@ use crate::types::typed_dict::typed_dict_params_from_class_def;
use crate::types::{ use crate::types::{
ApplyTypeMappingVisitor, Binding, BoundSuperError, BoundSuperType, CallableType, ApplyTypeMappingVisitor, Binding, BoundSuperError, BoundSuperType, CallableType,
DataclassParams, DeprecatedInstance, HasRelationToVisitor, IsEquivalentVisitor, DataclassParams, DeprecatedInstance, HasRelationToVisitor, IsEquivalentVisitor,
KnownInstanceType, ManualPEP695TypeAliasType, MaterializationKind, NormalizedVisitor, KnownInstanceType, ManualPEP695TypeAliasType, MaterializationKind, NewTypeBase,
PropertyInstanceType, StringLiteralType, TypeAliasType, TypeMapping, TypeRelation, NewTypeInstance, NormalizedVisitor, PropertyInstanceType, StringLiteralType, TypeAliasType,
TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, TypedDictParams, VarianceInferable, TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind,
declaration_type, infer_definition_types, todo_type, TypedDictParams, VarianceInferable, declaration_type, infer_definition_types, todo_type,
}; };
use crate::{ use crate::{
Db, FxIndexMap, FxOrderSet, Program, Db, FxIndexMap, FxOrderSet, Program,
@ -1317,7 +1319,8 @@ impl<'db> Field<'db> {
pub(crate) fn is_kw_only_sentinel(&self, db: &'db dyn Db) -> bool { pub(crate) fn is_kw_only_sentinel(&self, db: &'db dyn Db) -> bool {
self.declared_ty self.declared_ty
.into_nominal_instance() .into_nominal_instance()
.is_some_and(|instance| instance.class(db).is_known(db, KnownClass::KwOnly)) .and_then(|instance| instance.class_if_not_newtype(db))
.is_some_and(|class| class.is_known(db, KnownClass::KwOnly))
} }
} }
@ -4245,14 +4248,6 @@ impl KnownClass {
.is_ok_and(|class| class.is_subclass_of(db, None, other)) .is_ok_and(|class| class.is_subclass_of(db, None, other))
} }
pub(super) fn when_subclass_of<'db, C: Constraints<'db>>(
self,
db: &'db dyn Db,
other: ClassType<'db>,
) -> C {
C::from_bool(db, self.is_subclass_of(db, other))
}
/// Return the module in which we should look up the definition for this class /// Return the module in which we should look up the definition for this class
fn canonical_module(self, db: &dyn Db) -> KnownModule { fn canonical_module(self, db: &dyn Db) -> KnownModule {
match self { match self {
@ -4969,6 +4964,69 @@ impl KnownClass {
))); )));
} }
KnownClass::NewType => {
let assigned_to = index
.try_expression(ast::ExprRef::from(call_expression))
.and_then(|expr| expr.assigned_to(db));
let Some(target) = assigned_to.as_ref().and_then(|assigned_to| {
match assigned_to.node(module).targets.as_slice() {
[ast::Expr::Name(target)] => Some(target),
_ => None,
}
}) else {
if let Some(builder) = context.report_lint(&INVALID_NEWTYPE, call_expression) {
builder.into_diagnostic(
"A `typing.NewType` must be immediately assigned to a variable",
);
}
return;
};
let definition = index.expect_single_definition(target);
let [Some(name), Some(supertype), ..] = overload.parameter_types() else {
return;
};
let Some(name) = name.into_string_literal() else {
if let Some(builder) = context.report_lint(&INVALID_NEWTYPE, call_expression) {
builder.into_diagnostic(
"The name of a `typing.NewType` must be a string literal",
);
}
return;
};
let newtype_base = match supertype {
Type::ClassLiteral(class_literal) => {
NewTypeBase::ClassType(class_literal.default_specialization(db))
}
Type::GenericAlias(alias) => NewTypeBase::ClassType(ClassType::Generic(*alias)),
Type::KnownInstance(KnownInstanceType::NewType(newtype)) => {
NewTypeBase::NewType(*newtype)
}
_ => {
if let Some(builder) =
context.report_lint(&INVALID_NEWTYPE, call_expression)
{
builder.into_diagnostic(
"The second argument to `typing.NewType` must be a class or another `NewType`",
);
}
return;
}
};
overload.set_return_type(Type::KnownInstance(KnownInstanceType::NewType(
NewTypeInstance::new(
db,
ast::name::Name::new(name.value(db)),
definition,
newtype_base,
),
)));
}
_ => {} _ => {}
} }
} }

View File

@ -80,7 +80,9 @@ impl<'db> ClassBase<'db> {
Type::ClassLiteral(literal) => Some(Self::Class(literal.default_specialization(db))), Type::ClassLiteral(literal) => Some(Self::Class(literal.default_specialization(db))),
Type::GenericAlias(generic) => Some(Self::Class(ClassType::Generic(generic))), Type::GenericAlias(generic) => Some(Self::Class(ClassType::Generic(generic))),
Type::NominalInstance(instance) Type::NominalInstance(instance)
if instance.class(db).is_known(db, KnownClass::GenericAlias) => if instance
.class_if_not_newtype(db)
.is_some_and(|class| class.is_known(db, KnownClass::GenericAlias)) =>
{ {
Self::try_from_type(db, todo_type!("GenericAlias instance"), subclass) Self::try_from_type(db, todo_type!("GenericAlias instance"), subclass)
} }
@ -164,7 +166,8 @@ impl<'db> ClassBase<'db> {
KnownInstanceType::TypeAliasType(_) KnownInstanceType::TypeAliasType(_)
| KnownInstanceType::TypeVar(_) | KnownInstanceType::TypeVar(_)
| KnownInstanceType::Deprecated(_) | KnownInstanceType::Deprecated(_)
| KnownInstanceType::Field(_) => None, | KnownInstanceType::Field(_)
| KnownInstanceType::NewType(_) => None,
}, },
Type::SpecialForm(special_form) => match special_form { Type::SpecialForm(special_form) => match special_form {

View File

@ -12,6 +12,7 @@ pub enum TypeDefinition<'db> {
Function(Definition<'db>), Function(Definition<'db>),
TypeVar(Definition<'db>), TypeVar(Definition<'db>),
TypeAlias(Definition<'db>), TypeAlias(Definition<'db>),
NewType(Definition<'db>),
} }
impl TypeDefinition<'_> { impl TypeDefinition<'_> {
@ -21,7 +22,8 @@ impl TypeDefinition<'_> {
Self::Class(definition) Self::Class(definition)
| Self::Function(definition) | Self::Function(definition)
| Self::TypeVar(definition) | Self::TypeVar(definition)
| Self::TypeAlias(definition) => { | Self::TypeAlias(definition)
| Self::NewType(definition) => {
let module = parsed_module(db, definition.file(db)).load(db); let module = parsed_module(db, definition.file(db)).load(db);
Some(definition.focus_range(db, &module)) Some(definition.focus_range(db, &module))
} }
@ -38,7 +40,8 @@ impl TypeDefinition<'_> {
Self::Class(definition) Self::Class(definition)
| Self::Function(definition) | Self::Function(definition)
| Self::TypeVar(definition) | Self::TypeVar(definition)
| Self::TypeAlias(definition) => { | Self::TypeAlias(definition)
| Self::NewType(definition) => {
let module = parsed_module(db, definition.file(db)).load(db); let module = parsed_module(db, definition.file(db)).load(db);
Some(definition.full_range(db, &module)) Some(definition.full_range(db, &module))
} }

View File

@ -61,6 +61,7 @@ pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) {
registry.register_lint(&INVALID_GENERIC_CLASS); registry.register_lint(&INVALID_GENERIC_CLASS);
registry.register_lint(&INVALID_LEGACY_TYPE_VARIABLE); registry.register_lint(&INVALID_LEGACY_TYPE_VARIABLE);
registry.register_lint(&INVALID_TYPE_ALIAS_TYPE); registry.register_lint(&INVALID_TYPE_ALIAS_TYPE);
registry.register_lint(&INVALID_NEWTYPE);
registry.register_lint(&INVALID_METACLASS); registry.register_lint(&INVALID_METACLASS);
registry.register_lint(&INVALID_OVERLOAD); registry.register_lint(&INVALID_OVERLOAD);
registry.register_lint(&INVALID_PARAMETER_DEFAULT); registry.register_lint(&INVALID_PARAMETER_DEFAULT);
@ -886,6 +887,27 @@ declare_lint! {
} }
} }
declare_lint! {
/// ## What it does
/// Checks for the creation of invalid `NewType`s
///
/// ## Why is this bad?
/// There are several requirements that you must follow when creating a `NewType`.
///
/// ## Examples
/// ```python
/// from typing import NewType
///
/// Foo = NewType("Foo", int) # okay
/// Bar = NewType(get_name(), int) # error: NewType name must be a string literal
/// ```
pub(crate) static INVALID_NEWTYPE = {
summary: "detects invalid NewType definitions",
status: LintStatus::preview("1.0.0"),
default_level: Level::Error,
}
}
declare_lint! { declare_lint! {
/// ## What it does /// ## What it does
/// Checks for arguments to `metaclass=` that are invalid. /// Checks for arguments to `metaclass=` that are invalid.
@ -1976,7 +1998,8 @@ pub(super) fn report_invalid_assignment(
fn type_to_class_literal<'db>(ty: Type<'db>, db: &'db dyn crate::Db) -> Option<ClassLiteral<'db>> { fn type_to_class_literal<'db>(ty: Type<'db>, db: &'db dyn crate::Db) -> Option<ClassLiteral<'db>> {
match ty { match ty {
Type::ClassLiteral(class) => Some(class), Type::ClassLiteral(class) => Some(class),
Type::NominalInstance(instance) => match instance.class(db) { // TODO: How should we handle NewTypes here?
Type::NominalInstance(instance) => match instance.class_ignoring_newtype(db) {
crate::types::class::ClassType::NonGeneric(class) => Some(class), crate::types::class::ClassType::NonGeneric(class) => Some(class),
crate::types::class::ClassType::Generic(alias) => Some(alias.origin(db)), crate::types::class::ClassType::Generic(alias) => Some(alias.origin(db)),
}, },
@ -2591,7 +2614,13 @@ pub(crate) fn report_undeclared_protocol_member(
SubclassOfInner::Dynamic(DynamicType::Any) => return true, SubclassOfInner::Dynamic(DynamicType::Any) => return true,
SubclassOfInner::Dynamic(_) => return false, SubclassOfInner::Dynamic(_) => return false,
}, },
Type::NominalInstance(instance) => instance.class(db), Type::NominalInstance(instance) => {
if let Some(class) = instance.class_if_not_newtype(db) {
class
} else {
return false;
}
}
_ => return false, _ => return false,
}; };

View File

@ -187,21 +187,22 @@ impl Display for DisplayRepresentation<'_> {
Type::Dynamic(dynamic) => dynamic.fmt(f), Type::Dynamic(dynamic) => dynamic.fmt(f),
Type::Never => f.write_str("Never"), Type::Never => f.write_str("Never"),
Type::NominalInstance(instance) => { Type::NominalInstance(instance) => {
let class = instance.class(self.db); let (class, newtype) = instance.class_and_newtype(self.db);
match (class, class.known(self.db)) { match (newtype, class, class.known(self.db)) {
(_, Some(KnownClass::NoneType)) => f.write_str("None"), (Some(newtype), _, _) => f.write_str(newtype.name(self.db)),
(_, Some(KnownClass::NoDefaultType)) => f.write_str("NoDefault"), (None, _, Some(KnownClass::NoneType)) => f.write_str("None"),
(ClassType::Generic(alias), Some(KnownClass::Tuple)) => alias (None, _, Some(KnownClass::NoDefaultType)) => f.write_str("NoDefault"),
(None, ClassType::Generic(alias), Some(KnownClass::Tuple)) => alias
.specialization(self.db) .specialization(self.db)
.tuple(self.db) .tuple(self.db)
.expect("Specialization::tuple() should always return `Some()` for `KnownClass::Tuple`") .expect("Specialization::tuple() should always return `Some()` for `KnownClass::Tuple`")
.display_with(self.db, self.settings) .display_with(self.db, self.settings)
.fmt(f), .fmt(f),
(ClassType::NonGeneric(class), _) => { (None, ClassType::NonGeneric(class), _) => {
self.write_maybe_qualified_class(f, class) self.write_maybe_qualified_class(f, class)
}, },
(ClassType::Generic(alias), _) => alias.display_with(self.db, self.settings).fmt(f), (None, ClassType::Generic(alias), _) => alias.display_with(self.db, self.settings).fmt(f),
} }
} }
Type::ProtocolInstance(protocol) => match protocol.inner { Type::ProtocolInstance(protocol) => match protocol.inner {

View File

@ -130,7 +130,8 @@ pub(crate) fn enum_metadata<'db>(
// Some types are specifically disallowed for enum members. // Some types are specifically disallowed for enum members.
return None; return None;
} }
Type::NominalInstance(instance) => match instance.class(db).known(db) { Type::NominalInstance(instance) => {
match instance.class_ignoring_newtype(db).known(db) {
// enum.nonmember // enum.nonmember
Some(KnownClass::Nonmember) => return None, Some(KnownClass::Nonmember) => return None,
@ -149,7 +150,8 @@ pub(crate) fn enum_metadata<'db>(
} }
_ => None, _ => None,
}, }
}
_ => None, _ => None,
}; };
@ -208,7 +210,10 @@ pub(crate) fn enum_metadata<'db>(
PlaceAndQualifiers { PlaceAndQualifiers {
place: Place::Type(Type::NominalInstance(instance), _), place: Place::Type(Type::NominalInstance(instance), _),
.. ..
} if instance.class(db).is_known(db, KnownClass::Member) => { } if instance
.class_ignoring_newtype(db) // TODO: what could it mean to wrap `enum.member` in a NewType?
.is_known(db, KnownClass::Member) =>
{
// If the attribute is specifically declared with `enum.member`, it is considered a member // If the attribute is specifically declared with `enum.member`, it is considered a member
} }
_ => { _ => {

View File

@ -953,7 +953,7 @@ fn is_instance_truthiness<'db>(
let is_instance = |ty: &Type<'_>| { let is_instance = |ty: &Type<'_>| {
if let Type::NominalInstance(instance) = ty { if let Type::NominalInstance(instance) = ty {
if instance if instance
.class(db) .class_ignoring_newtype(db)
.iter_mro(db) .iter_mro(db)
.filter_map(ClassBase::into_class) .filter_map(ClassBase::into_class)
.any(|c| match c { .any(|c| match c {

View File

@ -1119,9 +1119,9 @@ impl<'db> SpecializationBuilder<'db> {
// Extract formal_alias if this is a generic class // Extract formal_alias if this is a generic class
let formal_alias = match formal { let formal_alias = match formal {
Type::NominalInstance(formal_nominal) => { Type::NominalInstance(formal_nominal) => formal_nominal
formal_nominal.class(self.db).into_generic_alias() .class_if_not_newtype(self.db)
} .and_then(ClassType::into_generic_alias),
// TODO: This will only handle classes that explicit implement a generic protocol // TODO: This will only handle classes that explicit implement a generic protocol
// by listing it as a base class. To handle classes that implicitly implement a // by listing it as a base class. To handle classes that implicitly implement a
// generic protocol, we will need to check the types of the protocol members to be // generic protocol, we will need to check the types of the protocol members to be
@ -1135,7 +1135,10 @@ impl<'db> SpecializationBuilder<'db> {
if let Some(formal_alias) = formal_alias { if let Some(formal_alias) = formal_alias {
let formal_origin = formal_alias.origin(self.db); let formal_origin = formal_alias.origin(self.db);
for base in actual_nominal.class(self.db).iter_mro(self.db) { for base in actual_nominal
.class_ignoring_newtype(self.db)
.iter_mro(self.db)
{
let ClassBase::Class(ClassType::Generic(base_alias)) = base else { let ClassBase::Class(ClassType::Generic(base_alias)) = base else {
continue; continue;
}; };

View File

@ -95,7 +95,8 @@ impl<'db> AllMembers<'db> {
), ),
Type::NominalInstance(instance) => { Type::NominalInstance(instance) => {
let (class_literal, _specialization) = instance.class(db).class_literal(db); let (class_literal, _specialization) =
instance.class_ignoring_newtype(db).class_literal(db);
self.extend_with_instance_members(db, ty, class_literal); self.extend_with_instance_members(db, ty, class_literal);
} }
@ -211,7 +212,9 @@ impl<'db> AllMembers<'db> {
match ty { match ty {
Type::NominalInstance(instance) Type::NominalInstance(instance)
if matches!( if matches!(
instance.class(db).known(db), instance
.class_if_not_newtype(db)
.and_then(|class| class.known(db)),
Some( Some(
KnownClass::TypeVar KnownClass::TypeVar
| KnownClass::TypeVarTuple | KnownClass::TypeVarTuple

View File

@ -1823,7 +1823,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
Type::BooleanLiteral(_) | Type::IntLiteral(_) => {} Type::BooleanLiteral(_) | Type::IntLiteral(_) => {}
Type::NominalInstance(instance) Type::NominalInstance(instance)
if matches!( if matches!(
instance.class(self.db()).known(self.db()), instance.class_ignoring_newtype(self.db()).known(self.db()),
Some(KnownClass::Float | KnownClass::Int | KnownClass::Bool) Some(KnownClass::Float | KnownClass::Int | KnownClass::Bool)
) => {} ) => {}
_ => return false, _ => return false,
@ -4004,7 +4004,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
// Super instances do not allow attribute assignment // Super instances do not allow attribute assignment
Type::NominalInstance(instance) Type::NominalInstance(instance)
if instance.class(db).is_known(db, KnownClass::Super) => if instance
.class_ignoring_newtype(db)
.is_known(db, KnownClass::Super) =>
{ {
if emit_diagnostics { if emit_diagnostics {
if let Some(builder) = self.context.report_lint(&INVALID_ASSIGNMENT, target) { if let Some(builder) = self.context.report_lint(&INVALID_ASSIGNMENT, target) {
@ -6274,6 +6276,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
| KnownClass::TypeVar | KnownClass::TypeVar
| KnownClass::TypeAliasType | KnownClass::TypeAliasType
| KnownClass::Deprecated | KnownClass::Deprecated
| KnownClass::NewType
) )
) || ( ) || (
// Constructor calls to `tuple` and subclasses of `tuple` are handled in `Type::Bindings`, // Constructor calls to `tuple` and subclasses of `tuple` are handled in `Type::Bindings`,
@ -9157,7 +9160,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
Type::Dynamic(DynamicType::TodoUnpack) => Err(GenericContextError::NotYetSupported), Type::Dynamic(DynamicType::TodoUnpack) => Err(GenericContextError::NotYetSupported),
Type::NominalInstance(nominal) Type::NominalInstance(nominal)
if matches!( if matches!(
nominal.class(self.db()).known(self.db()), nominal.class_ignoring_newtype(self.db()).known(self.db()),
Some(KnownClass::TypeVarTuple | KnownClass::ParamSpec) Some(KnownClass::TypeVarTuple | KnownClass::ParamSpec)
) => ) =>
{ {
@ -9201,8 +9204,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
Some(ty @ (Type::IntLiteral(_) | Type::BooleanLiteral(_))) => SliceArg::Arg(ty), Some(ty @ (Type::IntLiteral(_) | Type::BooleanLiteral(_))) => SliceArg::Arg(ty),
Some(ty @ Type::NominalInstance(instance)) Some(ty @ Type::NominalInstance(instance))
if instance if instance
.class(self.db()) .class_if_not_newtype(self.db())
.is_known(self.db(), KnownClass::NoneType) => .is_some_and(|class| class.is_known(self.db(), KnownClass::NoneType)) =>
{ {
SliceArg::Arg(ty) SliceArg::Arg(ty)
} }
@ -10426,6 +10429,16 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
self.infer_type_expression(&subscript.slice); self.infer_type_expression(&subscript.slice);
todo_type!("Generic PEP-695 type alias") todo_type!("Generic PEP-695 type alias")
} }
KnownInstanceType::NewType(newtype) => {
self.infer_type_expression(&subscript.slice);
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) {
builder.into_diagnostic(format_args!(
"`{}` is a `NewType` and cannot be specialized",
newtype.name(self.db())
));
}
Type::unknown()
}
}, },
Type::Dynamic(DynamicType::Todo(_)) => { Type::Dynamic(DynamicType::Todo(_)) => {
self.infer_type_expression(slice); self.infer_type_expression(slice);
@ -11141,7 +11154,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
} }
Type::NominalInstance(nominal) Type::NominalInstance(nominal)
if nominal if nominal
.class(self.db()) // TODO: What could NewType mean here?
.class_ignoring_newtype(self.db())
.is_known(self.db(), KnownClass::ParamSpec) => .is_known(self.db(), KnownClass::ParamSpec) =>
{ {
return Some(Parameters::todo()); return Some(Parameters::todo());

View File

@ -13,8 +13,8 @@ use crate::types::protocol_class::walk_protocol_interface;
use crate::types::tuple::{TupleSpec, TupleType}; use crate::types::tuple::{TupleSpec, TupleType};
use crate::types::{ use crate::types::{
ApplyTypeMappingVisitor, ClassBase, HasRelationToVisitor, IsDisjointVisitor, ApplyTypeMappingVisitor, ClassBase, HasRelationToVisitor, IsDisjointVisitor,
IsEquivalentVisitor, MaterializationKind, NormalizedVisitor, TypeMapping, TypeRelation, IsEquivalentVisitor, MaterializationKind, NewTypeInstance, NormalizedVisitor, TypeMapping,
VarianceInferable, TypeRelation, VarianceInferable,
}; };
use crate::{Db, FxOrderSet}; use crate::{Db, FxOrderSet};
@ -41,6 +41,10 @@ impl<'db> Type<'db> {
} }
} }
pub(crate) fn newtype_nominal_instance(newtype: NewTypeInstance<'db>) -> Self {
Type::NominalInstance(NominalInstanceType(NominalInstanceInner::NewType(newtype)))
}
pub(crate) fn tuple(tuple: Option<TupleType<'db>>) -> Self { pub(crate) fn tuple(tuple: Option<TupleType<'db>>) -> Self {
let Some(tuple) = tuple else { let Some(tuple) = tuple else {
return Type::Never; return Type::Never;
@ -75,7 +79,7 @@ impl<'db> Type<'db> {
/// **Private** helper function to create a `Type::NominalInstance` from a class that /// **Private** helper function to create a `Type::NominalInstance` from a class that
/// is known not to be `Any`, a protocol class, or a typed dict class. /// is known not to be `Any`, a protocol class, or a typed dict class.
fn non_tuple_instance(class: ClassType<'db>) -> Self { fn non_tuple_instance(class: ClassType<'db>) -> Self {
Type::NominalInstance(NominalInstanceType(NominalInstanceInner::NonTuple(class))) Type::NominalInstance(NominalInstanceType(NominalInstanceInner::Class(class)))
} }
pub(crate) const fn into_nominal_instance(self) -> Option<NominalInstanceType<'db>> { pub(crate) const fn into_nominal_instance(self) -> Option<NominalInstanceType<'db>> {
@ -130,25 +134,42 @@ pub(super) fn walk_nominal_instance_type<'db, V: super::visitor::TypeVisitor<'db
nominal: NominalInstanceType<'db>, nominal: NominalInstanceType<'db>,
visitor: &V, visitor: &V,
) { ) {
visitor.visit_type(db, nominal.class(db).into()); visitor.visit_type(db, nominal.class_ignoring_newtype(db).into());
} }
impl<'db> NominalInstanceType<'db> { impl<'db> NominalInstanceType<'db> {
pub(super) fn class(&self, db: &'db dyn Db) -> ClassType<'db> { pub(super) fn class_and_newtype(
&self,
db: &'db dyn Db,
) -> (ClassType<'db>, Option<NewTypeInstance<'db>>) {
match self.0 { match self.0 {
NominalInstanceInner::ExactTuple(tuple) => tuple.to_class_type(db), NominalInstanceInner::ExactTuple(tuple) => (tuple.to_class_type(db), None),
NominalInstanceInner::NonTuple(class) => class, NominalInstanceInner::NewType(newtype) => (newtype.base_class_type(db), Some(newtype)),
NominalInstanceInner::Class(class) => (class, None),
} }
} }
pub(super) fn class_ignoring_newtype(&self, db: &'db dyn Db) -> ClassType<'db> {
self.class_and_newtype(db).0
}
pub(super) fn class_if_not_newtype(&self, db: &'db dyn Db) -> Option<ClassType<'db>> {
match self.0 {
NominalInstanceInner::NewType(_) => None,
_ => Some(self.class_ignoring_newtype(db)),
}
}
pub(super) fn is_newtype(&self) -> bool {
matches!(self.0, NominalInstanceInner::NewType(_))
}
/// If this is an instance type where the class has a tuple spec, returns the tuple spec. /// If this is an instance type where the class has a tuple spec, returns the tuple spec.
/// ///
/// I.e., for the type `tuple[int, str]`, this will return the tuple spec `[int, str]`. /// I.e., for the type `tuple[int, str]`, this will return the tuple spec `[int, str]`.
/// For a subclass of `tuple[int, str]`, it will return the same tuple spec. /// For a subclass of `tuple[int, str]`, it will return the same tuple spec.
pub(super) fn tuple_spec(&self, db: &'db dyn Db) -> Option<Cow<'db, TupleSpec<'db>>> { pub(super) fn tuple_spec(&self, db: &'db dyn Db) -> Option<Cow<'db, TupleSpec<'db>>> {
match self.0 { let from_class_type = |class: ClassType<'db>| {
NominalInstanceInner::ExactTuple(tuple) => Some(Cow::Borrowed(tuple.tuple(db))),
NominalInstanceInner::NonTuple(class) => {
// Avoid an expensive MRO traversal for common stdlib classes. // Avoid an expensive MRO traversal for common stdlib classes.
if class if class
.known(db) .known(db)
@ -164,22 +185,24 @@ impl<'db> NominalInstanceType<'db> {
// the correct tuple spec for `sys._version_info`, since we special-case the class // the correct tuple spec for `sys._version_info`, since we special-case the class
// in `ClassLiteral::explicit_bases()` so that it is inferred as inheriting from // in `ClassLiteral::explicit_bases()` so that it is inferred as inheriting from
// a tuple type with the correct spec for the user's configured Python version and platform. // a tuple type with the correct spec for the user's configured Python version and platform.
KnownClass::VersionInfo => { KnownClass::VersionInfo => Some(Cow::Owned(TupleSpec::version_info_spec(db))),
Some(Cow::Owned(TupleSpec::version_info_spec(db)))
}
KnownClass::Tuple => Some( KnownClass::Tuple => Some(
class class
.into_generic_alias() .into_generic_alias()
.and_then(|alias| { .and_then(|alias| {
Some(Cow::Borrowed(alias.specialization(db).tuple(db)?)) Some(Cow::Borrowed(alias.specialization(db).tuple(db)?))
}) })
.unwrap_or_else(|| { .unwrap_or_else(|| Cow::Owned(TupleSpec::homogeneous(Type::unknown()))),
Cow::Owned(TupleSpec::homogeneous(Type::unknown()))
}),
), ),
_ => None, _ => None,
}) })
};
match self.0 {
NominalInstanceInner::ExactTuple(tuple) => Some(Cow::Borrowed(tuple.tuple(db))),
NominalInstanceInner::NewType(declaration) => {
from_class_type(declaration.base_class_type(db))
} }
NominalInstanceInner::Class(class) => from_class_type(class),
} }
} }
@ -187,7 +210,10 @@ impl<'db> NominalInstanceType<'db> {
pub(super) fn is_object(self, db: &'db dyn Db) -> bool { pub(super) fn is_object(self, db: &'db dyn Db) -> bool {
match self.0 { match self.0 {
NominalInstanceInner::ExactTuple(_) => false, NominalInstanceInner::ExactTuple(_) => false,
NominalInstanceInner::NonTuple(class) => class.is_object(db), NominalInstanceInner::NewType(declaration) => {
declaration.base_class_type(db).is_object(db)
}
NominalInstanceInner::Class(class) => class.is_object(db),
} }
} }
@ -204,7 +230,8 @@ impl<'db> NominalInstanceType<'db> {
pub(super) fn own_tuple_spec(&self, db: &'db dyn Db) -> Option<Cow<'db, TupleSpec<'db>>> { pub(super) fn own_tuple_spec(&self, db: &'db dyn Db) -> Option<Cow<'db, TupleSpec<'db>>> {
match self.0 { match self.0 {
NominalInstanceInner::ExactTuple(tuple) => Some(Cow::Borrowed(tuple.tuple(db))), NominalInstanceInner::ExactTuple(tuple) => Some(Cow::Borrowed(tuple.tuple(db))),
NominalInstanceInner::NonTuple(_) => None, NominalInstanceInner::NewType(_) => None,
NominalInstanceInner::Class(_) => None,
} }
} }
@ -216,7 +243,8 @@ impl<'db> NominalInstanceType<'db> {
pub(crate) fn slice_literal(self, db: &'db dyn Db) -> Option<SliceLiteral> { pub(crate) fn slice_literal(self, db: &'db dyn Db) -> Option<SliceLiteral> {
let class = match self.0 { let class = match self.0 {
NominalInstanceInner::ExactTuple(_) => return None, NominalInstanceInner::ExactTuple(_) => return None,
NominalInstanceInner::NonTuple(class) => class, NominalInstanceInner::NewType(_) => return None,
NominalInstanceInner::Class(class) => class,
}; };
let (class, Some(specialization)) = class.class_literal(db) else { let (class, Some(specialization)) = class.class_literal(db) else {
return None; return None;
@ -232,7 +260,9 @@ impl<'db> NominalInstanceType<'db> {
Type::IntLiteral(n) => i32::try_from(*n).map(Some).ok(), Type::IntLiteral(n) => i32::try_from(*n).map(Some).ok(),
Type::BooleanLiteral(b) => Some(Some(i32::from(*b))), Type::BooleanLiteral(b) => Some(Some(i32::from(*b))),
Type::NominalInstance(instance) Type::NominalInstance(instance)
if instance.class(db).is_known(db, KnownClass::NoneType) => if instance
.class_ignoring_newtype(db)
.is_known(db, KnownClass::NoneType) =>
{ {
Some(None) Some(None)
} }
@ -254,7 +284,10 @@ impl<'db> NominalInstanceType<'db> {
NominalInstanceInner::ExactTuple(tuple) => { NominalInstanceInner::ExactTuple(tuple) => {
Type::tuple(tuple.normalized_impl(db, visitor)) Type::tuple(tuple.normalized_impl(db, visitor))
} }
NominalInstanceInner::NonTuple(class) => { NominalInstanceInner::NewType(newtype) => {
Type::newtype_nominal_instance(newtype.normalized_impl(db, visitor))
}
NominalInstanceInner::Class(class) => {
Type::non_tuple_instance(class.normalized_impl(db, visitor)) Type::non_tuple_instance(class.normalized_impl(db, visitor))
} }
} }
@ -269,7 +302,10 @@ impl<'db> NominalInstanceType<'db> {
NominalInstanceInner::ExactTuple(tuple) => { NominalInstanceInner::ExactTuple(tuple) => {
Type::tuple(tuple.materialize(db, materialization_kind)) Type::tuple(tuple.materialize(db, materialization_kind))
} }
NominalInstanceInner::NonTuple(class) => { NominalInstanceInner::NewType(newtype) => {
Type::newtype_nominal_instance(newtype.materialize(db, materialization_kind))
}
NominalInstanceInner::Class(class) => {
Type::non_tuple_instance(class.materialize(db, materialization_kind)) Type::non_tuple_instance(class.materialize(db, materialization_kind))
} }
} }
@ -283,13 +319,25 @@ impl<'db> NominalInstanceType<'db> {
visitor: &HasRelationToVisitor<'db, C>, visitor: &HasRelationToVisitor<'db, C>,
) -> C { ) -> C {
match (self.0, other.0) { match (self.0, other.0) {
(NominalInstanceInner::NewType(newtype1), NominalInstanceInner::NewType(newtype2)) => {
newtype1.has_relation_to_impl(db, newtype2, relation, visitor)
}
// A non-NewType is never a subtype of a NewType.
(_, NominalInstanceInner::NewType(_)) => C::unsatisfiable(db),
(NominalInstanceInner::NewType(newtype), _) => {
Self(NominalInstanceInner::Class(newtype.base_class_type(db)))
.has_relation_to_impl(db, other, relation, visitor)
}
( (
NominalInstanceInner::ExactTuple(tuple1), NominalInstanceInner::ExactTuple(tuple1),
NominalInstanceInner::ExactTuple(tuple2), NominalInstanceInner::ExactTuple(tuple2),
) => tuple1.has_relation_to_impl(db, tuple2, relation, visitor), ) => tuple1.has_relation_to_impl(db, tuple2, relation, visitor),
_ => self _ => self.class_ignoring_newtype(db).has_relation_to_impl(
.class(db) db,
.has_relation_to_impl(db, other.class(db), relation, visitor), other.class_ignoring_newtype(db),
relation,
visitor,
),
} }
} }
@ -304,7 +352,7 @@ impl<'db> NominalInstanceType<'db> {
NominalInstanceInner::ExactTuple(tuple1), NominalInstanceInner::ExactTuple(tuple1),
NominalInstanceInner::ExactTuple(tuple2), NominalInstanceInner::ExactTuple(tuple2),
) => tuple1.is_equivalent_to_impl(db, tuple2, visitor), ) => tuple1.is_equivalent_to_impl(db, tuple2, visitor),
(NominalInstanceInner::NonTuple(class1), NominalInstanceInner::NonTuple(class2)) => { (NominalInstanceInner::Class(class1), NominalInstanceInner::Class(class2)) => {
class1.is_equivalent_to_impl(db, class2, visitor) class1.is_equivalent_to_impl(db, class2, visitor)
} }
_ => C::unsatisfiable(db), _ => C::unsatisfiable(db),
@ -327,10 +375,14 @@ impl<'db> NominalInstanceType<'db> {
} }
} }
result.or(db, || { result.or(db, || {
C::from_bool( let (self_class, self_newtype) = self.class_and_newtype(db);
db, let (other_class, other_newtype) = other.class_and_newtype(db);
!(self.class(db)).could_coexist_in_mro_with(db, other.class(db)), match (self_newtype, other_newtype) {
) (Some(self_newtype), Some(other_newtype)) => {
self_newtype.is_disjoint_from_impl(db, other_newtype, visitor)
}
_ => C::from_bool(db, !self_class.could_coexist_in_mro_with(db, other_class)),
}
}) })
} }
@ -342,7 +394,8 @@ impl<'db> NominalInstanceType<'db> {
// See: // See:
// https://docs.python.org/3/reference/expressions.html#parenthesized-forms // https://docs.python.org/3/reference/expressions.html#parenthesized-forms
NominalInstanceInner::ExactTuple(_) => false, NominalInstanceInner::ExactTuple(_) => false,
NominalInstanceInner::NonTuple(class) => class NominalInstanceInner::NewType(_) => false,
NominalInstanceInner::Class(class) => class
.known(db) .known(db)
.map(KnownClass::is_singleton) .map(KnownClass::is_singleton)
.unwrap_or_else(|| is_single_member_enum(db, class.class_literal(db).0)), .unwrap_or_else(|| is_single_member_enum(db, class.class_literal(db).0)),
@ -350,18 +403,24 @@ impl<'db> NominalInstanceType<'db> {
} }
pub(super) fn is_single_valued(self, db: &'db dyn Db) -> bool { pub(super) fn is_single_valued(self, db: &'db dyn Db) -> bool {
match self.0 { let class_type_is_single_valued = |class: ClassType<'db>| {
NominalInstanceInner::ExactTuple(tuple) => tuple.is_single_valued(db), class
NominalInstanceInner::NonTuple(class) => class
.known(db) .known(db)
.and_then(KnownClass::is_single_valued) .and_then(KnownClass::is_single_valued)
.or_else(|| Some(self.tuple_spec(db)?.is_single_valued(db))) .or_else(|| Some(self.tuple_spec(db)?.is_single_valued(db)))
.unwrap_or_else(|| is_single_member_enum(db, class.class_literal(db).0)), .unwrap_or_else(|| is_single_member_enum(db, class.class_literal(db).0))
};
match self.0 {
NominalInstanceInner::ExactTuple(tuple) => tuple.is_single_valued(db),
NominalInstanceInner::NewType(newtype) => {
class_type_is_single_valued(newtype.base_class_type(db))
}
NominalInstanceInner::Class(class) => class_type_is_single_valued(class),
} }
} }
pub(super) fn to_meta_type(self, db: &'db dyn Db) -> Type<'db> { pub(super) fn to_meta_type(self, db: &'db dyn Db) -> Type<'db> {
SubclassOfType::from(db, self.class(db)) SubclassOfType::from(db, self.class_ignoring_newtype(db))
} }
pub(super) fn apply_type_mapping_impl<'a>( pub(super) fn apply_type_mapping_impl<'a>(
@ -374,7 +433,10 @@ impl<'db> NominalInstanceType<'db> {
NominalInstanceInner::ExactTuple(tuple) => { NominalInstanceInner::ExactTuple(tuple) => {
Type::tuple(tuple.apply_type_mapping_impl(db, type_mapping, visitor)) Type::tuple(tuple.apply_type_mapping_impl(db, type_mapping, visitor))
} }
NominalInstanceInner::NonTuple(class) => { NominalInstanceInner::NewType(newtype) => Type::newtype_nominal_instance(
newtype.apply_type_mapping_impl(db, type_mapping, visitor),
),
NominalInstanceInner::Class(class) => {
Type::non_tuple_instance(class.apply_type_mapping_impl(db, type_mapping, visitor)) Type::non_tuple_instance(class.apply_type_mapping_impl(db, type_mapping, visitor))
} }
} }
@ -390,7 +452,12 @@ impl<'db> NominalInstanceType<'db> {
NominalInstanceInner::ExactTuple(tuple) => { NominalInstanceInner::ExactTuple(tuple) => {
tuple.find_legacy_typevars(db, binding_context, typevars); tuple.find_legacy_typevars(db, binding_context, typevars);
} }
NominalInstanceInner::NonTuple(class) => { NominalInstanceInner::NewType(newtype) => {
newtype
.base_class_type(db)
.find_legacy_typevars(db, binding_context, typevars);
}
NominalInstanceInner::Class(class) => {
class.find_legacy_typevars(db, binding_context, typevars); class.find_legacy_typevars(db, binding_context, typevars);
} }
} }
@ -413,12 +480,19 @@ enum NominalInstanceInner<'db> {
/// Note that the type `tuple[int, str]` includes subtypes of `tuple[int, str]`, /// Note that the type `tuple[int, str]` includes subtypes of `tuple[int, str]`,
/// but those subtypes would be represented using the `NonTuple` variant. /// but those subtypes would be represented using the `NonTuple` variant.
ExactTuple(TupleType<'db>), ExactTuple(TupleType<'db>),
/// A `NewType` instance type, e.g. `NewType("Foo", int)`.
///
/// These instances are the return values of the return values of (sic) `typing.NewType`. See
/// `NewTypeInstance` for an example.
NewType(NewTypeInstance<'db>),
/// Any instance type that does not represent some kind of instance of the /// Any instance type that does not represent some kind of instance of the
/// builtin `tuple` class. /// builtin `tuple` class.
/// ///
/// This variant includes types that are subtypes of "exact tuple" types, /// This variant includes types that are subtypes of "exact tuple" types,
/// because they represent "all instances of a class that is a tuple subclass". /// because they represent "all instances of a class that is a tuple subclass".
NonTuple(ClassType<'db>), Class(ClassType<'db>),
} }
pub(crate) struct SliceLiteral { pub(crate) struct SliceLiteral {
@ -429,7 +503,7 @@ pub(crate) struct SliceLiteral {
impl<'db> VarianceInferable<'db> for NominalInstanceType<'db> { impl<'db> VarianceInferable<'db> for NominalInstanceType<'db> {
fn variance_of(self, db: &'db dyn Db, typevar: BoundTypeVarInstance<'db>) -> TypeVarVariance { fn variance_of(self, db: &'db dyn Db, typevar: BoundTypeVarInstance<'db>) -> TypeVarVariance {
self.class(db).variance_of(db, typevar) self.class_ignoring_newtype(db).variance_of(db, typevar)
} }
} }

View File

@ -554,7 +554,9 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
} }
// Treat `bool` as `Literal[True, False]`. // Treat `bool` as `Literal[True, False]`.
Type::NominalInstance(instance) Type::NominalInstance(instance)
if instance.class(db).is_known(db, KnownClass::Bool) => if instance
.class_if_not_newtype(db)
.is_some_and(|class| class.is_known(db, KnownClass::Bool)) =>
{ {
UnionType::from_elements( UnionType::from_elements(
db, db,
@ -565,11 +567,20 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
} }
// Treat enums as a union of their members. // Treat enums as a union of their members.
Type::NominalInstance(instance) Type::NominalInstance(instance)
if enum_metadata(db, instance.class(db).class_literal(db).0).is_some() => if !instance.is_newtype()
&& enum_metadata(
db,
instance.class_ignoring_newtype(db).class_literal(db).0,
)
.is_some() =>
{ {
UnionType::from_elements( UnionType::from_elements(
db, db,
enum_member_literals(db, instance.class(db).class_literal(db).0, None) enum_member_literals(
db,
instance.class_ignoring_newtype(db).class_literal(db).0,
None,
)
.expect("Calling `enum_member_literals` on an enum class") .expect("Calling `enum_member_literals` on an enum class")
.map(|ty| filter_to_cannot_be_equal(db, ty, rhs_ty)), .map(|ty| filter_to_cannot_be_equal(db, ty, rhs_ty)),
) )
@ -596,7 +607,9 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
fn evaluate_expr_ne(&mut self, lhs_ty: Type<'db>, rhs_ty: Type<'db>) -> Option<Type<'db>> { fn evaluate_expr_ne(&mut self, lhs_ty: Type<'db>, rhs_ty: Type<'db>) -> Option<Type<'db>> {
match (lhs_ty, rhs_ty) { match (lhs_ty, rhs_ty) {
(Type::NominalInstance(instance), Type::IntLiteral(i)) (Type::NominalInstance(instance), Type::IntLiteral(i))
if instance.class(self.db).is_known(self.db, KnownClass::Bool) => if instance
.class_if_not_newtype(self.db)
.is_some_and(|class| class.is_known(self.db, KnownClass::Bool)) =>
{ {
if i == 0 { if i == 0 {
Some(Type::BooleanLiteral(false).negate(self.db)) Some(Type::BooleanLiteral(false).negate(self.db))

View File

@ -153,7 +153,7 @@ impl Ty {
.place .place
.expect_type(); .expect_type();
debug_assert!( debug_assert!(
matches!(ty, Type::NominalInstance(instance) if is_single_member_enum(db, instance.class(db).class_literal(db).0)) matches!(ty, Type::NominalInstance(instance) if is_single_member_enum(db, instance.class_ignoring_newtype(db).class_literal(db).0))
); );
ty ty
} }

View File

@ -131,7 +131,15 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
(_, Type::TypeIs(_)) => Ordering::Greater, (_, Type::TypeIs(_)) => Ordering::Greater,
(Type::NominalInstance(left), Type::NominalInstance(right)) => { (Type::NominalInstance(left), Type::NominalInstance(right)) => {
left.class(db).cmp(&right.class(db)) let (left_class, left_newtype) = left.class_and_newtype(db);
let (right_class, right_newtype) = right.class_and_newtype(db);
// NewTypes order after regular classes.
match (left_newtype, right_newtype) {
(None, None) => left_class.cmp(&right_class),
(Some(_), None) => Ordering::Greater,
(None, Some(_)) => Ordering::Less,
(Some(left_newtype), Some(right_newtype)) => left_newtype.cmp(&right_newtype),
}
} }
(Type::NominalInstance(_), _) => Ordering::Less, (Type::NominalInstance(_), _) => Ordering::Less,
(_, Type::NominalInstance(_)) => Ordering::Greater, (_, Type::NominalInstance(_)) => Ordering::Greater,
@ -180,7 +188,17 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
(SuperOwnerKind::Class(_), _) => Ordering::Less, (SuperOwnerKind::Class(_), _) => Ordering::Less,
(_, SuperOwnerKind::Class(_)) => Ordering::Greater, (_, SuperOwnerKind::Class(_)) => Ordering::Greater,
(SuperOwnerKind::Instance(left), SuperOwnerKind::Instance(right)) => { (SuperOwnerKind::Instance(left), SuperOwnerKind::Instance(right)) => {
left.class(db).cmp(&right.class(db)) let (left_class, left_newtype) = left.class_and_newtype(db);
let (right_class, right_newtype) = right.class_and_newtype(db);
// NewTypes order after regular classes.
match (left_newtype, right_newtype) {
(None, None) => left_class.cmp(&right_class),
(Some(_), None) => Ordering::Greater,
(None, Some(_)) => Ordering::Less,
(Some(left_newtype), Some(right_newtype)) => {
left_newtype.cmp(&right_newtype)
}
}
} }
(SuperOwnerKind::Instance(_), _) => Ordering::Less, (SuperOwnerKind::Instance(_), _) => Ordering::Less,
(_, SuperOwnerKind::Instance(_)) => Ordering::Greater, (_, SuperOwnerKind::Instance(_)) => Ordering::Greater,

10
ty.schema.json generated
View File

@ -581,6 +581,16 @@
} }
] ]
}, },
"invalid-newtype": {
"title": "detects invalid NewType definitions",
"description": "## What it does\nChecks for the creation of invalid `NewType`s\n\n## Why is this bad?\nThere are several requirements that you must follow when creating a `NewType`.\n\n## Examples\n```python\nfrom typing import NewType\n\nFoo = NewType(\"Foo\", int) # okay\nBar = NewType(get_name(), int) # error: NewType name must be a string literal\n```",
"default": "error",
"oneOf": [
{
"$ref": "#/definitions/Level"
}
]
},
"invalid-overload": { "invalid-overload": {
"title": "detects invalid `@overload` usages", "title": "detects invalid `@overload` usages",
"description": "## What it does\nChecks for various invalid `@overload` usages.\n\n## Why is this bad?\nThe `@overload` decorator is used to define functions and methods that accepts different\ncombinations of arguments and return different types based on the arguments passed. This is\nmainly beneficial for type checkers. But, if the `@overload` usage is invalid, the type\nchecker may not be able to provide correct type information.\n\n## Example\n\nDefining only one overload:\n\n```py\nfrom typing import overload\n\n@overload\ndef foo(x: int) -> int: ...\ndef foo(x: int | None) -> int | None:\n return x\n```\n\nOr, not providing an implementation for the overloaded definition:\n\n```py\nfrom typing import overload\n\n@overload\ndef foo() -> None: ...\n@overload\ndef foo(x: int) -> int: ...\n```\n\n## References\n- [Python documentation: `@overload`](https://docs.python.org/3/library/typing.html#typing.overload)", "description": "## What it does\nChecks for various invalid `@overload` usages.\n\n## Why is this bad?\nThe `@overload` decorator is used to define functions and methods that accepts different\ncombinations of arguments and return different types based on the arguments passed. This is\nmainly beneficial for type checkers. But, if the `@overload` usage is invalid, the type\nchecker may not be able to provide correct type information.\n\n## Example\n\nDefining only one overload:\n\n```py\nfrom typing import overload\n\n@overload\ndef foo(x: int) -> int: ...\ndef foo(x: int | None) -> int | None:\n return x\n```\n\nOr, not providing an implementation for the overloaded definition:\n\n```py\nfrom typing import overload\n\n@overload\ndef foo() -> None: ...\n@overload\ndef foo(x: int) -> int: ...\n```\n\n## References\n- [Python documentation: `@overload`](https://docs.python.org/3/library/typing.html#typing.overload)",