[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>
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) ·
[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>
**What it does**
@ -58,7 +58,7 @@ Calling a non-callable object will raise a `TypeError` at runtime.
<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%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>
**What it does**
@ -88,7 +88,7 @@ f(int) # error
<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%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>
**What it does**
@ -117,7 +117,7 @@ a = 1
<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%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>
**What it does**
@ -147,7 +147,7 @@ class C(A, B): ...
<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%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>
**What it does**
@ -177,7 +177,7 @@ class B(A): ...
<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%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>
**What it does**
@ -202,7 +202,7 @@ class B(A, A): ...
<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%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>
**What it does**
@ -306,7 +306,7 @@ def test(): -> "Literal[5]":
<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%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>
**What it does**
@ -334,7 +334,7 @@ class C(A, B): ...
<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%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>
**What it does**
@ -358,7 +358,7 @@ t[3] # IndexError: tuple index out of range
<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%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>
**What it does**
@ -445,7 +445,7 @@ an atypical memory layout.
<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-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>
**What it does**
@ -470,7 +470,7 @@ func("foo") # error: [invalid-argument-type]
<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-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>
**What it does**
@ -496,7 +496,7 @@ a: int = ''
<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-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>
**What it does**
@ -528,7 +528,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable
<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-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>
**What it does**
@ -562,7 +562,7 @@ asyncio.run(main())
<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-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>
**What it does**
@ -584,7 +584,7 @@ class A(42): ... # error: [invalid-base]
<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-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>
**What it does**
@ -609,7 +609,7 @@ with 1:
<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-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>
**What it does**
@ -636,7 +636,7 @@ a: str
<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-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>
**What it does**
@ -678,7 +678,7 @@ except ZeroDivisionError:
<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-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>
**What it does**
@ -709,7 +709,7 @@ class C[U](Generic[T]): ...
<small>
Default level: [`error`](../rules.md#rule-levels "This lint has a default level of 'error'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-key) ·
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L566)
[View source](https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L567)
</small>
**What it does**
@ -738,7 +738,7 @@ alice["height"] # KeyError: 'height'
<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-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>
**What it does**
@ -771,7 +771,7 @@ def f(t: TypeVar("U")): ...
<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-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>
**What it does**
@ -803,7 +803,7 @@ class B(metaclass=f): ...
<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-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>
**What it does**
@ -828,12 +828,37 @@ in a class's bases list.
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`
<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-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>
**What it does**
@ -881,7 +906,7 @@ def foo(x: int) -> int: ...
<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-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>
**What it does**
@ -905,7 +930,7 @@ def f(a: int = ''): ...
<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-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>
**What it does**
@ -937,7 +962,7 @@ TypeError: Protocols can only inherit from other protocols, got <class 'int'>
<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-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>
Checks for `raise` statements that raise non-exceptions or use invalid
@ -984,7 +1009,7 @@ def g():
<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-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>
**What it does**
@ -1007,7 +1032,7 @@ def func() -> int:
<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-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>
**What it does**
@ -1061,7 +1086,7 @@ TODO #14889
<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-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>
**What it does**
@ -1086,7 +1111,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus
<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-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>
**What it does**
@ -1114,7 +1139,7 @@ TYPE_CHECKING = ''
<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-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>
**What it does**
@ -1142,7 +1167,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments
<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-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>
**What it does**
@ -1174,7 +1199,7 @@ f(10) # Error
<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-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>
**What it does**
@ -1206,7 +1231,7 @@ class C:
<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-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>
**What it does**
@ -1239,7 +1264,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar
<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%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>
**What it does**
@ -1262,7 +1287,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x'
<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%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>
**What it does**
@ -1293,7 +1318,7 @@ alice["age"] # KeyError
<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%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>
**What it does**
@ -1320,7 +1345,7 @@ func("string") # error: [no-matching-overload]
<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%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>
**What it does**
@ -1342,7 +1367,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt
<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%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>
**What it does**
@ -1366,7 +1391,7 @@ for i in 34: # TypeError: 'int' object is not iterable
<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%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>
**What it does**
@ -1420,7 +1445,7 @@ def test(): -> "int":
<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%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>
**What it does**
@ -1448,7 +1473,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr
<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%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>
**What it does**
@ -1475,7 +1500,7 @@ class B(A): ... # Error raised here
<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%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>
**What it does**
@ -1500,7 +1525,7 @@ f("foo") # Error raised here
<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%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>
**What it does**
@ -1526,7 +1551,7 @@ def _(x: int):
<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%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>
**What it does**
@ -1570,7 +1595,7 @@ class A:
<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%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>
**What it does**
@ -1595,7 +1620,7 @@ f(x=1, y=2) # Error raised here
<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%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>
**What it does**
@ -1621,7 +1646,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo'
<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%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>
**What it does**
@ -1644,7 +1669,7 @@ import foo # ModuleNotFoundError: No module named 'foo'
<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%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>
**What it does**
@ -1667,7 +1692,7 @@ print(x) # NameError: name 'x' is not defined
<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%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>
**What it does**
@ -1702,7 +1727,7 @@ b1 < b2 < b1 # exception raised here
<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%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>
**What it does**
@ -1728,7 +1753,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A'
<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%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>
**What it does**
@ -1751,7 +1776,7 @@ l[1:10:0] # ValueError: slice step cannot be zero
<small>
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) ·
[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>
**What it does**
@ -1790,7 +1815,7 @@ class SubProto(BaseProto, Protocol):
<small>
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) ·
[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>
**What it does**
@ -1843,7 +1868,7 @@ a = 20 / 0 # type: ignore
<small>
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) ·
[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>
**What it does**
@ -1869,7 +1894,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c'
<small>
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) ·
[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>
**What it does**
@ -1899,7 +1924,7 @@ A()[0] # TypeError: 'A' object is not subscriptable
<small>
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) ·
[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>
**What it does**
@ -1929,7 +1954,7 @@ from module import a # ImportError: cannot import name 'a' from 'module'
<small>
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) ·
[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>
**What it does**
@ -1954,7 +1979,7 @@ cast(int, f()) # Redundant
<small>
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) ·
[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>
**What it does**
@ -2005,7 +2030,7 @@ a = 20 / 0 # ty: ignore[division-by-zero]
<small>
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) ·
[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>
**What it does**
@ -2059,7 +2084,7 @@ def g():
<small>
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) ·
[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>
**What it does**
@ -2096,7 +2121,7 @@ class D(C): ... # error: [unsupported-base]
<small>
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) ·
[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>
**What it does**
@ -2118,7 +2143,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime.
<small>
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) ·
[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>
**What it does**

View File

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

View File

@ -1,7 +1,5 @@
# NewType
Currently, ty doesn't support `typing.NewType` in type annotations.
## Valid forms
```py
@ -12,13 +10,44 @@ X = GenericAlias(type, ())
A = NewType("A", int)
# TODO: typeshed for `typing.GenericAlias` uses `type` for the first argument. `NewType` should be special-cased
# 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, ())
def _(
a: A,
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)
```
## 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 {
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 {
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 {
self.into_nominal_instance().is_some_and(|instance| {
instance
.class(db)
.is_known(db, KnownClass::NotImplementedType)
})
self.into_nominal_instance()
.and_then(|instance| instance.class_if_not_newtype(db))
.is_some_and(|class| class.is_known(db, KnownClass::NotImplementedType))
}
pub(crate) fn is_object(&self, db: &'db dyn Db) -> bool {
@ -1932,8 +1932,13 @@ impl<'db> Type<'db> {
return C::unsatisfiable(db);
}
let class_literal = instance.class(db).class_literal(db).0;
C::from_bool(db, is_single_member_enum(db, class_literal))
C::from_bool(
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),
}
@ -2232,7 +2237,7 @@ impl<'db> Type<'db> {
// (<https://github.com/rust-lang/rust/issues/129967>)
(Type::ProtocolInstance(protocol), nominal @ Type::NominalInstance(n))
| (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), || {
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::NominalInstance(instance), Type::SpecialForm(special_form)) => {
C::from_bool(db, !special_form.is_instance_of(db, instance.class(db)))
}
| (Type::NominalInstance(instance), Type::SpecialForm(special_form)) => C::from_bool(
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::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::NominalInstance(instance), Type::BooleanLiteral(..) | Type::TypeIs(_)) => {
// A `Type::BooleanLiteral()` must be an instance of exactly `bool`
// (it cannot be an instance of a `bool` subclass)
KnownClass::Bool
.when_subclass_of::<C>(db, instance.class(db))
.negate(db)
C::from_bool(
db,
!instance
.class_if_not_newtype(db)
.is_some_and(|class| KnownClass::Bool.is_subclass_of(db, class)),
)
}
(Type::BooleanLiteral(..) | Type::TypeIs(_), _)
@ -2313,9 +2329,12 @@ impl<'db> Type<'db> {
| (Type::NominalInstance(instance), Type::IntLiteral(..)) => {
// A `Type::IntLiteral()` must be an instance of exactly `int`
// (it cannot be an instance of an `int` subclass)
KnownClass::Int
.when_subclass_of::<C>(db, instance.class(db))
.negate(db)
C::from_bool(
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),
@ -2327,9 +2346,12 @@ impl<'db> Type<'db> {
| (Type::NominalInstance(instance), Type::StringLiteral(..) | Type::LiteralString) => {
// A `Type::StringLiteral()` or a `Type::LiteralString` must be an instance of exactly `str`
// (it cannot be an instance of a `str` subclass)
KnownClass::Str
.when_subclass_of::<C>(db, instance.class(db))
.negate(db)
C::from_bool(
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),
@ -2339,9 +2361,12 @@ impl<'db> Type<'db> {
| (Type::NominalInstance(instance), Type::BytesLiteral(..)) => {
// A `Type::BytesLiteral()` must be an instance of exactly `bytes`
// (it cannot be an instance of a `bytes` subclass)
KnownClass::Bytes
.when_subclass_of::<C>(db, instance.class(db))
.negate(db)
C::from_bool(
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(_))
@ -2373,9 +2398,12 @@ impl<'db> Type<'db> {
| (Type::NominalInstance(instance), Type::FunctionLiteral(..)) => {
// A `Type::FunctionLiteral()` must be an instance of exactly `types.FunctionType`
// (it cannot be an instance of a `types.FunctionType` subclass)
KnownClass::FunctionType
.when_subclass_of::<C>(db, instance.class(db))
.negate(db)
C::from_bool(
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
@ -2426,7 +2454,7 @@ impl<'db> Type<'db> {
| (
instance @ Type::NominalInstance(nominal),
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(
db,
Name::new_static("__call__"),
@ -2788,7 +2816,9 @@ impl<'db> Type<'db> {
// `Type::NominalInstance(type)` is equivalent to looking up the name in the
// MRO of the class `object`.
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() {
Some(Place::Unbound.into())
@ -2908,7 +2938,9 @@ impl<'db> Type<'db> {
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),
@ -3428,7 +3460,9 @@ impl<'db> Type<'db> {
Type::NominalInstance(instance)
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 segment = if name == "major" {
@ -3506,7 +3540,7 @@ impl<'db> Type<'db> {
// resolve the attribute.
if matches!(
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)
) {
return Place::Unbound.into();
@ -3826,7 +3860,7 @@ impl<'db> Type<'db> {
Type::TypeVar(_) => Truthiness::Ambiguous,
Type::NominalInstance(instance) => instance
.class(db)
.class_ignoring_newtype(db)
.known(db)
.and_then(KnownClass::bool)
.map(Ok)
@ -4407,6 +4441,21 @@ impl<'db> Type<'db> {
.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) => {
// ```py
// class object:
@ -4801,6 +4850,16 @@ impl<'db> Type<'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) => {
known_instance.instance_fallback(db).bindings(db)
}
@ -5275,9 +5334,10 @@ impl<'db> Type<'db> {
};
match self {
Type::NominalInstance(instance) => {
instance.class(db).iter_mro(db).find_map(from_class_base)
}
Type::NominalInstance(instance) => instance
.class_ignoring_newtype(db)
.iter_mro(db)
.find_map(from_class_base),
Type::ProtocolInstance(instance) => {
if let Protocol::FromClass(class) = instance.inner {
class.iter_mro(db).find_map(from_class_base)
@ -5635,6 +5695,7 @@ impl<'db> Type<'db> {
invalid_expressions: smallvec::smallvec_inline![InvalidTypeExpression::Generic],
fallback_type: Type::unknown(),
}),
KnownInstanceType::NewType(newtype) => Ok(Type::newtype_nominal_instance(*newtype)),
},
Type::SpecialForm(special_form) => match special_form {
@ -5812,32 +5873,37 @@ impl<'db> Type<'db> {
Type::Dynamic(_) => Ok(*self),
Type::NominalInstance(instance) => match instance.class(db).known(db) {
Some(KnownClass::TypeVar) => Ok(todo_type!(
"Support for `typing.TypeVar` instances in type expressions"
)),
Some(
KnownClass::ParamSpec | KnownClass::ParamSpecArgs | KnownClass::ParamSpecKwargs,
) => Ok(todo_type!("Support for `typing.ParamSpec`")),
Some(KnownClass::TypeVarTuple) => Ok(todo_type!(
"Support for `typing.TypeVarTuple` instances in type expressions"
)),
Some(KnownClass::NewType) => Ok(todo_type!(
"Support for `typing.NewType` instances in type expressions"
)),
Some(KnownClass::GenericAlias) => Ok(todo_type!(
"Support for `typing.GenericAlias` instances in type expressions"
)),
Some(KnownClass::UnionType) => Ok(todo_type!(
"Support for `types.UnionType` instances in type expressions"
)),
_ => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec::smallvec_inline![
InvalidTypeExpression::InvalidType(*self, scope_id)
],
fallback_type: Type::unknown(),
}),
},
Type::NominalInstance(instance) => {
// TODO: Should NewType wrapping be supported here?
match instance.class_ignoring_newtype(db).known(db) {
Some(KnownClass::TypeVar) => Ok(todo_type!(
"Support for `typing.TypeVar` instances in type expressions"
)),
Some(
KnownClass::ParamSpec
| KnownClass::ParamSpecArgs
| KnownClass::ParamSpecKwargs,
) => Ok(todo_type!("Support for `typing.ParamSpec`")),
Some(KnownClass::TypeVarTuple) => Ok(todo_type!(
"Support for `typing.TypeVarTuple` instances in type expressions"
)),
Some(KnownClass::NewType) => Ok(todo_type!(
"Support for `typing.NewType` instances in type expressions"
)),
Some(KnownClass::GenericAlias) => Ok(todo_type!(
"Support for `typing.GenericAlias` instances in type expressions"
)),
Some(KnownClass::UnionType) => Ok(todo_type!(
"Support for `types.UnionType` instances in type expressions"
)),
_ => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec::smallvec_inline![
InvalidTypeExpression::InvalidType(*self, scope_id)
],
fallback_type: Type::unknown(),
}),
}
}
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::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 {
KnownInstanceType::TypeVar(var) => {
@ -6375,17 +6446,18 @@ impl<'db> Type<'db> {
| Self::PropertyInstance(_)
| Self::BoundSuper(_) => self.to_meta_type(db).definition(db),
Self::NonInferableTypeVar(bound_typevar) |
Self::TypeVar(bound_typevar) => Some(TypeDefinition::TypeVar(bound_typevar.typevar(db).definition(db)?)),
Self::NonInferableTypeVar(bound_typevar) | Self::TypeVar(bound_typevar) => Some(
TypeDefinition::TypeVar(bound_typevar.typevar(db).definition(db)?),
),
Self::ProtocolInstance(protocol) => match protocol.inner {
Protocol::FromClass(class) => Some(TypeDefinition::Class(class.definition(db))),
Protocol::Synthesized(_) => None,
},
Type::TypedDict(typed_dict) => {
Some(TypeDefinition::Class(typed_dict.defining_class().definition(db)))
}
Type::TypedDict(typed_dict) => Some(TypeDefinition::Class(
typed_dict.defining_class().definition(db),
)),
Self::Union(_) | Self::Intersection(_) => None,
@ -6466,7 +6538,7 @@ impl<'db> Type<'db> {
match self {
Type::GenericAlias(generic) => Some(generic.origin(db)),
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))
} else {
None
@ -6712,6 +6784,10 @@ pub enum KnownInstanceType<'db> {
/// A single instance of `dataclasses.Field`
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>(
@ -6736,6 +6812,11 @@ fn walk_known_instance_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
KnownInstanceType::Field(field) => {
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::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::Deprecated(_) => KnownClass::Deprecated,
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)?;
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,
/// or an implicit typevar like `Self` was used.
#[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))
}
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 {
SuperOwnerKind::Dynamic(_) => None,
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 {
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);
};
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`");
}
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);

View File

@ -631,7 +631,14 @@ impl<'db> IntersectionBuilder<'db> {
self
}
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() =>
{
let mut contains_enum_literal_as_negative_element = false;
@ -657,9 +664,13 @@ impl<'db> IntersectionBuilder<'db> {
self.add_positive_impl(
Type::Union(UnionType::new(
db,
enum_member_literals(db, instance.class(db).class_literal(db).0, None)
.expect("Calling `enum_member_literals` on an enum class")
.collect::<Box<[_]>>(),
enum_member_literals(
db,
instance.class_ignoring_newtype(db).class_literal(db).0,
None,
)
.expect("Calling `enum_member_literals` on an enum class")
.collect::<Box<[_]>>(),
)),
seen_aliases,
)
@ -854,7 +865,8 @@ impl<'db> InnerIntersectionBuilder<'db> {
_ => {
let known_instance = new_positive
.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) {
// `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);
}
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 {
// `bool & AlwaysTruthy` -> `Literal[True]`
@ -968,7 +982,8 @@ impl<'db> InnerIntersectionBuilder<'db> {
self.positive
.iter()
.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)
};

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 {
match ty {
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)
|| instance.tuple_spec(db).is_some_and(|spec| match &*spec {
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.
match ty {
Type::NominalInstance(instance) => {
let class = instance.class(db);
let class = instance.class_if_not_newtype(db)?;
if class.is_known(db, KnownClass::Bool) {
return Some(vec![

View File

@ -20,7 +20,9 @@ use crate::semantic_index::{
};
use crate::types::constraints::{Constraints, IteratorConstraintsExtension};
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::function::{DataclassTransformerParams, KnownFunction};
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::{
ApplyTypeMappingVisitor, Binding, BoundSuperError, BoundSuperType, CallableType,
DataclassParams, DeprecatedInstance, HasRelationToVisitor, IsEquivalentVisitor,
KnownInstanceType, ManualPEP695TypeAliasType, MaterializationKind, NormalizedVisitor,
PropertyInstanceType, StringLiteralType, TypeAliasType, TypeMapping, TypeRelation,
TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, TypedDictParams, VarianceInferable,
declaration_type, infer_definition_types, todo_type,
KnownInstanceType, ManualPEP695TypeAliasType, MaterializationKind, NewTypeBase,
NewTypeInstance, NormalizedVisitor, PropertyInstanceType, StringLiteralType, TypeAliasType,
TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind,
TypedDictParams, VarianceInferable, declaration_type, infer_definition_types, todo_type,
};
use crate::{
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 {
self.declared_ty
.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))
}
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
fn canonical_module(self, db: &dyn Db) -> KnownModule {
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::GenericAlias(generic) => Some(Self::Class(ClassType::Generic(generic))),
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)
}
@ -164,7 +166,8 @@ impl<'db> ClassBase<'db> {
KnownInstanceType::TypeAliasType(_)
| KnownInstanceType::TypeVar(_)
| KnownInstanceType::Deprecated(_)
| KnownInstanceType::Field(_) => None,
| KnownInstanceType::Field(_)
| KnownInstanceType::NewType(_) => None,
},
Type::SpecialForm(special_form) => match special_form {

View File

@ -12,6 +12,7 @@ pub enum TypeDefinition<'db> {
Function(Definition<'db>),
TypeVar(Definition<'db>),
TypeAlias(Definition<'db>),
NewType(Definition<'db>),
}
impl TypeDefinition<'_> {
@ -21,7 +22,8 @@ impl TypeDefinition<'_> {
Self::Class(definition)
| Self::Function(definition)
| Self::TypeVar(definition)
| Self::TypeAlias(definition) => {
| Self::TypeAlias(definition)
| Self::NewType(definition) => {
let module = parsed_module(db, definition.file(db)).load(db);
Some(definition.focus_range(db, &module))
}
@ -38,7 +40,8 @@ impl TypeDefinition<'_> {
Self::Class(definition)
| Self::Function(definition)
| Self::TypeVar(definition)
| Self::TypeAlias(definition) => {
| Self::TypeAlias(definition)
| Self::NewType(definition) => {
let module = parsed_module(db, definition.file(db)).load(db);
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_LEGACY_TYPE_VARIABLE);
registry.register_lint(&INVALID_TYPE_ALIAS_TYPE);
registry.register_lint(&INVALID_NEWTYPE);
registry.register_lint(&INVALID_METACLASS);
registry.register_lint(&INVALID_OVERLOAD);
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! {
/// ## What it does
/// 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>> {
match ty {
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::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(_) => 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,
};

View File

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

View File

@ -130,26 +130,28 @@ pub(crate) fn enum_metadata<'db>(
// Some types are specifically disallowed for enum members.
return None;
}
Type::NominalInstance(instance) => match instance.class(db).known(db) {
// enum.nonmember
Some(KnownClass::Nonmember) => return None,
Type::NominalInstance(instance) => {
match instance.class_ignoring_newtype(db).known(db) {
// enum.nonmember
Some(KnownClass::Nonmember) => return None,
// enum.member
Some(KnownClass::Member) => Some(
ty.member(db, "value")
.place
.ignore_possibly_unbound()
.unwrap_or(Type::unknown()),
),
// enum.member
Some(KnownClass::Member) => Some(
ty.member(db, "value")
.place
.ignore_possibly_unbound()
.unwrap_or(Type::unknown()),
),
// enum.auto
Some(KnownClass::Auto) => {
auto_counter += 1;
Some(Type::IntLiteral(auto_counter))
// enum.auto
Some(KnownClass::Auto) => {
auto_counter += 1;
Some(Type::IntLiteral(auto_counter))
}
_ => None,
}
_ => None,
},
}
_ => None,
};
@ -208,7 +210,10 @@ pub(crate) fn enum_metadata<'db>(
PlaceAndQualifiers {
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
}
_ => {

View File

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

View File

@ -1119,9 +1119,9 @@ impl<'db> SpecializationBuilder<'db> {
// Extract formal_alias if this is a generic class
let formal_alias = match formal {
Type::NominalInstance(formal_nominal) => {
formal_nominal.class(self.db).into_generic_alias()
}
Type::NominalInstance(formal_nominal) => formal_nominal
.class_if_not_newtype(self.db)
.and_then(ClassType::into_generic_alias),
// 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
// 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 {
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 {
continue;
};

View File

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

View File

@ -1823,7 +1823,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
Type::BooleanLiteral(_) | Type::IntLiteral(_) => {}
Type::NominalInstance(instance)
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)
) => {}
_ => return false,
@ -4004,7 +4004,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
// Super instances do not allow attribute assignment
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 let Some(builder) = self.context.report_lint(&INVALID_ASSIGNMENT, target) {
@ -6274,6 +6276,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
| KnownClass::TypeVar
| KnownClass::TypeAliasType
| KnownClass::Deprecated
| KnownClass::NewType
)
) || (
// 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::NominalInstance(nominal)
if matches!(
nominal.class(self.db()).known(self.db()),
nominal.class_ignoring_newtype(self.db()).known(self.db()),
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::NominalInstance(instance))
if instance
.class(self.db())
.is_known(self.db(), KnownClass::NoneType) =>
.class_if_not_newtype(self.db())
.is_some_and(|class| class.is_known(self.db(), KnownClass::NoneType)) =>
{
SliceArg::Arg(ty)
}
@ -10426,6 +10429,16 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
self.infer_type_expression(&subscript.slice);
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(_)) => {
self.infer_type_expression(slice);
@ -11141,7 +11154,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
}
Type::NominalInstance(nominal)
if nominal
.class(self.db())
// TODO: What could NewType mean here?
.class_ignoring_newtype(self.db())
.is_known(self.db(), KnownClass::ParamSpec) =>
{
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::{
ApplyTypeMappingVisitor, ClassBase, HasRelationToVisitor, IsDisjointVisitor,
IsEquivalentVisitor, MaterializationKind, NormalizedVisitor, TypeMapping, TypeRelation,
VarianceInferable,
IsEquivalentVisitor, MaterializationKind, NewTypeInstance, NormalizedVisitor, TypeMapping,
TypeRelation, VarianceInferable,
};
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 {
let Some(tuple) = tuple else {
return Type::Never;
@ -75,7 +79,7 @@ impl<'db> Type<'db> {
/// **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.
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>> {
@ -130,56 +134,75 @@ pub(super) fn walk_nominal_instance_type<'db, V: super::visitor::TypeVisitor<'db
nominal: NominalInstanceType<'db>,
visitor: &V,
) {
visitor.visit_type(db, nominal.class(db).into());
visitor.visit_type(db, nominal.class_ignoring_newtype(db).into());
}
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 {
NominalInstanceInner::ExactTuple(tuple) => tuple.to_class_type(db),
NominalInstanceInner::NonTuple(class) => class,
NominalInstanceInner::ExactTuple(tuple) => (tuple.to_class_type(db), None),
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.
///
/// 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.
pub(super) fn tuple_spec(&self, db: &'db dyn Db) -> Option<Cow<'db, TupleSpec<'db>>> {
let from_class_type = |class: ClassType<'db>| {
// Avoid an expensive MRO traversal for common stdlib classes.
if class
.known(db)
.is_some_and(|known_class| !known_class.is_tuple_subclass())
{
return None;
}
class
.iter_mro(db)
.filter_map(ClassBase::into_class)
.find_map(|class| match class.known(db)? {
// N.B. this is a pure optimisation: iterating through the MRO would give us
// 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
// a tuple type with the correct spec for the user's configured Python version and platform.
KnownClass::VersionInfo => Some(Cow::Owned(TupleSpec::version_info_spec(db))),
KnownClass::Tuple => Some(
class
.into_generic_alias()
.and_then(|alias| {
Some(Cow::Borrowed(alias.specialization(db).tuple(db)?))
})
.unwrap_or_else(|| Cow::Owned(TupleSpec::homogeneous(Type::unknown()))),
),
_ => None,
})
};
match self.0 {
NominalInstanceInner::ExactTuple(tuple) => Some(Cow::Borrowed(tuple.tuple(db))),
NominalInstanceInner::NonTuple(class) => {
// Avoid an expensive MRO traversal for common stdlib classes.
if class
.known(db)
.is_some_and(|known_class| !known_class.is_tuple_subclass())
{
return None;
}
class
.iter_mro(db)
.filter_map(ClassBase::into_class)
.find_map(|class| match class.known(db)? {
// N.B. this is a pure optimisation: iterating through the MRO would give us
// 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
// a tuple type with the correct spec for the user's configured Python version and platform.
KnownClass::VersionInfo => {
Some(Cow::Owned(TupleSpec::version_info_spec(db)))
}
KnownClass::Tuple => Some(
class
.into_generic_alias()
.and_then(|alias| {
Some(Cow::Borrowed(alias.specialization(db).tuple(db)?))
})
.unwrap_or_else(|| {
Cow::Owned(TupleSpec::homogeneous(Type::unknown()))
}),
),
_ => None,
})
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 {
match self.0 {
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>>> {
match self.0 {
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> {
let class = match self.0 {
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 {
return None;
@ -232,7 +260,9 @@ impl<'db> NominalInstanceType<'db> {
Type::IntLiteral(n) => i32::try_from(*n).map(Some).ok(),
Type::BooleanLiteral(b) => Some(Some(i32::from(*b))),
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)
}
@ -254,7 +284,10 @@ impl<'db> NominalInstanceType<'db> {
NominalInstanceInner::ExactTuple(tuple) => {
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))
}
}
@ -269,7 +302,10 @@ impl<'db> NominalInstanceType<'db> {
NominalInstanceInner::ExactTuple(tuple) => {
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))
}
}
@ -283,13 +319,25 @@ impl<'db> NominalInstanceType<'db> {
visitor: &HasRelationToVisitor<'db, C>,
) -> C {
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(tuple2),
) => tuple1.has_relation_to_impl(db, tuple2, relation, visitor),
_ => self
.class(db)
.has_relation_to_impl(db, other.class(db), relation, visitor),
_ => self.class_ignoring_newtype(db).has_relation_to_impl(
db,
other.class_ignoring_newtype(db),
relation,
visitor,
),
}
}
@ -304,7 +352,7 @@ impl<'db> NominalInstanceType<'db> {
NominalInstanceInner::ExactTuple(tuple1),
NominalInstanceInner::ExactTuple(tuple2),
) => 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)
}
_ => C::unsatisfiable(db),
@ -327,10 +375,14 @@ impl<'db> NominalInstanceType<'db> {
}
}
result.or(db, || {
C::from_bool(
db,
!(self.class(db)).could_coexist_in_mro_with(db, other.class(db)),
)
let (self_class, self_newtype) = self.class_and_newtype(db);
let (other_class, other_newtype) = other.class_and_newtype(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:
// https://docs.python.org/3/reference/expressions.html#parenthesized-forms
NominalInstanceInner::ExactTuple(_) => false,
NominalInstanceInner::NonTuple(class) => class
NominalInstanceInner::NewType(_) => false,
NominalInstanceInner::Class(class) => class
.known(db)
.map(KnownClass::is_singleton)
.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 {
match self.0 {
NominalInstanceInner::ExactTuple(tuple) => tuple.is_single_valued(db),
NominalInstanceInner::NonTuple(class) => class
let class_type_is_single_valued = |class: ClassType<'db>| {
class
.known(db)
.and_then(KnownClass::is_single_valued)
.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> {
SubclassOfType::from(db, self.class(db))
SubclassOfType::from(db, self.class_ignoring_newtype(db))
}
pub(super) fn apply_type_mapping_impl<'a>(
@ -374,7 +433,10 @@ impl<'db> NominalInstanceType<'db> {
NominalInstanceInner::ExactTuple(tuple) => {
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))
}
}
@ -390,7 +452,12 @@ impl<'db> NominalInstanceType<'db> {
NominalInstanceInner::ExactTuple(tuple) => {
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);
}
}
@ -413,12 +480,19 @@ enum NominalInstanceInner<'db> {
/// Note that the type `tuple[int, str]` includes subtypes of `tuple[int, str]`,
/// but those subtypes would be represented using the `NonTuple` variant.
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
/// builtin `tuple` class.
///
/// This variant includes types that are subtypes of "exact tuple" types,
/// because they represent "all instances of a class that is a tuple subclass".
NonTuple(ClassType<'db>),
Class(ClassType<'db>),
}
pub(crate) struct SliceLiteral {
@ -429,7 +503,7 @@ pub(crate) struct SliceLiteral {
impl<'db> VarianceInferable<'db> for NominalInstanceType<'db> {
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]`.
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(
db,
@ -565,13 +567,22 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
}
// Treat enums as a union of their members.
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(
db,
enum_member_literals(db, instance.class(db).class_literal(db).0, None)
.expect("Calling `enum_member_literals` on an enum class")
.map(|ty| filter_to_cannot_be_equal(db, ty, rhs_ty)),
enum_member_literals(
db,
instance.class_ignoring_newtype(db).class_literal(db).0,
None,
)
.expect("Calling `enum_member_literals` on an enum class")
.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>> {
match (lhs_ty, rhs_ty) {
(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 {
Some(Type::BooleanLiteral(false).negate(self.db))

View File

@ -153,7 +153,7 @@ impl Ty {
.place
.expect_type();
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
}

View File

@ -131,7 +131,15 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
(_, Type::TypeIs(_)) => Ordering::Greater,
(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::Greater,
@ -180,7 +188,17 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
(SuperOwnerKind::Class(_), _) => Ordering::Less,
(_, SuperOwnerKind::Class(_)) => Ordering::Greater,
(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::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": {
"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)",