[ty] implement typing.NewType by adding Type::NewTypeInstance

This commit is contained in:
Jack O'Connor 2025-10-23 10:10:10 -07:00
parent 039a69fa8c
commit 5f3e086ee4
25 changed files with 1343 additions and 191 deletions

164
crates/ty/docs/rules.md generated
View file

@ -39,7 +39,7 @@ def test(): -> "int":
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-non-callable" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L118" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L120" target="_blank">View source</a>
</small>
@ -63,7 +63,7 @@ Calling a non-callable object will raise a `TypeError` at runtime.
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-argument-forms" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L162" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L164" target="_blank">View source</a>
</small>
@ -95,7 +95,7 @@ f(int) # error
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-declarations" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L188" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L190" target="_blank">View source</a>
</small>
@ -126,7 +126,7 @@ a = 1
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20conflicting-metaclass" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L213" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L215" target="_blank">View source</a>
</small>
@ -158,7 +158,7 @@ class C(A, B): ...
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20cyclic-class-definition" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L239" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L241" target="_blank">View source</a>
</small>
@ -190,7 +190,7 @@ class B(A): ...
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-base" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L304" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L306" target="_blank">View source</a>
</small>
@ -217,7 +217,7 @@ class B(A, A): ...
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.12">0.0.1-alpha.12</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20duplicate-kw-only" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L325" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L327" target="_blank">View source</a>
</small>
@ -329,7 +329,7 @@ def test(): -> "Literal[5]":
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20inconsistent-mro" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L529" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L531" target="_blank">View source</a>
</small>
@ -359,7 +359,7 @@ class C(A, B): ...
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20index-out-of-bounds" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L553" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L555" target="_blank">View source</a>
</small>
@ -385,7 +385,7 @@ t[3] # IndexError: tuple index out of range
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.12">0.0.1-alpha.12</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20instance-layout-conflict" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L357" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L359" target="_blank">View source</a>
</small>
@ -474,7 +474,7 @@ an atypical memory layout.
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-argument-type" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L607" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L609" target="_blank">View source</a>
</small>
@ -501,7 +501,7 @@ func("foo") # error: [invalid-argument-type]
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-assignment" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L647" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L649" target="_blank">View source</a>
</small>
@ -529,7 +529,7 @@ a: int = ''
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-attribute-access" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1782" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1808" target="_blank">View source</a>
</small>
@ -563,7 +563,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.19">0.0.1-alpha.19</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-await" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L669" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L671" target="_blank">View source</a>
</small>
@ -599,7 +599,7 @@ asyncio.run(main())
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-base" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L699" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L701" target="_blank">View source</a>
</small>
@ -623,7 +623,7 @@ class A(42): ... # error: [invalid-base]
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-context-manager" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L750" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L752" target="_blank">View source</a>
</small>
@ -650,7 +650,7 @@ with 1:
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-declaration" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L771" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L773" target="_blank">View source</a>
</small>
@ -679,7 +679,7 @@ a: str
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-exception-caught" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L794" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L796" target="_blank">View source</a>
</small>
@ -723,7 +723,7 @@ except ZeroDivisionError:
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-class" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L830" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L832" target="_blank">View source</a>
</small>
@ -756,7 +756,7 @@ class C[U](Generic[T]): ...
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.17">0.0.1-alpha.17</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-key" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L574" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L576" target="_blank">View source</a>
</small>
@ -795,7 +795,7 @@ carol = Person(name="Carol", age=25) # typo!
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-legacy-type-variable" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L856" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L858" target="_blank">View source</a>
</small>
@ -830,7 +830,7 @@ def f(t: TypeVar("U")): ...
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-metaclass" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L929" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L955" target="_blank">View source</a>
</small>
@ -864,7 +864,7 @@ class B(metaclass=f): ...
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.19">0.0.1-alpha.19</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-named-tuple" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L503" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L505" target="_blank">View source</a>
</small>
@ -890,13 +890,43 @@ in a class's bases list.
TypeError: can only inherit from a NamedTuple type and Generic
```
## `invalid-newtype`
<small>
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/1.0.0">1.0.0</a>) ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-newtype" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L931" target="_blank">View source</a>
</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
def get_name() -> str: ...
Foo = NewType("Foo", int) # okay
Bar = NewType(get_name(), int) # error: The first argument to `NewType` must be a string literal
Baz = NewType("Baz", int | str) # error: invalid base for `typing.NewType`
```
## `invalid-overload`
<small>
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-overload" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L956" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L982" target="_blank">View source</a>
</small>
@ -946,7 +976,7 @@ def foo(x: int) -> int: ...
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-parameter-default" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1055" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1081" target="_blank">View source</a>
</small>
@ -972,7 +1002,7 @@ def f(a: int = ''): ...
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-paramspec" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L884" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L886" target="_blank">View source</a>
</small>
@ -1003,7 +1033,7 @@ P2 = ParamSpec("S2") # error: ParamSpec name must match the variable it's assig
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-protocol" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L439" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L441" target="_blank">View source</a>
</small>
@ -1037,7 +1067,7 @@ TypeError: Protocols can only inherit from other protocols, got <class 'int'>
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-raise" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1075" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1101" target="_blank">View source</a>
</small>
@ -1086,7 +1116,7 @@ def g():
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-return-type" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L628" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L630" target="_blank">View source</a>
</small>
@ -1111,7 +1141,7 @@ def func() -> int:
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-super-argument" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1118" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1144" target="_blank">View source</a>
</small>
@ -1169,7 +1199,7 @@ TODO #14889
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.6">0.0.1-alpha.6</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-alias-type" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L908" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L910" target="_blank">View source</a>
</small>
@ -1196,7 +1226,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-checking-constant" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1157" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1183" target="_blank">View source</a>
</small>
@ -1226,7 +1256,7 @@ TYPE_CHECKING = ''
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-form" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1181" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1207" target="_blank">View source</a>
</small>
@ -1256,7 +1286,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.11">0.0.1-alpha.11</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-call" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1233" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1259" target="_blank">View source</a>
</small>
@ -1290,7 +1320,7 @@ f(10) # Error
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.11">0.0.1-alpha.11</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-guard-definition" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1205" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1231" target="_blank">View source</a>
</small>
@ -1324,7 +1354,7 @@ class C:
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-variable-constraints" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1261" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1287" target="_blank">View source</a>
</small>
@ -1359,7 +1389,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-argument" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1290" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1316" target="_blank">View source</a>
</small>
@ -1384,7 +1414,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x'
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.20">0.0.1-alpha.20</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20missing-typed-dict-key" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1883" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1909" target="_blank">View source</a>
</small>
@ -1417,7 +1447,7 @@ alice["age"] # KeyError
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20no-matching-overload" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1309" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1335" target="_blank">View source</a>
</small>
@ -1446,7 +1476,7 @@ func("string") # error: [no-matching-overload]
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20non-subscriptable" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1332" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1358" target="_blank">View source</a>
</small>
@ -1470,7 +1500,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20not-iterable" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1350" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1376" target="_blank">View source</a>
</small>
@ -1496,7 +1526,7 @@ for i in 34: # TypeError: 'int' object is not iterable
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20parameter-already-assigned" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1401" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1427" target="_blank">View source</a>
</small>
@ -1523,7 +1553,7 @@ f(1, x=2) # Error raised here
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20positional-only-parameter-as-kwarg" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1636" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1662" target="_blank">View source</a>
</small>
@ -1581,7 +1611,7 @@ def test(): -> "int":
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20static-assert-error" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1758" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1784" target="_blank">View source</a>
</small>
@ -1611,7 +1641,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20subclass-of-final-class" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1492" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1518" target="_blank">View source</a>
</small>
@ -1640,7 +1670,7 @@ class B(A): ... # Error raised here
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20too-many-positional-arguments" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1537" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1563" target="_blank">View source</a>
</small>
@ -1667,7 +1697,7 @@ f("foo") # Error raised here
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20type-assertion-failure" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1515" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1541" target="_blank">View source</a>
</small>
@ -1695,7 +1725,7 @@ def _(x: int):
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unavailable-implicit-super-arguments" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1558" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1584" target="_blank">View source</a>
</small>
@ -1741,7 +1771,7 @@ class A:
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unknown-argument" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1615" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1641" target="_blank">View source</a>
</small>
@ -1768,7 +1798,7 @@ f(x=1, y=2) # Error raised here
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-attribute" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1657" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1683" target="_blank">View source</a>
</small>
@ -1796,7 +1826,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo'
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-import" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1679" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1705" target="_blank">View source</a>
</small>
@ -1821,7 +1851,7 @@ import foo # ModuleNotFoundError: No module named 'foo'
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-reference" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1698" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1724" target="_blank">View source</a>
</small>
@ -1846,7 +1876,7 @@ print(x) # NameError: name 'x' is not defined
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-bool-conversion" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1370" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1396" target="_blank">View source</a>
</small>
@ -1883,7 +1913,7 @@ b1 < b2 < b1 # exception raised here
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-operator" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1717" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1743" target="_blank">View source</a>
</small>
@ -1911,7 +1941,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A'
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'error'."><code>error</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20zero-stepsize-in-slice" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1739" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1765" target="_blank">View source</a>
</small>
@ -1936,7 +1966,7 @@ l[1:10:0] # ValueError: slice step cannot be zero
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.20">0.0.1-alpha.20</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20ambiguous-protocol-member" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L468" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L470" target="_blank">View source</a>
</small>
@ -1977,7 +2007,7 @@ class SubProto(BaseProto, Protocol):
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.16">0.0.1-alpha.16</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20deprecated" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L283" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L285" target="_blank">View source</a>
</small>
@ -2065,7 +2095,7 @@ a = 20 / 0 # type: ignore
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-attribute" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1422" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1448" target="_blank">View source</a>
</small>
@ -2093,7 +2123,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c'
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-implicit-call" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L136" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L138" target="_blank">View source</a>
</small>
@ -2125,7 +2155,7 @@ A()[0] # TypeError: 'A' object is not subscriptable
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-missing-import" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1444" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1470" target="_blank">View source</a>
</small>
@ -2157,7 +2187,7 @@ from module import a # ImportError: cannot import name 'a' from 'module'
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20redundant-cast" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1810" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1836" target="_blank">View source</a>
</small>
@ -2184,7 +2214,7 @@ cast(int, f()) # Redundant
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20undefined-reveal" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1597" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1623" target="_blank">View source</a>
</small>
@ -2208,7 +2238,7 @@ reveal_type(1) # NameError: name 'reveal_type' is not defined
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.15">0.0.1-alpha.15</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unresolved-global" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1831" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1857" target="_blank">View source</a>
</small>
@ -2266,7 +2296,7 @@ def g():
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.7">0.0.1-alpha.7</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-base" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L717" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L719" target="_blank">View source</a>
</small>
@ -2305,7 +2335,7 @@ class D(C): ... # error: [unsupported-base]
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'warn'."><code>warn</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.22">0.0.1-alpha.22</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20useless-overload-body" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L999" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1025" target="_blank">View source</a>
</small>
@ -2368,7 +2398,7 @@ def foo(x: int | str) -> int | str:
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'ignore'."><code>ignore</code></a> ·
Preview (since <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a>) ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20division-by-zero" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L265" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L267" target="_blank">View source</a>
</small>
@ -2392,7 +2422,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime.
Default level: <a href="../rules.md#rule-levels" title="This lint has a default level of 'ignore'."><code>ignore</code></a> ·
Added in <a href="https://github.com/astral-sh/ty/releases/tag/0.0.1-alpha.1">0.0.1-alpha.1</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20possibly-unresolved-reference" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1470" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1496" target="_blank">View source</a>
</small>

View file

@ -127,7 +127,8 @@ impl<'db> Completion<'db> {
Type::NominalInstance(_)
| Type::PropertyInstance(_)
| Type::BoundSuper(_)
| Type::TypedDict(_) => CompletionKind::Struct,
| Type::TypedDict(_)
| Type::NewTypeInstance(_) => CompletionKind::Struct,
Type::IntLiteral(_)
| Type::BooleanLiteral(_)
| Type::TypeIs(_)

View file

@ -209,16 +209,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,389 @@ 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 `<NewType pseudo-class '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
The basic purpose of `NewType` is that it acts like a subtype of its base, but not the exact same
type (i.e. not an alias).
```py
from typing_extensions import NewType
from ty_extensions import static_assert, is_subtype_of, is_equivalent_to
Foo = NewType("Foo", int)
Bar = NewType("Bar", Foo)
static_assert(is_subtype_of(Foo, int))
static_assert(not is_equivalent_to(Foo, int))
static_assert(is_subtype_of(Bar, Foo))
static_assert(is_subtype_of(Bar, int))
static_assert(not is_equivalent_to(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("forty-two") # error: [invalid-argument-type] "Argument is incorrect: Expected `int`, found `Literal["forty-two"]`"
def f(_: int): ...
def g(_: Foo): ...
def h(_: Bar): ...
f(42)
f(Foo(42))
f(Bar(Foo(42)))
g(42) # error: [invalid-argument-type] "Argument to function `g` is incorrect: Expected `Foo`, found `Literal[42]`"
g(Foo(42))
g(Bar(Foo(42)))
h(42) # error: [invalid-argument-type] "Argument to function `h` is incorrect: Expected `Bar`, found `Literal[42]`"
h(Foo(42)) # error: [invalid-argument-type] "Argument to function `h` is incorrect: Expected `Bar`, found `Foo`"
h(Bar(Foo(42)))
```
## Member and method lookup work
```py
from typing_extensions import NewType
class Foo:
foo_member: str = "hello"
def foo_method(self) -> int:
return 42
Bar = NewType("Bar", Foo)
Baz = NewType("Baz", Bar)
baz = Baz(Bar(Foo()))
reveal_type(baz.foo_member) # revealed: str
reveal_type(baz.foo_method()) # revealed: int
```
We also infer member access on the `NewType` pseudo-type itself correctly:
```py
reveal_type(Bar.__supertype__) # revealed: type | NewType
reveal_type(Baz.__supertype__) # revealed: type | NewType
```
## `NewType` wrapper functions are `Callable`
```py
from collections.abc import Callable
from typing_extensions import NewType
from ty_extensions import CallableTypeOf
Foo = NewType("Foo", int)
def _(obj: CallableTypeOf[Foo]):
reveal_type(obj) # revealed: (int, /) -> Foo
def f(_: Callable[[int], Foo]): ...
f(Foo)
map(Foo, [1, 2, 3])
def g(_: Callable[[str], Foo]): ...
g(Foo) # error: [invalid-argument-type]
```
## `NewType` instances are `Callable` if the base type is
```py
from typing import NewType, Callable, Any
from ty_extensions import CallableTypeOf
N = NewType("N", int)
i = N(42)
y: Callable[..., Any] = i # error: [invalid-assignment] "Object of type `N` is not assignable to `(...) -> Any`"
# error: [invalid-type-form] "Expected the first argument to `ty_extensions.CallableTypeOf` to be a callable object, but got an object of type `N`"
def f(x: CallableTypeOf[i]):
reveal_type(x) # revealed: Unknown
class SomethingCallable:
def __call__(self, a: str) -> bytes:
raise NotImplementedError
N2 = NewType("N2", SomethingCallable)
j = N2(SomethingCallable())
z: Callable[[str], bytes] = j # fine
def g(x: CallableTypeOf[j]):
reveal_type(x) # revealed: (a: str) -> bytes
```
## The name must be a string literal
```py
from typing_extensions import NewType
def _(name: str) -> None:
_ = NewType(name, int) # error: [invalid-newtype] "The first argument to `NewType` must be a string literal"
```
However, the literal doesn't necessarily need to be inline, as long as we infer it:
```py
name = "Foo"
Foo = NewType(name, int)
reveal_type(Foo) # revealed: <NewType pseudo-class 'Foo'>
```
## The second argument must be a class type or another newtype
Other typing constructs like `Union` are not allowed.
```py
from typing_extensions import NewType
# error: [invalid-newtype] "invalid base for `typing.NewType`"
Foo = NewType("Foo", int | str)
```
We don't emit the "invalid base" diagnostic for `Unknown`, because that typically results from other
errors that already have a diagnostic, and there's no need to pile on. For example, this mistake
gives you an "Int literals are not allowed" error, and we'd rather not see an "invalid base" error
on top of that:
```py
# error: [invalid-type-form] "Int literals are not allowed in this context in a type expression"
Foo = NewType("Foo", 42)
```
## A `NewType` definition must be a simple variable assignment
```py
from typing import NewType
N: NewType = NewType("N", int) # error: [invalid-newtype] "A `NewType` definition must be a simple variable assignment"
```
## Newtypes can be cyclic in various ways
Cyclic newtypes are kind of silly, but it's possible for the user to express them, and it's
important that we don't go into infinite recursive loops and crash with a stack overflow. In fact,
this is *why* base type evaluation is deferred; otherwise Salsa itself would crash.
```py
from typing_extensions import NewType, reveal_type, cast
# Define a directly cyclic newtype.
A = NewType("A", "A")
reveal_type(A) # revealed: <NewType pseudo-class 'A'>
# Typechecking still works. We can't construct an `A` "honestly", but we can `cast` into one.
a: A
a = 42 # error: [invalid-assignment] "Object of type `Literal[42]` is not assignable to `A`"
a = A(42) # error: [invalid-argument-type] "Argument is incorrect: Expected `A`, found `Literal[42]`"
a = cast(A, 42)
reveal_type(a) # revealed: A
# A newtype cycle might involve more than one step.
B = NewType("B", "C")
C = NewType("C", "B")
reveal_type(B) # revealed: <NewType pseudo-class 'B'>
reveal_type(C) # revealed: <NewType pseudo-class 'C'>
b: B = cast(B, 42)
c: C = C(b)
reveal_type(b) # revealed: B
reveal_type(c) # revealed: C
# Cyclic types behave in surprising ways. These assignments are legal, even though B and C aren't
# the same type, because each of them is a subtype of the other.
b = c
c = b
# Another newtype could inherit from a cyclic one.
D = NewType("D", C)
reveal_type(D) # revealed: <NewType pseudo-class 'D'>
d: D
d = D(42) # error: [invalid-argument-type] "Argument is incorrect: Expected `C`, found `Literal[42]`"
d = D(c)
d = D(b) # Allowed, the same surprise as above. B and C are subtypes of each other.
reveal_type(d) # revealed: D
```
Normal classes can't inherit from newtypes, but generic classes can be parametrized with them, so we
also need to detect "ordinary" type cycles that happen to involve a newtype.
```py
E = NewType("E", list["E"])
reveal_type(E) # revealed: <NewType pseudo-class 'E'>
e: E = E([])
reveal_type(e) # revealed: E
reveal_type(E(E(E(E(E([])))))) # revealed: E
reveal_type(E([E([E([]), E([E([])])]), E([])])) # revealed: E
E(["foo"]) # error: [invalid-argument-type]
E(E(E(["foo"]))) # error: [invalid-argument-type]
```
## `NewType` wrapping preserves singleton-ness and single-valued-ness
```py
from typing_extensions import NewType
from ty_extensions import is_singleton, is_single_valued, static_assert
from types import EllipsisType
A = NewType("A", EllipsisType)
static_assert(is_singleton(A))
static_assert(is_single_valued(A))
reveal_type(type(A(...)) is EllipsisType) # revealed: Literal[True]
# TODO: This should be `Literal[True]` also.
reveal_type(A(...) is ...) # revealed: bool
B = NewType("B", int)
static_assert(not is_singleton(B))
static_assert(not is_single_valued(B))
```
## `NewType`s of tuples can be iterated/unpacked
```py
from typing import NewType
N = NewType("N", tuple[int, str])
a, b = N((1, "foo"))
reveal_type(a) # revealed: int
reveal_type(b) # revealed: str
```
## `isinstance` of a `NewType` instance and its base class is inferred as `Literal[True]`
```py
from typing import NewType
N = NewType("N", int)
def f(x: N):
reveal_type(isinstance(x, int)) # revealed: Literal[True]
```
However, a `NewType` isn't a real class, so it isn't a valid second argument to `isinstance`:
```py
def f(x: N):
# error: [invalid-argument-type] "Argument to function `isinstance` is incorrect"
reveal_type(isinstance(x, N)) # revealed: bool
```
Because of that, we don't generate any narrowing constraints for it:
```py
def f(x: N | str):
if isinstance(x, N): # error: [invalid-argument-type]
reveal_type(x) # revealed: N | str
else:
reveal_type(x) # revealed: N | str
```
## Trying to subclass a `NewType` produces an error matching CPython
<!-- snapshot-diagnostics -->
```py
from typing import NewType
X = NewType("X", int)
class Foo(X): ... # error: [invalid-base]
```
## Don't narrow `NewType`-wrapped `Enum`s inside of match arms
`Literal[Foo.X]` is actually disjoint from `N` here:
```py
from enum import Enum
from typing import NewType
class Foo(Enum):
X = 0
Y = 1
N = NewType("N", Foo)
def f(x: N):
match x:
case Foo.X:
reveal_type(x) # revealed: N
case Foo.Y:
reveal_type(x) # revealed: N
case _:
reveal_type(x) # revealed: N
```
## We don't support `NewType` on Python 3.9
We implement `typing.NewType` as a `KnownClass`, but in Python 3.9 it's actually a function, so all
we get is the `Any` annotations from typeshed. However, `typing_extensions.NewType` is always a
class. This could be improved in the future, but Python 3.9 is now end-of-life, so it's not
high-priority.
```toml
[environment]
python-version = "3.9"
```
```py
from typing import NewType
Foo = NewType("Foo", int)
reveal_type(Foo) # revealed: Any
reveal_type(Foo(42)) # revealed: Any
from typing_extensions import NewType
Bar = NewType("Bar", int)
reveal_type(Bar) # revealed: <NewType pseudo-class 'Bar'>
reveal_type(Bar(42)) # revealed: Bar
```
## The base of a `NewType` can't be a protocol class or a `TypedDict`
<!-- snapshot-diagnostics -->
```py
from typing import NewType, Protocol, TypedDict
class Id(Protocol):
code: int
UserId = NewType("UserId", Id) # error: [invalid-newtype]
class Foo(TypedDict):
a: int
Bar = NewType("Bar", Foo) # error: [invalid-newtype]
```
## TODO: A `NewType` cannot be generic
```py
from typing import Any, NewType, TypeVar
# All of these are allowed.
A = NewType("A", list)
B = NewType("B", list[int])
B = NewType("B", list[Any])
# But a free typevar is not allowed.
T = TypeVar("T")
C = NewType("C", list[T]) # TODO: should be "error: [invalid-newtype]"
```

View file

@ -66,7 +66,7 @@ synthesized `Protocol`s that cannot be upcast to, or interpreted as, a non-`obje
```py
import types
from typing_extensions import Callable, TypeIs, Literal, TypedDict
from typing_extensions import Callable, TypeIs, Literal, NewType, TypedDict
def f(): ...
@ -81,6 +81,8 @@ class SomeTypedDict(TypedDict):
x: int
y: bytes
N = NewType("N", int)
# revealed: <super: <class 'object'>, FunctionType>
reveal_type(super(object, f))
# revealed: <super: <class 'object'>, WrapperDescriptorType>
@ -95,6 +97,8 @@ reveal_type(super(object, Alias))
reveal_type(super(object, Foo().method))
# revealed: <super: <class 'object'>, property>
reveal_type(super(object, Foo.some_property))
# revealed: <super: <class 'object'>, int>
reveal_type(super(object, N(42)))
def g(x: object) -> TypeIs[list[object]]:
return isinstance(x, list)

View file

@ -0,0 +1,58 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: new_types.md - NewType - The base of a `NewType` can't be a protocol class or a `TypedDict`
mdtest path: crates/ty_python_semantic/resources/mdtest/annotations/new_types.md
---
# Python source files
## mdtest_snippet.py
```
1 | from typing import NewType, Protocol, TypedDict
2 |
3 | class Id(Protocol):
4 | code: int
5 |
6 | UserId = NewType("UserId", Id) # error: [invalid-newtype]
7 |
8 | class Foo(TypedDict):
9 | a: int
10 |
11 | Bar = NewType("Bar", Foo) # error: [invalid-newtype]
```
# Diagnostics
```
error[invalid-newtype]: invalid base for `typing.NewType`
--> src/mdtest_snippet.py:6:28
|
4 | code: int
5 |
6 | UserId = NewType("UserId", Id) # error: [invalid-newtype]
| ^^ type `Id`
7 |
8 | class Foo(TypedDict):
|
info: The base of a `NewType` is not allowed to be a protocol class.
info: rule `invalid-newtype` is enabled by default
```
```
error[invalid-newtype]: invalid base for `typing.NewType`
--> src/mdtest_snippet.py:11:22
|
9 | a: int
10 |
11 | Bar = NewType("Bar", Foo) # error: [invalid-newtype]
| ^^^ type `Foo`
|
info: The base of a `NewType` is not allowed to be a `TypedDict`.
info: rule `invalid-newtype` is enabled by default
```

View file

@ -0,0 +1,37 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: new_types.md - NewType - Trying to subclass a `NewType` produces an error matching CPython
mdtest path: crates/ty_python_semantic/resources/mdtest/annotations/new_types.md
---
# Python source files
## mdtest_snippet.py
```
1 | from typing import NewType
2 |
3 | X = NewType("X", int)
4 |
5 | class Foo(X): ... # error: [invalid-base]
```
# Diagnostics
```
error[invalid-base]: Cannot subclass an instance of NewType
--> src/mdtest_snippet.py:5:11
|
3 | X = NewType("X", int)
4 |
5 | class Foo(X): ... # error: [invalid-base]
| ^
|
info: Perhaps you were looking for: `Foo = NewType('Foo', X)`
info: Definition of class `Foo` will raise `TypeError` at runtime
info: rule `invalid-base` is enabled by default
```

View file

@ -46,7 +46,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/class/super.md
32 | reveal_type(super(C, C()).aa) # revealed: int
33 | reveal_type(super(C, C()).bb) # revealed: int
34 | import types
35 | from typing_extensions import Callable, TypeIs, Literal, TypedDict
35 | from typing_extensions import Callable, TypeIs, Literal, NewType, TypedDict
36 |
37 | def f(): ...
38 |
@ -61,59 +61,63 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/class/super.md
47 | x: int
48 | y: bytes
49 |
50 | # revealed: <super: <class 'object'>, FunctionType>
51 | reveal_type(super(object, f))
52 | # revealed: <super: <class 'object'>, WrapperDescriptorType>
53 | reveal_type(super(object, types.FunctionType.__get__))
54 | # revealed: <super: <class 'object'>, GenericAlias>
55 | reveal_type(super(object, Foo[int]))
56 | # revealed: <super: <class 'object'>, _SpecialForm>
57 | reveal_type(super(object, Literal))
58 | # revealed: <super: <class 'object'>, TypeAliasType>
59 | reveal_type(super(object, Alias))
60 | # revealed: <super: <class 'object'>, MethodType>
61 | reveal_type(super(object, Foo().method))
62 | # revealed: <super: <class 'object'>, property>
63 | reveal_type(super(object, Foo.some_property))
64 |
65 | def g(x: object) -> TypeIs[list[object]]:
66 | return isinstance(x, list)
67 |
68 | def _(x: object, y: SomeTypedDict, z: Callable[[int, str], bool]):
69 | if hasattr(x, "bar"):
70 | # revealed: <Protocol with members 'bar'>
71 | reveal_type(x)
72 | # error: [invalid-super-argument]
73 | # revealed: Unknown
74 | reveal_type(super(object, x))
75 |
76 | # error: [invalid-super-argument]
77 | # revealed: Unknown
78 | reveal_type(super(object, z))
50 | N = NewType("N", int)
51 |
52 | # revealed: <super: <class 'object'>, FunctionType>
53 | reveal_type(super(object, f))
54 | # revealed: <super: <class 'object'>, WrapperDescriptorType>
55 | reveal_type(super(object, types.FunctionType.__get__))
56 | # revealed: <super: <class 'object'>, GenericAlias>
57 | reveal_type(super(object, Foo[int]))
58 | # revealed: <super: <class 'object'>, _SpecialForm>
59 | reveal_type(super(object, Literal))
60 | # revealed: <super: <class 'object'>, TypeAliasType>
61 | reveal_type(super(object, Alias))
62 | # revealed: <super: <class 'object'>, MethodType>
63 | reveal_type(super(object, Foo().method))
64 | # revealed: <super: <class 'object'>, property>
65 | reveal_type(super(object, Foo.some_property))
66 | # revealed: <super: <class 'object'>, int>
67 | reveal_type(super(object, N(42)))
68 |
69 | def g(x: object) -> TypeIs[list[object]]:
70 | return isinstance(x, list)
71 |
72 | def _(x: object, y: SomeTypedDict, z: Callable[[int, str], bool]):
73 | if hasattr(x, "bar"):
74 | # revealed: <Protocol with members 'bar'>
75 | reveal_type(x)
76 | # error: [invalid-super-argument]
77 | # revealed: Unknown
78 | reveal_type(super(object, x))
79 |
80 | is_list = g(x)
81 | # revealed: TypeIs[list[object] @ x]
82 | reveal_type(is_list)
83 | # revealed: <super: <class 'object'>, bool>
84 | reveal_type(super(object, is_list))
85 |
86 | # revealed: <super: <class 'object'>, dict[Literal["x", "y"], int | bytes]>
87 | reveal_type(super(object, y))
88 |
89 | # The first argument to `super()` must be an actual class object;
90 | # instances of `GenericAlias` are not accepted at runtime:
91 | #
92 | # error: [invalid-super-argument]
93 | # revealed: Unknown
94 | reveal_type(super(list[int], []))
95 | class Super:
96 | def method(self) -> int:
97 | return 42
98 |
99 | class Sub(Super):
100 | def method(self: Sub) -> int:
101 | # revealed: <super: <class 'Sub'>, Sub>
102 | return reveal_type(super(self.__class__, self)).method()
80 | # error: [invalid-super-argument]
81 | # revealed: Unknown
82 | reveal_type(super(object, z))
83 |
84 | is_list = g(x)
85 | # revealed: TypeIs[list[object] @ x]
86 | reveal_type(is_list)
87 | # revealed: <super: <class 'object'>, bool>
88 | reveal_type(super(object, is_list))
89 |
90 | # revealed: <super: <class 'object'>, dict[Literal["x", "y"], int | bytes]>
91 | reveal_type(super(object, y))
92 |
93 | # The first argument to `super()` must be an actual class object;
94 | # instances of `GenericAlias` are not accepted at runtime:
95 | #
96 | # error: [invalid-super-argument]
97 | # revealed: Unknown
98 | reveal_type(super(list[int], []))
99 | class Super:
100 | def method(self) -> int:
101 | return 42
102 |
103 | class Sub(Super):
104 | def method(self: Sub) -> int:
105 | # revealed: <super: <class 'Sub'>, Sub>
106 | return reveal_type(super(self.__class__, self)).method()
```
# Diagnostics
@ -206,14 +210,14 @@ info: rule `unresolved-attribute` is enabled by default
```
error[invalid-super-argument]: `<Protocol with members 'bar'>` is an abstract/structural type in `super(<class 'object'>, <Protocol with members 'bar'>)` call
--> src/mdtest_snippet.py:74:21
--> src/mdtest_snippet.py:78:21
|
72 | # error: [invalid-super-argument]
73 | # revealed: Unknown
74 | reveal_type(super(object, x))
76 | # error: [invalid-super-argument]
77 | # revealed: Unknown
78 | reveal_type(super(object, x))
| ^^^^^^^^^^^^^^^^
75 |
76 | # error: [invalid-super-argument]
79 |
80 | # error: [invalid-super-argument]
|
info: rule `invalid-super-argument` is enabled by default
@ -221,14 +225,14 @@ info: rule `invalid-super-argument` is enabled by default
```
error[invalid-super-argument]: `(int, str, /) -> bool` is an abstract/structural type in `super(<class 'object'>, (int, str, /) -> bool)` call
--> src/mdtest_snippet.py:78:17
--> src/mdtest_snippet.py:82:17
|
76 | # error: [invalid-super-argument]
77 | # revealed: Unknown
78 | reveal_type(super(object, z))
80 | # error: [invalid-super-argument]
81 | # revealed: Unknown
82 | reveal_type(super(object, z))
| ^^^^^^^^^^^^^^^^
79 |
80 | is_list = g(x)
83 |
84 | is_list = g(x)
|
info: rule `invalid-super-argument` is enabled by default
@ -236,15 +240,15 @@ info: rule `invalid-super-argument` is enabled by default
```
error[invalid-super-argument]: `types.GenericAlias` instance `list[int]` is not a valid class
--> src/mdtest_snippet.py:94:13
|
92 | # error: [invalid-super-argument]
93 | # revealed: Unknown
94 | reveal_type(super(list[int], []))
| ^^^^^^^^^^^^^^^^^^^^
95 | class Super:
96 | def method(self) -> int:
|
--> src/mdtest_snippet.py:98:13
|
96 | # error: [invalid-super-argument]
97 | # revealed: Unknown
98 | reveal_type(super(list[int], []))
| ^^^^^^^^^^^^^^^^^^^^
99 | class Super:
100 | def method(self) -> int:
|
info: rule `invalid-super-argument` is enabled by default
```

View file

@ -66,6 +66,7 @@ use crate::types::generics::{
use crate::types::infer::infer_unpack_types;
use crate::types::mro::{Mro, MroError, MroIterator};
pub(crate) use crate::types::narrow::infer_narrowing_constraint;
use crate::types::newtype::NewType;
use crate::types::signatures::{ParameterForm, walk_signature};
use crate::types::tuple::{TupleSpec, TupleSpecBuilder};
pub(crate) use crate::types::typed_dict::{TypedDictParams, TypedDictType, walk_typed_dict_type};
@ -98,6 +99,7 @@ mod instance;
mod member;
mod mro;
mod narrow;
mod newtype;
mod protocol_class;
mod signatures;
mod special_form;
@ -783,6 +785,13 @@ pub enum Type<'db> {
TypedDict(TypedDictType<'db>),
/// An aliased type (lazily not-yet-unpacked to its value type).
TypeAlias(TypeAliasType<'db>),
/// The set of Python objects that belong to a `typing.NewType` subtype. Note that
/// `typing.NewType` itself is a `Type::ClassLiteral` with `KnownClass::NewType`, and the
/// identity callables it returns (which behave like subtypes in type expressions) are of
/// `Type::KnownInstance` with `KnownInstanceType::NewType`. This `Type` refers to the objects
/// wrapped/returned by a specific one of those identity callables, or by another that inherits
/// from it.
NewTypeInstance(NewType<'db>),
}
#[salsa::tracked]
@ -1420,6 +1429,13 @@ impl<'db> Type<'db> {
self
}
Type::TypeAlias(alias) => alias.value_type(db).normalized_impl(db, visitor),
Type::NewTypeInstance(newtype) => {
visitor.visit(self, || {
Type::NewTypeInstance(newtype.map_base_class_type(db, |class_type| {
class_type.normalized_impl(db, visitor)
}))
})
}
Type::LiteralString
| Type::AlwaysFalsy
| Type::AlwaysTruthy
@ -1482,7 +1498,8 @@ impl<'db> Type<'db> {
| Type::BoundSuper(_)
| Type::TypeIs(_)
| Type::TypedDict(_)
| Type::TypeAlias(_) => false,
| Type::TypeAlias(_)
| Type::NewTypeInstance(_) => false,
}
}
@ -1520,6 +1537,10 @@ impl<'db> Type<'db> {
Type::GenericAlias(alias) => Some(ClassType::Generic(alias).into_callable(db)),
Type::NewTypeInstance(newtype) => {
Type::instance(db, newtype.base_class_type(db)).try_upcast_to_callable(db)
}
// TODO: This is unsound so in future we can consider an opt-in option to disable it.
Type::SubclassOf(subclass_of_ty) => match subclass_of_ty.subclass_of() {
SubclassOfInner::Class(class) => Some(class.into_callable(db)),
@ -1549,6 +1570,15 @@ impl<'db> Type<'db> {
false,
))),
Type::KnownInstance(KnownInstanceType::NewType(newtype)) => Some(CallableType::single(
db,
Signature::new(
Parameters::new([Parameter::positional_only(None)
.with_annotated_type(newtype.base(db).instance_type(db))]),
Some(Type::NewTypeInstance(newtype)),
),
)),
Type::Never
| Type::DataclassTransformer(_)
| Type::AlwaysTruthy
@ -2429,6 +2459,22 @@ impl<'db> Type<'db> {
})
}
(Type::NewTypeInstance(self_newtype), Type::NewTypeInstance(target_newtype)) => {
self_newtype.has_relation_to_impl(db, target_newtype)
}
(
Type::NewTypeInstance(self_newtype),
Type::NominalInstance(target_nominal_instance),
) => self_newtype.base_class_type(db).has_relation_to_impl(
db,
target_nominal_instance.class(db),
inferable,
relation,
relation_visitor,
disjointness_visitor,
),
(Type::PropertyInstance(_), _) => {
KnownClass::Property.to_instance(db).has_relation_to_impl(
db,
@ -2448,14 +2494,15 @@ impl<'db> Type<'db> {
disjointness_visitor,
),
// Other than the special cases enumerated above, `Instance` types and typevars are
// never subtypes of any other variants
// Other than the special cases enumerated above, nominal-instance types,
// newtype-instance types, and typevars are never subtypes of any other variants
(Type::TypeVar(bound_typevar), _) => {
// All inferable cases should have been handled above
assert!(!bound_typevar.is_inferable(db, inferable));
ConstraintSet::from(false)
}
(Type::NominalInstance(_), _) => ConstraintSet::from(false),
(Type::NewTypeInstance(_), _) => ConstraintSet::from(false),
}
}
@ -2529,6 +2576,10 @@ impl<'db> Type<'db> {
})
}
(Type::NewTypeInstance(self_newtype), Type::NewTypeInstance(other_newtype)) => {
ConstraintSet::from(self_newtype.is_equivalent_to_impl(db, other_newtype))
}
(Type::NominalInstance(first), Type::NominalInstance(second)) => {
first.is_equivalent_to_impl(db, second, inferable, visitor)
}
@ -3288,6 +3339,19 @@ impl<'db> Type<'db> {
)
}),
(Type::NewTypeInstance(left), Type::NewTypeInstance(right)) => {
left.is_disjoint_from_impl(db, right)
}
(Type::NewTypeInstance(newtype), other) | (other, Type::NewTypeInstance(newtype)) => {
Type::instance(db, newtype.base_class_type(db)).is_disjoint_from_impl(
db,
other,
inferable,
disjointness_visitor,
relation_visitor,
)
}
(Type::PropertyInstance(_), other) | (other, Type::PropertyInstance(_)) => {
KnownClass::Property.to_instance(db).is_disjoint_from_impl(
db,
@ -3432,6 +3496,9 @@ impl<'db> Type<'db> {
Type::TypeIs(type_is) => type_is.is_bound(db),
Type::TypedDict(_) => false,
Type::TypeAlias(alias) => alias.value_type(db).is_singleton(db),
Type::NewTypeInstance(newtype) => {
Type::instance(db, newtype.base_class_type(db)).is_singleton(db)
}
}
}
@ -3482,6 +3549,9 @@ impl<'db> Type<'db> {
}
Type::NominalInstance(instance) => instance.is_single_valued(db),
Type::NewTypeInstance(newtype) => {
Type::instance(db, newtype.base_class_type(db)).is_single_valued(db)
}
Type::BoundSuper(_) => {
// At runtime two super instances never compare equal, even if their arguments are identical.
@ -3645,7 +3715,8 @@ impl<'db> Type<'db> {
| Type::ProtocolInstance(_)
| Type::PropertyInstance(_)
| Type::TypeIs(_)
| Type::TypedDict(_) => None,
| Type::TypedDict(_)
| Type::NewTypeInstance(_) => None,
}
}
@ -3732,6 +3803,7 @@ impl<'db> Type<'db> {
Type::Dynamic(_) | Type::Never => Place::bound(self).into(),
Type::NominalInstance(instance) => instance.class(db).instance_member(db, name),
Type::NewTypeInstance(newtype) => newtype.base_class_type(db).instance_member(db, name),
Type::ProtocolInstance(protocol) => protocol.instance_member(db, name),
@ -4404,6 +4476,7 @@ impl<'db> Type<'db> {
Type::NominalInstance(..)
| Type::ProtocolInstance(..)
| Type::NewTypeInstance(..)
| Type::BooleanLiteral(..)
| Type::IntLiteral(..)
| Type::StringLiteral(..)
@ -4842,6 +4915,8 @@ impl<'db> Type<'db> {
.value_type(db)
.try_bool_impl(db, allow_short_circuit, visitor)
})?,
Type::NewTypeInstance(newtype) => Type::instance(db, newtype.base_class_type(db))
.try_bool_impl(db, allow_short_circuit, visitor)?,
};
Ok(truthiness)
@ -5528,7 +5603,7 @@ impl<'db> Type<'db> {
SubclassOfInner::Class(class) => Type::from(class).bindings(db),
},
Type::NominalInstance(_) | Type::ProtocolInstance(_) => {
Type::NominalInstance(_) | Type::ProtocolInstance(_) | Type::NewTypeInstance(_) => {
// Note that for objects that have a (possibly not callable!) `__call__` attribute,
// we will get the signature of the `__call__` attribute, but will pass in the type
// of the original object as the "callable type". That ensures that we get errors
@ -5581,6 +5656,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).instance_type(db))]),
Some(Type::NewTypeInstance(newtype)),
),
)
.into(),
Type::KnownInstance(known_instance) => {
known_instance.instance_fallback(db).bindings(db)
}
@ -5716,6 +5801,7 @@ impl<'db> Type<'db> {
match ty {
Type::NominalInstance(nominal) => nominal.tuple_spec(db),
Type::NewTypeInstance(newtype) => non_async_special_case(db, Type::instance(db, newtype.base_class_type(db))),
Type::GenericAlias(alias) if alias.origin(db).is_tuple(db) => {
Some(Cow::Owned(TupleSpec::homogeneous(todo_type!(
"*tuple[] annotations"
@ -6346,6 +6432,9 @@ impl<'db> Type<'db> {
Type::ClassLiteral(class) => Some(Type::instance(db, class.default_specialization(db))),
Type::GenericAlias(alias) => Some(Type::instance(db, ClassType::from(alias))),
Type::SubclassOf(subclass_of_ty) => Some(subclass_of_ty.to_instance(db)),
Type::KnownInstance(KnownInstanceType::NewType(newtype)) => {
Some(Type::NewTypeInstance(newtype))
}
Type::Union(union) => union.to_instance(db),
// If there is no bound or constraints on a typevar `T`, `T: object` implicitly, which
// has no instance type. Otherwise, synthesize a typevar with bound or constraints
@ -6376,7 +6465,8 @@ impl<'db> Type<'db> {
| Type::AlwaysTruthy
| Type::AlwaysFalsy
| Type::TypeIs(_)
| Type::TypedDict(_) => None,
| Type::TypedDict(_)
| Type::NewTypeInstance(_) => None,
}
}
@ -6455,6 +6545,7 @@ impl<'db> Type<'db> {
Type::KnownInstance(known_instance) => match known_instance {
KnownInstanceType::TypeAliasType(alias) => Ok(Type::TypeAlias(*alias)),
KnownInstanceType::NewType(newtype) => Ok(Type::NewTypeInstance(*newtype)),
KnownInstanceType::TypeVar(typevar) => {
let index = semantic_index(db, scope_id.file(db));
Ok(bind_typevar(
@ -6669,9 +6760,6 @@ impl<'db> Type<'db> {
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"
)),
@ -6690,6 +6778,13 @@ impl<'db> Type<'db> {
.value_type(db)
.in_type_expression(db, scope_id, typevar_binding_context)
}
Type::NewTypeInstance(_) => Err(InvalidTypeExpressionError {
invalid_expressions: smallvec::smallvec_inline![
InvalidTypeExpression::InvalidType(*self, scope_id)
],
fallback_type: Type::unknown(),
}),
}
}
@ -6764,6 +6859,7 @@ impl<'db> Type<'db> {
// understand a more specific meta type in order to correctly handle `__getitem__`.
Type::TypedDict(typed_dict) => SubclassOfType::from(db, typed_dict.defining_class()),
Type::TypeAlias(alias) => alias.value_type(db).to_meta_type(db),
Type::NewTypeInstance(newtype) => Type::from(newtype.base_class_type(db)),
}
}
@ -6873,8 +6969,8 @@ impl<'db> Type<'db> {
| TypeMapping::ReplaceParameterDefaults
| TypeMapping::BindLegacyTypevars(_) => self,
TypeMapping::Materialize(materialization_kind) => {
Type::TypeVar(bound_typevar.materialize_impl(db, *materialization_kind, visitor))
}
Type::TypeVar(bound_typevar.materialize_impl(db, *materialization_kind, visitor))
}
}
Type::KnownInstance(KnownInstanceType::TypeVar(typevar)) => match type_mapping {
@ -6909,6 +7005,12 @@ impl<'db> Type<'db> {
instance.apply_type_mapping_impl(db, type_mapping, tcx, visitor)
},
Type::NewTypeInstance(newtype) => visitor.visit(self, || {
Type::NewTypeInstance(newtype.map_base_class_type(db, |class_type| {
class_type.apply_type_mapping_impl(db, type_mapping, tcx, visitor)
}))
}),
Type::ProtocolInstance(instance) => {
// TODO: Add tests for materialization once subtyping/assignability is implemented for
// protocols. It _might_ require changing the logic here because:
@ -7150,6 +7252,12 @@ impl<'db> Type<'db> {
instance.find_legacy_typevars_impl(db, binding_context, typevars, visitor);
}
Type::NewTypeInstance(_) => {
// A newtype can never be constructed from an unspecialized generic class, so it is
// impossible that we could ever find any legacy typevars in a newtype instance or
// its underlying class.
}
Type::SubclassOf(subclass_of) => {
subclass_of.find_legacy_typevars_impl(db, binding_context, typevars, visitor);
}
@ -7305,6 +7413,7 @@ impl<'db> Type<'db> {
},
Self::TypeAlias(alias) => alias.value_type(db).definition(db),
Self::NewTypeInstance(newtype) => Some(TypeDefinition::NewType(newtype.definition(db))),
Self::StringLiteral(_)
| Self::BooleanLiteral(_)
@ -7528,7 +7637,8 @@ impl<'db> VarianceInferable<'db> for Type<'db> {
| Type::BoundSuper(_)
| Type::TypeVar(_)
| Type::TypedDict(_)
| Type::TypeAlias(_) => TypeVarVariance::Bivariant,
| Type::TypeAlias(_)
| Type::NewTypeInstance(_) => TypeVarVariance::Bivariant,
};
tracing::trace!(
@ -7726,6 +7836,10 @@ pub enum KnownInstanceType<'db> {
/// A single instance of `typing.Annotated`
Annotated(InternedType<'db>),
/// An identity callable created with `typing.NewType(name, base)`, which behaves like a
/// subtype of `base` in type expressions. See the `struct NewType` payload for an example.
NewType(NewType<'db>),
}
fn walk_known_instance_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
@ -7760,6 +7874,11 @@ fn walk_known_instance_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
KnownInstanceType::Literal(ty) | KnownInstanceType::Annotated(ty) => {
visitor.visit_type(db, ty.inner(db));
}
KnownInstanceType::NewType(newtype) => {
if let ClassType::Generic(generic_alias) = newtype.base_class_type(db) {
visitor.visit_generic_alias_type(db, generic_alias);
}
}
}
}
@ -7799,6 +7918,10 @@ impl<'db> KnownInstanceType<'db> {
Self::UnionType(list) => Self::UnionType(list.normalized_impl(db, visitor)),
Self::Literal(ty) => Self::Literal(ty.normalized_impl(db, visitor)),
Self::Annotated(ty) => Self::Annotated(ty.normalized_impl(db, visitor)),
Self::NewType(newtype) => Self::NewType(
newtype
.map_base_class_type(db, |class_type| class_type.normalized_impl(db, visitor)),
),
}
}
@ -7819,6 +7942,7 @@ impl<'db> KnownInstanceType<'db> {
Self::UnionType(_) => KnownClass::UnionType,
Self::Literal(_) => KnownClass::GenericAlias,
Self::Annotated(_) => KnownClass::GenericAlias,
Self::NewType(_) => KnownClass::NewType,
}
}
@ -7903,6 +8027,9 @@ impl<'db> KnownInstanceType<'db> {
KnownInstanceType::Annotated(_) => {
f.write_str("<typing.Annotated special form>")
}
KnownInstanceType::NewType(declaration) => {
write!(f, "<NewType pseudo-class '{}'>", declaration.name(self.db))
}
}
}
}

View file

@ -404,6 +404,9 @@ impl<'db> BoundSuperType<'db> {
.to_specialized_instance(db, [key_builder.build(), value_builder.build()]),
);
}
Type::NewTypeInstance(newtype) => {
return delegate_to(Type::instance(db, newtype.base_class_type(db)));
}
Type::Callable(callable) if callable.is_function_like(db) => {
return delegate_to(KnownClass::FunctionType.to_instance(db));
}

View file

@ -358,6 +358,14 @@ pub enum ClassType<'db> {
#[salsa::tracked]
impl<'db> ClassType<'db> {
/// Return a `ClassType` representing the class `builtins.object`
pub(super) fn object(db: &'db dyn Db) -> Self {
KnownClass::Object
.to_class_literal(db)
.to_class_type(db)
.unwrap()
}
pub(super) const fn is_generic(self) -> bool {
matches!(self, Self::Generic(_))
}

View file

@ -137,6 +137,12 @@ impl<'db> ClassBase<'db> {
Type::TypeAlias(alias) => Self::try_from_type(db, alias.value_type(db), subclass),
Type::NewTypeInstance(newtype) => ClassBase::try_from_type(
db,
Type::instance(db, newtype.base_class_type(db)),
subclass,
),
Type::PropertyInstance(_)
| Type::BooleanLiteral(_)
| Type::FunctionLiteral(_)
@ -169,7 +175,11 @@ impl<'db> ClassBase<'db> {
| KnownInstanceType::Field(_)
| KnownInstanceType::ConstraintSet(_)
| KnownInstanceType::UnionType(_)
| KnownInstanceType::Literal(_) => None,
| KnownInstanceType::Literal(_)
// A class inheriting from a newtype would make intuitive sense, but newtype
// wrappers are just identity callables at runtime, so this sort of inheritance
// doesn't work and isn't allowed.
| KnownInstanceType::NewType(_) => None,
KnownInstanceType::Annotated(ty) => Self::try_from_type(db, ty.inner(db), subclass),
},

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

@ -12,6 +12,7 @@ use crate::semantic_index::definition::{Definition, DefinitionKind};
use crate::semantic_index::place::{PlaceTable, ScopedPlaceId};
use crate::semantic_index::{global_scope, place_table};
use crate::suppression::FileSuppressionId;
use crate::types::KnownInstanceType;
use crate::types::call::CallError;
use crate::types::class::{DisjointBase, DisjointBaseKind, Field};
use crate::types::function::KnownFunction;
@ -65,6 +66,7 @@ pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) {
registry.register_lint(&INVALID_LEGACY_TYPE_VARIABLE);
registry.register_lint(&INVALID_PARAMSPEC);
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(&USELESS_OVERLOAD_BODY);
@ -926,6 +928,30 @@ 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
///
/// def get_name() -> str: ...
///
/// Foo = NewType("Foo", int) # okay
/// Bar = NewType(get_name(), int) # error: The first argument to `NewType` must be a string literal
/// Baz = NewType("Baz", int | str) # error: invalid base for `typing.NewType`
/// ```
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.
@ -2898,6 +2924,24 @@ pub(crate) fn report_invalid_or_unsupported_base(
return;
}
if let Type::KnownInstance(KnownInstanceType::NewType(newtype)) = base_type {
let Some(builder) = context.report_lint(&INVALID_BASE, base_node) else {
return;
};
let mut diagnostic = builder.into_diagnostic("Cannot subclass an instance of NewType");
diagnostic.info(format_args!(
"Perhaps you were looking for: `{} = NewType('{}', {})`",
class.name(context.db()),
class.name(context.db()),
newtype.name(context.db()),
));
diagnostic.info(format_args!(
"Definition of class `{}` will raise `TypeError` at runtime",
class.name(context.db())
));
return;
}
let tuple_of_types = Type::homogeneous_tuple(db, instance_of_type);
let explain_mro_entries = |diagnostic: &mut LintDiagnosticGuard| {

View file

@ -618,6 +618,7 @@ impl Display for DisplayRepresentation<'_> {
.fmt(f),
}
}
Type::NewTypeInstance(newtype) => f.write_str(newtype.name(self.db)),
}
}
}

View file

@ -1101,6 +1101,11 @@ fn is_instance_truthiness<'db>(
Type::NominalInstance(..) => always_true_if(is_instance(&ty)),
Type::NewTypeInstance(newtype) => always_true_if(is_instance(&Type::instance(
db,
newtype.base_class_type(db),
))),
Type::BooleanLiteral(..)
| Type::BytesLiteral(..)
| Type::IntLiteral(..)

View file

@ -128,6 +128,10 @@ impl<'db> AllMembers<'db> {
}
}
Type::NewTypeInstance(newtype) => {
self.extend_with_type(db, Type::instance(db, newtype.base_class_type(db)));
}
Type::ClassLiteral(class_literal) if class_literal.is_typed_dict(db) => {
self.extend_with_type(db, KnownClass::TypedDictFallback.to_class_literal(db));
}

View file

@ -59,8 +59,8 @@ use crate::types::diagnostic::{
DIVISION_BY_ZERO, DUPLICATE_KW_ONLY, INCONSISTENT_MRO, INVALID_ARGUMENT_TYPE,
INVALID_ASSIGNMENT, INVALID_ATTRIBUTE_ACCESS, INVALID_BASE, INVALID_DECLARATION,
INVALID_GENERIC_CLASS, INVALID_KEY, INVALID_LEGACY_TYPE_VARIABLE, INVALID_METACLASS,
INVALID_NAMED_TUPLE, INVALID_OVERLOAD, INVALID_PARAMETER_DEFAULT, INVALID_PARAMSPEC,
INVALID_PROTOCOL, INVALID_TYPE_FORM, INVALID_TYPE_GUARD_CALL,
INVALID_NAMED_TUPLE, INVALID_NEWTYPE, INVALID_OVERLOAD, INVALID_PARAMETER_DEFAULT,
INVALID_PARAMSPEC, INVALID_PROTOCOL, INVALID_TYPE_FORM, INVALID_TYPE_GUARD_CALL,
INVALID_TYPE_VARIABLE_CONSTRAINTS, IncompatibleBases, NON_SUBSCRIPTABLE,
POSSIBLY_MISSING_IMPLICIT_CALL, POSSIBLY_MISSING_IMPORT, SUBCLASS_OF_FINAL_CLASS,
UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_GLOBAL, UNRESOLVED_IMPORT,
@ -90,6 +90,7 @@ use crate::types::generics::{
use crate::types::infer::nearest_enclosing_function;
use crate::types::instance::SliceLiteral;
use crate::types::mro::MroErrorKind;
use crate::types::newtype::NewType;
use crate::types::signatures::Signature;
use crate::types::subclass_of::SubclassOfInner;
use crate::types::tuple::{Tuple, TupleLength, TupleSpec, TupleType};
@ -3884,7 +3885,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
| Type::AlwaysTruthy
| Type::AlwaysFalsy
| Type::TypeIs(_)
| Type::TypedDict(_) => {
| Type::TypedDict(_)
| Type::NewTypeInstance(_) => {
// TODO: We could use the annotated parameter type of `__setattr__` as type context here.
// However, we would still have to perform the first inference without type context.
let value_ty = infer_value_ty(self, TypeContext::default());
@ -4454,6 +4456,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
Some(KnownClass::ParamSpec) => {
self.infer_paramspec(target, call_expr, definition)
}
Some(KnownClass::NewType) => {
self.infer_newtype_expression(target, call_expr, definition)
}
Some(_) | None => {
self.infer_call_expression_impl(call_expr, callable_type, tcx)
}
@ -4892,14 +4897,114 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
)))
}
fn infer_newtype_expression(
&mut self,
target: &ast::Expr,
call_expr: &ast::ExprCall,
definition: Definition<'db>,
) -> Type<'db> {
fn error<'db>(
context: &InferContext<'db, '_>,
message: impl std::fmt::Display,
node: impl Ranged,
) -> Type<'db> {
if let Some(builder) = context.report_lint(&INVALID_NEWTYPE, node) {
builder.into_diagnostic(message);
}
Type::unknown()
}
let db = self.db();
let arguments = &call_expr.arguments;
if !arguments.keywords.is_empty() {
return error(
&self.context,
"Keyword arguments are not supported in `NewType` creation",
call_expr,
);
}
if let Some(starred) = arguments.args.iter().find(|arg| arg.is_starred_expr()) {
return error(
&self.context,
"Starred arguments are not supported in `NewType` creation",
starred,
);
}
if arguments.args.len() != 2 {
return error(
&self.context,
format!(
"Wrong number of arguments in `NewType` creation, expected 2, found {}",
arguments.args.len()
),
call_expr,
);
}
let name_param_ty = self.infer_expression(&arguments.args[0], TypeContext::default());
let Some(name) = name_param_ty.as_string_literal().map(|name| name.value(db)) else {
return error(
&self.context,
"The first argument to `NewType` must be a string literal",
call_expr,
);
};
let ast::Expr::Name(ast::ExprName {
id: target_name, ..
}) = target
else {
return error(
&self.context,
"A `NewType` definition must be a simple variable assignment",
target,
);
};
if name != target_name {
return error(
&self.context,
format_args!(
"The name of a `NewType` (`{name}`) must match \
the name of the variable it is assigned to (`{target_name}`)"
),
target,
);
}
// Inference of `tp` must be deferred, to avoid cycles.
self.deferred.insert(definition, self.multi_inference_state);
Type::KnownInstance(KnownInstanceType::NewType(NewType::new(
db,
ast::name::Name::from(name),
definition,
None,
)))
}
fn infer_assignment_deferred(&mut self, value: &ast::Expr) {
// Infer deferred bounds/constraints/defaults of a legacy TypeVar / ParamSpec.
// Infer deferred bounds/constraints/defaults of a legacy TypeVar / ParamSpec / NewType.
let ast::Expr::Call(ast::ExprCall {
func, arguments, ..
}) = value
else {
return;
};
let func_ty = self
.try_expression_type(func)
.unwrap_or_else(|| self.infer_expression(func, TypeContext::default()));
let known_class = func_ty
.as_class_literal()
.and_then(|cls| cls.known(self.db()));
if let Some(KnownClass::NewType) = known_class {
self.infer_newtype_assignment_deferred(arguments);
return;
}
for arg in arguments.args.iter().skip(1) {
self.infer_type_expression(arg);
}
@ -4907,12 +5012,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
self.infer_type_expression(&bound.value);
}
if let Some(default) = arguments.find_keyword("default") {
let func_ty = self
.try_expression_type(func)
.unwrap_or_else(|| self.infer_expression(func, TypeContext::default()));
if func_ty.as_class_literal().is_some_and(|class_literal| {
class_literal.is_known(self.db(), KnownClass::ParamSpec)
}) {
if let Some(KnownClass::ParamSpec) = known_class {
self.infer_paramspec_default(&default.value);
} else {
self.infer_type_expression(&default.value);
@ -4920,6 +5020,34 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}
}
// Infer the deferred base type of a NewType.
fn infer_newtype_assignment_deferred(&mut self, arguments: &ast::Arguments) {
match self.infer_type_expression(&arguments.args[1]) {
Type::NominalInstance(_) | Type::NewTypeInstance(_) => {}
// `Unknown` is likely to be the result of an unresolved import or a typo, which will
// already get a diagnostic, so don't pile on an extra diagnostic here.
Type::Dynamic(DynamicType::Unknown) => {}
other_type => {
if let Some(builder) = self
.context
.report_lint(&INVALID_NEWTYPE, &arguments.args[1])
{
let mut diag = builder.into_diagnostic("invalid base for `typing.NewType`");
diag.set_primary_message(format!("type `{}`", other_type.display(self.db())));
if matches!(other_type, Type::ProtocolInstance(_)) {
diag.info("The base of a `NewType` is not allowed to be a protocol class.");
} else if matches!(other_type, Type::TypedDict(_)) {
diag.info("The base of a `NewType` is not allowed to be a `TypedDict`.");
} else {
diag.info(
"The base of a `NewType` must be a class type or another `NewType`.",
);
}
}
}
}
}
fn infer_annotated_assignment_statement(&mut self, assignment: &ast::StmtAnnAssign) {
if assignment.target.is_name_expr() {
self.infer_definition(assignment);
@ -7483,11 +7611,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
.to_class_type(self.db())
.is_none_or(|enum_class| !class.is_subclass_of(self.db(), enum_class))
{
// Inference of correctly-placed `TypeVar` and `ParamSpec` definitions is done in
// `TypeInferenceBuilder::infer_legacy_typevar` and
// `TypeInferenceBuilder::infer_paramspec`, and doesn't use the full
// call-binding machinery. If we reach here, it means that someone is trying to
// instantiate a `typing.TypeVar` and `typing.ParamSpec` in an invalid context.
// Inference of correctly-placed `TypeVar`, `ParamSpec`, and `NewType` definitions
// is done in `infer_legacy_typevar`, `infer_paramspec`, and
// `infer_newtype_expression`, and doesn't use the full call-binding machinery. If
// we reach here, it means that someone is trying to instantiate one of these in an
// invalid context.
match class.known(self.db()) {
Some(KnownClass::TypeVar | KnownClass::ExtensionsTypeVar) => {
if let Some(builder) = self
@ -7509,6 +7637,15 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
);
}
}
Some(KnownClass::NewType) => {
if let Some(builder) =
self.context.report_lint(&INVALID_NEWTYPE, call_expression)
{
builder.into_diagnostic(
"A `NewType` definition must be a simple variable assignment",
);
}
}
_ => {}
}
@ -8577,7 +8714,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
| Type::BoundSuper(_)
| Type::TypeVar(_)
| Type::TypeIs(_)
| Type::TypedDict(_),
| Type::TypedDict(_)
| Type::NewTypeInstance(_),
) => {
let unary_dunder_method = match op {
ast::UnaryOp::Invert => "__invert__",
@ -9025,7 +9163,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
| Type::BoundSuper(_)
| Type::TypeVar(_)
| Type::TypeIs(_)
| Type::TypedDict(_),
| Type::TypedDict(_)
| Type::NewTypeInstance(_),
Type::FunctionLiteral(_)
| Type::BooleanLiteral(_)
| Type::Callable(..)
@ -9054,7 +9193,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
| Type::BoundSuper(_)
| Type::TypeVar(_)
| Type::TypeIs(_)
| Type::TypedDict(_),
| Type::TypedDict(_)
| Type::NewTypeInstance(_),
op,
) => Type::try_call_bin_op(self.db(), left_ty, op, right_ty)
.map(|outcome| outcome.return_type(self.db()))

View file

@ -828,6 +828,16 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
self.infer_type_expression(slice);
todo_type!("Generic specialization of typing.Annotated")
}
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);

View file

@ -252,7 +252,8 @@ impl ClassInfoConstraintFunction {
| Type::TypeIs(_)
| Type::WrapperDescriptor(_)
| Type::DataclassTransformer(_)
| Type::TypedDict(_) => None,
| Type::TypedDict(_)
| Type::NewTypeInstance(_) => None,
}
}
}

View file

@ -0,0 +1,266 @@
use std::collections::BTreeSet;
use crate::Db;
use crate::semantic_index::definition::{Definition, DefinitionKind};
use crate::types::constraints::ConstraintSet;
use crate::types::{ClassType, Type, definition_expression_type, visitor};
use ruff_db::parsed::parsed_module;
use ruff_python_ast as ast;
/// A `typing.NewType` declaration, either from the perspective of the
/// identity-callable-that-acts-like-a-subtype-in-type-expressions returned by the call to
/// `typing.NewType(...)`, or from the perspective of instances of that subtype returned by the
/// identity callable. For example:
///
/// ```py
/// import typing
/// Foo = typing.NewType("Foo", int)
/// x = Foo(42)
/// ```
///
/// The revealed types there are:
/// - `typing.NewType`: `Type::ClassLiteral(ClassLiteral)` with `KnownClass::NewType`.
/// - `Foo`: `Type::KnownInstance(KnownInstanceType::NewType(NewType { .. }))`
/// - `x`: `Type::NewTypeInstance(NewType { .. })`
///
/// # Ordering
/// Ordering is based on the newtype's salsa-assigned id and not on its values.
/// The id may change between runs, or when the newtype was garbage collected and recreated.
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct NewType<'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 type of this NewType, if it's eagerly specified. This is typically `None` when a
// `NewType` is first encountered, because the base type is lazy/deferred to avoid panics in
// the recursive case. This becomes `Some` when a `NewType` is modified by methods like
// `.normalize()`. Callers should use the `base` method instead of accessing this field
// directly.
eager_base: Option<NewTypeBase<'db>>,
}
impl get_size2::GetSize for NewType<'_> {}
#[salsa::tracked]
impl<'db> NewType<'db> {
pub fn base(self, db: &'db dyn Db) -> NewTypeBase<'db> {
match self.eager_base(db) {
Some(base) => base,
None => self.lazy_base(db),
}
}
#[salsa::tracked(
cycle_initial=lazy_base_cycle_initial,
heap_size=ruff_memory_usage::heap_size
)]
fn lazy_base(self, db: &'db dyn Db) -> NewTypeBase<'db> {
// `TypeInferenceBuilder` emits diagnostics for invalid `NewType` definitions that show up
// in assignments, but invalid definitions still get here, and also `NewType` might show up
// in places that aren't definitions at all. Fall back to `object` in all error cases.
let object_fallback = NewTypeBase::ClassType(ClassType::object(db));
let definition = self.definition(db);
let module = parsed_module(db, definition.file(db)).load(db);
let DefinitionKind::Assignment(assignment) = definition.kind(db) else {
return object_fallback;
};
let Some(call_expr) = assignment.value(&module).as_call_expr() else {
return object_fallback;
};
let Some(second_arg) = call_expr.arguments.args.get(1) else {
return object_fallback;
};
match definition_expression_type(db, definition, second_arg) {
Type::NominalInstance(nominal_instance_type) => {
NewTypeBase::ClassType(nominal_instance_type.class(db))
}
Type::NewTypeInstance(newtype) => NewTypeBase::NewType(newtype),
// This branch includes bases that are other typing constructs besides classes and
// other newtypes, for example unions. `NewType("Foo", int | str)` is not allowed.
_ => object_fallback,
}
}
fn iter_bases(self, db: &'db dyn Db) -> NewTypeBaseIter<'db> {
NewTypeBaseIter {
current: Some(self),
seen_before: BTreeSet::new(),
db,
}
}
// Walk the `NewTypeBase` chain to find the underlying `ClassType`. There might not be a
// `ClassType` if this `NewType` is cyclical, and we fall back to `object` in that case.
pub fn base_class_type(self, db: &'db dyn Db) -> ClassType<'db> {
for base in self.iter_bases(db) {
if let NewTypeBase::ClassType(class_type) = base {
return class_type;
}
}
ClassType::object(db)
}
pub(crate) fn is_equivalent_to_impl(self, db: &'db dyn Db, other: Self) -> bool {
// Two instances of the "same" `NewType` won't compare == if one of them has an eagerly
// evaluated base (or a normalized base, etc.) and the other doesn't, so we only check for
// equality of the `definition`.
self.definition(db) == other.definition(db)
}
// Since a regular class can't inherit from a newtype, the only way for one newtype to be a
// subtype of another is to have the other in its chain of newtype bases. Once we reach the
// base class, we don't have to keep looking.
pub(crate) fn has_relation_to_impl(self, db: &'db dyn Db, other: Self) -> ConstraintSet<'db> {
if self.is_equivalent_to_impl(db, other) {
return ConstraintSet::from(true);
}
for base in self.iter_bases(db) {
if let NewTypeBase::NewType(base_newtype) = base {
if base_newtype.is_equivalent_to_impl(db, other) {
return ConstraintSet::from(true);
}
}
}
ConstraintSet::from(false)
}
pub(crate) fn is_disjoint_from_impl(self, db: &'db dyn Db, other: Self) -> ConstraintSet<'db> {
// 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.
let mut self_not_subtype_of_other = self.has_relation_to_impl(db, other).negate(db);
let other_not_subtype_of_self = other.has_relation_to_impl(db, self).negate(db);
self_not_subtype_of_other.intersect(db, other_not_subtype_of_self)
}
/// Create a new `NewType` by mapping the underlying `ClassType`. This descends through any
/// number of nested `NewType` layers and rebuilds the whole chain. In the rare case of cyclic
/// `NewType`s with no underlying `ClassType`, this has no effect and does not call `f`.
pub(crate) fn map_base_class_type(
self,
db: &'db dyn Db,
f: impl FnOnce(ClassType<'db>) -> ClassType<'db>,
) -> Self {
// Modifying the base class type requires unwrapping and re-wrapping however many base
// newtypes there are between here and there. Normally recursion would be natural for this,
// but the bases iterator does cycle detection, and I think using that with a stack is a
// little cleaner than conjuring up yet another `CycleDetector` visitor and yet another
// layer of "*_impl" nesting. Also if there is no base class type, returning `self`
// unmodified seems more correct than injecting some default type like `object` into the
// cycle, which is what `CycleDetector` would do if we used it here.
let mut inner_newtype_stack = Vec::new();
for base in self.iter_bases(db) {
match base {
// Build up the stack of intermediate newtypes that we'll need to re-wrap after
// we've mapped the `ClassType`.
NewTypeBase::NewType(base_newtype) => inner_newtype_stack.push(base_newtype),
// We've reached the `ClassType`.
NewTypeBase::ClassType(base_class_type) => {
// Call `f`.
let mut mapped_base = NewTypeBase::ClassType(f(base_class_type));
// Re-wrap the mapped base class in however many newtypes we unwrapped.
for inner_newtype in inner_newtype_stack.into_iter().rev() {
mapped_base = NewTypeBase::NewType(NewType::new(
db,
inner_newtype.name(db).clone(),
inner_newtype.definition(db),
Some(mapped_base),
));
}
return NewType::new(
db,
self.name(db).clone(),
self.definition(db),
Some(mapped_base),
);
}
}
}
// If we get here, there is no `ClassType` (because this newtype is cyclic), and we don't
// call `f` at all.
self
}
}
pub(crate) fn walk_newtype_instance_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
db: &'db dyn Db,
newtype: NewType<'db>,
visitor: &V,
) {
visitor.visit_type(db, newtype.base(db).instance_type(db));
}
/// `typing.NewType` typically wraps a class type, but it can also wrap another newtype.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, get_size2::GetSize, salsa::Update)]
pub enum NewTypeBase<'db> {
ClassType(ClassType<'db>),
NewType(NewType<'db>),
}
impl<'db> NewTypeBase<'db> {
pub fn instance_type(self, db: &'db dyn Db) -> Type<'db> {
match self {
NewTypeBase::ClassType(class_type) => Type::instance(db, class_type),
NewTypeBase::NewType(newtype) => Type::NewTypeInstance(newtype),
}
}
}
/// An iterator over the transitive bases of a `NewType`. In the most common case, e.g.
/// `Foo = NewType("Foo", int)`, this yields the one `NewTypeBase::ClassType` (e.g. `int`). For
/// newtypes that wrap other newtypes, this iterator yields the `NewTypeBase::NewType`s (not
/// including `self`) before finally yielding the `NewTypeBase::ClassType`. In the pathological
/// case of cyclic newtypes like `Foo = NewType("Foo", "Foo")`, this iterator yields the unique
/// `NewTypeBase::NewType`s (not including `self`), detects the cycle, and then stops.
///
/// Note that this does *not* detect indirect cycles that go through a proper class, like this:
/// ```py
/// Foo = NewType("Foo", list["Foo"])
/// ```
/// As far as this iterator is concerned, that's the "common case", and it yields the one
/// `NewTypeBase::ClassType` for `list[Foo]`. Functions like `normalize` that continue recursing
/// over the base class need to pass down a cycle-detecting visitor as usual.
struct NewTypeBaseIter<'db> {
current: Option<NewType<'db>>,
seen_before: BTreeSet<NewType<'db>>,
db: &'db dyn Db,
}
impl<'db> Iterator for NewTypeBaseIter<'db> {
type Item = NewTypeBase<'db>;
fn next(&mut self) -> Option<Self::Item> {
let current = self.current?;
match current.base(self.db) {
NewTypeBase::ClassType(base_class_type) => {
self.current = None;
Some(NewTypeBase::ClassType(base_class_type))
}
NewTypeBase::NewType(base_newtype) => {
// Doing the insertion only in this branch avoids allocating in the common case.
self.seen_before.insert(current);
if self.seen_before.contains(&base_newtype) {
// Cycle detected. Stop iterating.
self.current = None;
None
} else {
self.current = Some(base_newtype);
Some(NewTypeBase::NewType(base_newtype))
}
}
}
}
}
fn lazy_base_cycle_initial<'db>(
db: &'db dyn Db,
_id: salsa::Id,
_self: NewType<'db>,
) -> NewTypeBase<'db> {
NewTypeBase::ClassType(ClassType::object(db))
}

View file

@ -213,6 +213,10 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
(Type::TypedDict(_), _) => Ordering::Less,
(_, Type::TypedDict(_)) => Ordering::Greater,
(Type::NewTypeInstance(left), Type::NewTypeInstance(right)) => left.cmp(right),
(Type::NewTypeInstance(_), _) => Ordering::Less,
(_, Type::NewTypeInstance(_)) => Ordering::Greater,
(Type::Union(_), _) | (_, Type::Union(_)) => {
unreachable!("our type representation does not permit nested unions");
}

View file

@ -11,6 +11,7 @@ use crate::{
class::walk_generic_alias,
function::{FunctionType, walk_function_type},
instance::{walk_nominal_instance_type, walk_protocol_instance_type},
newtype::{NewType, walk_newtype_instance_type},
subclass_of::walk_subclass_of_type,
walk_bound_method_type, walk_bound_type_var_type, walk_callable_type,
walk_intersection_type, walk_known_instance_type, walk_method_wrapper_type,
@ -109,6 +110,10 @@ pub(crate) trait TypeVisitor<'db> {
fn visit_typed_dict_type(&self, db: &'db dyn Db, typed_dict: TypedDictType<'db>) {
walk_typed_dict_type(db, typed_dict, self);
}
fn visit_newtype_instance_type(&self, db: &'db dyn Db, newtype: NewType<'db>) {
walk_newtype_instance_type(db, newtype, self);
}
}
/// Enumeration of types that may contain other types, such as unions, intersections, and generics.
@ -131,6 +136,7 @@ pub(super) enum NonAtomicType<'db> {
ProtocolInstance(ProtocolInstanceType<'db>),
TypedDict(TypedDictType<'db>),
TypeAlias(TypeAliasType<'db>),
NewTypeInstance(NewType<'db>),
}
pub(super) enum TypeKind<'db> {
@ -198,6 +204,9 @@ impl<'db> From<Type<'db>> for TypeKind<'db> {
TypeKind::NonAtomic(NonAtomicType::TypedDict(typed_dict))
}
Type::TypeAlias(alias) => TypeKind::NonAtomic(NonAtomicType::TypeAlias(alias)),
Type::NewTypeInstance(newtype) => {
TypeKind::NonAtomic(NonAtomicType::NewTypeInstance(newtype))
}
}
}
}
@ -239,6 +248,9 @@ pub(super) fn walk_non_atomic_type<'db, V: TypeVisitor<'db> + ?Sized>(
NonAtomicType::TypeAlias(alias) => {
visitor.visit_type_alias_type(db, alias);
}
NonAtomicType::NewTypeInstance(newtype) => {
visitor.visit_newtype_instance_type(db, newtype);
}
}
}

View file

@ -59,6 +59,7 @@ Settings: Settings {
"invalid-legacy-type-variable": Error (Default),
"invalid-metaclass": Error (Default),
"invalid-named-tuple": Error (Default),
"invalid-newtype": Error (Default),
"invalid-overload": Error (Default),
"invalid-parameter-default": Error (Default),
"invalid-paramspec": Error (Default),

10
ty.schema.json generated
View file

@ -623,6 +623,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\ndef get_name() -> str: ...\n\nFoo = NewType(\"Foo\", int) # okay\nBar = NewType(get_name(), int) # error: The first argument to `NewType` must be a string literal\nBaz = NewType(\"Baz\", int | str) # error: invalid base for `typing.NewType`\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)",