Compare commits

..

22 Commits

Author SHA1 Message Date
Micha Reiser
c5aa525d2a Reformat files 2026-01-14 10:48:16 +01:00
Micha Reiser
1566137d27 Disable pyi mode for mdtests 2026-01-14 10:47:57 +01:00
Charlie Marsh
b5814b91c1 [ty] Add diagnostics to validate TypeIs and TypeGuard definitions (#22300)
## Summary

Closes https://github.com/astral-sh/ty/issues/2267.
2026-01-13 20:24:05 -05:00
Charlie Marsh
ea46426157 [ty] Apply narrowing to walrus targets (#22369)
## Summary

Closes https://github.com/astral-sh/ty/issues/2300.
2026-01-14 00:56:47 +00:00
Alex Waygood
ddd2fc7a90 [ty] Use "typeguard constraints" for two kinds of tuple narrowing (#22348)
## Summary

Since we've already filtered the union in these locations, it seems like
needless overhead to then intersect the previous union with the filtered
union. We know what that intersection will simplify to: it will simplify
to the filtered union. So rather than using a regular intersection-based
constraint, we can use a "typeguard constraint", which will just
directly replace the previous type with the new type instead of creating
an intersection.

## Test Plan

- Existing tests all pass
- The primer report should be clean
2026-01-13 23:37:09 +00:00
Will Duke
4ebf10cf1b [ty] Add a conformance script to compare ty diagnostics with expected errors (#22231) 2026-01-13 22:19:41 +00:00
Charlie Marsh
9a676bbeb7 [ty] Add diagnostic to catch generic enums (#22482)
## Summary

Closes https://github.com/astral-sh/ty/issues/2416.

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2026-01-13 16:55:46 -05:00
Bhuminjay Soni
d9028a098b [isort] Insert imports in alphabetical order (I002) (#22493)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title? (Please prefix
with `[ty]` for ty pull
  requests.)
- Does this pull request include references to any relevant issues?
-->

## Summary

<!-- What's the purpose of the change? What does it do, and why? -->
This PR fixes #20811 , current approach reverses the order in `BtreeSet`
however as pointed in
https://github.com/astral-sh/ruff/issues/20811#issuecomment-3398958832
here we cannot use I`IndexSet` to preserve config order since Settings
derives `CacheKey` which isn't implemented for `IndexSet`, another
approach to preserve the original order might be to use `Vec` however
lookup time complexity might get affected as a result.

<!-- How was it tested? -->
I have tested it locally its working as expected ,
<img width="2200" height="1071" alt="image"
src="https://github.com/user-attachments/assets/7d97b488-1552-4a42-9d90-92acf55ec493"
/>

---------

Signed-off-by: Bhuminjay <bhuminjaysoni@gmail.com>
2026-01-13 21:21:18 +00:00
Alex Waygood
56077ee9a9 [ty] Fix @Todo type for starred expressions (#22503) 2026-01-13 21:09:29 +00:00
Alex Waygood
20c01d2553 [ty] Use the top materialization of classes for if type(x) is y narrowing (#22553) 2026-01-13 20:53:52 +00:00
Harutaka Kawamura
c98ea1bc24 [flake8-pytest-style] Add check parameter example to PT017 docs (#22546)
## Summary

- Adds an alternative example to the PT017 (`pytest-assert-in-except`)
rule documentation showing pytest's `check` parameter for validating
exceptions, available since pytest 8.4.0

Closes #22529

## Test plan

Documentation-only change. Verified with `uvx prek run -a`.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 15:45:53 -05:00
GiGaGon
9a2990b2a1 [ruff] Make example error out-of-the-box (RUF103) (#22558)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title? (Please prefix
with `[ty]` for ty pull
  requests.)
- Does this pull request include references to any relevant issues?
-->

## Summary

<!-- What's the purpose of the change? What does it do, and why? -->

Part of #18972

This PR makes [invalid-suppression-comment
(RUF103)](https://docs.astral.sh/ruff/rules/invalid-suppression-comment/#invalid-suppression-comment-ruf103)'s
example error out-of-the-box.

[Old example](https://play.ruff.rs/3ff757f3-04ae-4d27-986d-49972338fa24)
```py
ruff: disable # missing codes
```

[New example](https://play.ruff.rs/4a9970c4-3b33-4533-8ffa-f15d481b1e6f)
```py
# ruff: disable # missing codes
```

## Test Plan

<!-- How was it tested? -->

N/A, no functionality/tests affected
2026-01-13 15:06:54 -05:00
Carl Meyer
a697050a83 [ty] Fix stack overflow with recursive type aliases containing tuple … (#22543)
This fixes issue #2470 where recursive type aliases like `type
RecursiveT = int | tuple[RecursiveT, ...]` caused a stack overflow when
used in return type checking with constructors like `list()`.

The fix moves all type mapping processing for `UniqueSpecialization`
(and other non-EagerExpansion mappings) inside the `visitor.visit()`
closure. This ensures that if we encounter the same TypeAlias
recursively during type mapping, the cycle detector will properly detect
it and return the fallback value instead of recursing infinitely.

The key insight is that the previous code called
`apply_function_specialization` followed by another
`apply_type_mapping_impl` AFTER the visitor closure returned. At that
point, the TypeAlias was no longer in the visitor's `seen` set, so
recursive references would not be detected as cycles.
2026-01-13 11:25:01 -08:00
Dex Devlon
2f64ef9c72 [ty] Include type parameters in generic callable display (#22435) 2026-01-13 17:29:08 +00:00
RasmusNygren
fde7d72fbb [ty] Add diagnostics for __init_subclass__ argument mismatch (#22185) 2026-01-13 16:15:51 +00:00
drbh
d13b5db066 [ty] narrow the right-hand side of ==, !=, is and is not conditions when the left-hand side is not narrowable (#22511)
Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
2026-01-13 16:01:54 +00:00
Alex Waygood
c7b41060f4 [ty] Improve disambiguation of types (#22547) 2026-01-13 14:56:56 +00:00
Charlie Marsh
3878701265 [ty] Support own instance members for type(...) classes (#22480)
## Summary

Addresses
https://github.com/astral-sh/ruff/pull/22291#discussion_r2674467950.
2026-01-13 09:36:03 -05:00
Enric Calabuig
6e89e0abff [ty] Fix classmethod + contextmanager + Self (#22407)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title? (Please prefix
with `[ty]` for ty pull
  requests.)
- Does this pull request include references to any relevant issues?
-->

## Summary

The test I've added illustrates the fix. Copying it here too:

```python
from contextlib import contextmanager
from typing import Iterator
from typing_extensions import Self

class Base:
    @classmethod
    @contextmanager
    def create(cls) -> Iterator[Self]:
        yield cls()

class Child(Base): ...

with Base.create() as base:
    reveal_type(base)  # revealed: Base (after the fix, None before)

with Child.create() as child:
    reveal_type(child)  # revealed: Child (after the fix, None before)
```

Full disclosure: I've used LLMs for this PR, but the result is
thoroughly reviewed by me before submitting. I'm excited about my first
Rust contribution to Astral tools and will address feedback quickly.

Related to https://github.com/astral-sh/ty/issues/2030, I am working on
a fix for the TypeVar case also reported in that issue (by me)

<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan

<!-- How was it tested? -->

Updated mdtests

---------

Co-authored-by: Douglas Creager <dcreager@dcreager.net>
2026-01-13 09:29:58 -05:00
Manuel Jacob
cb31883c5f Correct comment about public functions starting with an underscore. (#22550) 2026-01-13 14:04:19 +00:00
Charlie Marsh
6d8f2864c3 [ty] Rename MRO structs to match static nomenclature (#22549)
## Summary

I didn't want to make the "dynamic" `type(...)` PR any larger, but it
probably makes sense to rename these now that we have `Dynamic`
variants.
2026-01-13 08:53:49 -05:00
Dhruv Manilawala
990d0a8999 [ty] Improve log guidance message for Zed (#22530)
## Summary

closes: https://github.com/astral-sh/ty/issues/1717

## Test Plan


https://github.com/user-attachments/assets/5d8d6d03-3451-4403-a6cd-2deba9e796fc
2026-01-13 08:13:11 +00:00
286 changed files with 11163 additions and 1258 deletions

View File

@@ -121,7 +121,7 @@ repos:
hooks:
- id: blacken-docs
language: python # means renovate will also update `additional_dependencies`
args: ["--pyi", "--line-length", "130"]
args: ["--line-length", "130"]
files: '^crates/.*/resources/mdtest/.*\.md'
exclude: |
(?x)^(

View File

@@ -106,6 +106,16 @@ impl Violation for PytestCompositeAssertion {
/// assert exc_info.value.args
/// ```
///
/// Or, for pytest 8.4.0 and later:
/// ```python
/// import pytest
///
///
/// def test_foo():
/// with pytest.raises(ZeroDivisionError, check=lambda e: e.args):
/// 1 / 0
/// ```
///
/// ## References
/// - [`pytest` documentation: `pytest.raises`](https://docs.pytest.org/en/latest/reference/reference.html#pytest-raises)
#[derive(ViolationMetadata)]

View File

@@ -101,7 +101,7 @@ pub(crate) fn private_member_access(checker: &Checker, expr: &Expr) {
}
}
// Allow some documented private methods, like `os._exit()`.
// Allow some public functions whose names start with an underscore, like `os._exit()`.
if let Some(qualified_name) = semantic.resolve_qualified_name(expr) {
if matches!(qualified_name.segments(), ["os", "_exit"]) {
return;

View File

@@ -140,7 +140,7 @@ pub(crate) fn add_required_imports(
source_type: PySourceType,
context: &LintContext,
) {
for required_import in &settings.isort.required_imports {
for required_import in settings.isort.required_imports.iter().rev() {
add_required_import(
required_import,
parsed,

View File

@@ -1,14 +1,6 @@
---
source: crates/ruff_linter/src/rules/isort/mod.rs
---
I002 [*] Missing required import: `from __future__ import annotations`
--> docstring.py:1:1
help: Insert required import: `from __future__ import annotations`
1 | """Hello, world!"""
2 + from __future__ import annotations
3 |
4 | x = 1
I002 [*] Missing required import: `from __future__ import generator_stop`
--> docstring.py:1:1
help: Insert required import: `from __future__ import generator_stop`
@@ -16,3 +8,11 @@ help: Insert required import: `from __future__ import generator_stop`
2 + from __future__ import generator_stop
3 |
4 | x = 1
I002 [*] Missing required import: `from __future__ import annotations`
--> docstring.py:1:1
help: Insert required import: `from __future__ import annotations`
1 | """Hello, world!"""
2 + from __future__ import annotations
3 |
4 | x = 1

View File

@@ -1,15 +1,6 @@
---
source: crates/ruff_linter/src/rules/isort/mod.rs
---
I002 [*] Missing required import: `from __future__ import annotations`
--> multiple_strings.py:1:1
help: Insert required import: `from __future__ import annotations`
1 | """This is a docstring."""
2 + from __future__ import annotations
3 | "This is not a docstring."
4 | "This is also not a docstring."
5 |
I002 [*] Missing required import: `from __future__ import generator_stop`
--> multiple_strings.py:1:1
help: Insert required import: `from __future__ import generator_stop`
@@ -17,4 +8,13 @@ help: Insert required import: `from __future__ import generator_stop`
2 + from __future__ import generator_stop
3 | "This is not a docstring."
4 | "This is also not a docstring."
5 |
I002 [*] Missing required import: `from __future__ import annotations`
--> multiple_strings.py:1:1
help: Insert required import: `from __future__ import annotations`
1 | """This is a docstring."""
2 + from __future__ import annotations
3 | "This is not a docstring."
4 | "This is also not a docstring."
5 |

View File

@@ -398,17 +398,17 @@ mod tests {
1 + from pipes import Template
2 + from shlex import quote
I002 [*] Missing required import: `from __future__ import generator_stop`
--> <filename>:1:1
help: Insert required import: `from __future__ import generator_stop`
1 + from __future__ import generator_stop
2 | from pipes import quote, Template
I002 [*] Missing required import: `from collections import Sequence`
--> <filename>:1:1
help: Insert required import: `from collections import Sequence`
1 + from collections import Sequence
2 | from pipes import quote, Template
I002 [*] Missing required import: `from __future__ import generator_stop`
--> <filename>:1:1
help: Insert required import: `from __future__ import generator_stop`
1 + from __future__ import generator_stop
2 | from pipes import quote, Template
");
}

View File

@@ -12,7 +12,7 @@ use crate::suppression::{InvalidSuppressionKind, ParseErrorKind};
///
/// ## Example
/// ```python
/// ruff: disable # missing codes
/// # ruff: disable # missing codes
/// ```
///
/// Use instead:

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

@@ -8,7 +8,7 @@
Default level: <a href="../../rules#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#L541" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L542" target="_blank">View source</a>
</small>
@@ -80,7 +80,7 @@ def test(): -> "int":
Default level: <a href="../../rules#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#L140" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L141" target="_blank">View source</a>
</small>
@@ -104,7 +104,7 @@ Calling a non-callable object will raise a `TypeError` at runtime.
Default level: <a href="../../rules#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.7">0.0.7</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20call-top-callable" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L158" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L159" target="_blank">View source</a>
</small>
@@ -135,7 +135,7 @@ def f(x: object):
Default level: <a href="../../rules#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#L209" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L210" target="_blank">View source</a>
</small>
@@ -167,7 +167,7 @@ f(int) # error
Default level: <a href="../../rules#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#L235" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L236" target="_blank">View source</a>
</small>
@@ -198,7 +198,7 @@ a = 1
Default level: <a href="../../rules#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#L260" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L261" target="_blank">View source</a>
</small>
@@ -230,7 +230,7 @@ class C(A, B): ...
Default level: <a href="../../rules#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#L286" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L287" target="_blank">View source</a>
</small>
@@ -262,7 +262,7 @@ class B(A): ...
Default level: <a href="../../rules#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%20cyclic-type-alias-definition" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L312" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L313" target="_blank">View source</a>
</small>
@@ -290,7 +290,7 @@ type B = A
Default level: <a href="../../rules#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#L356" target="_blank">View source</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>
</small>
@@ -317,7 +317,7 @@ old_func() # emits [deprecated] diagnostic
Default level: <a href="../../rules#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#L334" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L335" target="_blank">View source</a>
</small>
@@ -346,7 +346,7 @@ false positives it can produce.
Default level: <a href="../../rules#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#L377" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L378" target="_blank">View source</a>
</small>
@@ -373,7 +373,7 @@ class B(A, A): ...
Default level: <a href="../../rules#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#L398" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L399" target="_blank">View source</a>
</small>
@@ -529,7 +529,7 @@ def test(): -> "Literal[5]":
Default level: <a href="../../rules#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#L624" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L625" target="_blank">View source</a>
</small>
@@ -559,7 +559,7 @@ class C(A, B): ...
Default level: <a href="../../rules#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#L648" 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>
@@ -585,7 +585,7 @@ t[3] # IndexError: tuple index out of range
Default level: <a href="../../rules#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#L430" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L431" target="_blank">View source</a>
</small>
@@ -674,7 +674,7 @@ an atypical memory layout.
Default level: <a href="../../rules#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#L702" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L703" target="_blank">View source</a>
</small>
@@ -701,7 +701,7 @@ func("foo") # error: [invalid-argument-type]
Default level: <a href="../../rules#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#L742" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L743" target="_blank">View source</a>
</small>
@@ -729,7 +729,7 @@ a: int = ''
Default level: <a href="../../rules#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#L2079" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2122" target="_blank">View source</a>
</small>
@@ -763,7 +763,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable
Default level: <a href="../../rules#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#L764" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L765" target="_blank">View source</a>
</small>
@@ -799,7 +799,7 @@ asyncio.run(main())
Default level: <a href="../../rules#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#L794" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L795" target="_blank">View source</a>
</small>
@@ -823,7 +823,7 @@ class A(42): ... # error: [invalid-base]
Default level: <a href="../../rules#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#L879" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L880" target="_blank">View source</a>
</small>
@@ -850,7 +850,7 @@ with 1:
Default level: <a href="../../rules#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#L900" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L901" target="_blank">View source</a>
</small>
@@ -879,7 +879,7 @@ a: str
Default level: <a href="../../rules#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#L923" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L924" target="_blank">View source</a>
</small>
@@ -923,7 +923,7 @@ except ZeroDivisionError:
Default level: <a href="../../rules#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.28">0.0.1-alpha.28</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-explicit-override" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1749" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1792" target="_blank">View source</a>
</small>
@@ -965,7 +965,7 @@ class D(A):
Default level: <a href="../../rules#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.35">0.0.1-alpha.35</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-frozen-dataclass-subclass" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2330" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2373" target="_blank">View source</a>
</small>
@@ -1009,7 +1009,7 @@ class NonFrozenChild(FrozenBase): # Error raised here
Default level: <a href="../../rules#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#L959" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1002" target="_blank">View source</a>
</small>
@@ -1041,6 +1041,55 @@ class D(Generic[U, T]): ...
- [Typing spec: Generics](https://typing.python.org/en/latest/spec/generics.html#introduction)
## `invalid-generic-enum`
<small>
Default level: <a href="../../rules#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.12">0.0.12</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-generic-enum" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L960" target="_blank">View source</a>
</small>
**What it does**
Checks for enum classes that are also generic.
**Why is this bad?**
Enum classes cannot be generic. Python does not support generic enums:
attempting to create one will either result in an immediate `TypeError`
at runtime, or will create a class that cannot be specialized in the way
that a normal generic class can.
**Examples**
```python
from enum import Enum
from typing import Generic, TypeVar
T = TypeVar("T")
# error: enum class cannot be generic (class creation fails with `TypeError`)
class E[T](Enum):
A = 1
# error: enum class cannot be generic (class creation fails with `TypeError`)
class F(Enum, Generic[T]):
A = 1
# error: enum class cannot be generic -- the class creation does not immediately fail...
class G(Generic[T], Enum):
A = 1
# ...but this raises `KeyError`:
x: G[int]
```
**References**
- [Python documentation: Enum](https://docs.python.org/3/library/enum.html)
## `invalid-ignore-comment`
<small>
@@ -1077,7 +1126,7 @@ a = 20 / 0 # type: ignore
Default level: <a href="../../rules#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#L669" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L670" target="_blank">View source</a>
</small>
@@ -1116,7 +1165,7 @@ carol = Person(name="Carol", age=25) # typo!
Default level: <a href="../../rules#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#L990" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1033" target="_blank">View source</a>
</small>
@@ -1151,7 +1200,7 @@ def f(t: TypeVar("U")): ...
Default level: <a href="../../rules#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#L1087" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1130" target="_blank">View source</a>
</small>
@@ -1185,7 +1234,7 @@ class B(metaclass=f): ...
Default level: <a href="../../rules#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%20invalid-method-override" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2232" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2275" target="_blank">View source</a>
</small>
@@ -1292,7 +1341,7 @@ Correct use of `@override` is enforced by ty's `invalid-explicit-override` rule.
Default level: <a href="../../rules#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#L576" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L577" target="_blank">View source</a>
</small>
@@ -1346,7 +1395,7 @@ AttributeError: Cannot overwrite NamedTuple attribute _asdict
Default level: <a href="../../rules#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#L1063" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1106" target="_blank">View source</a>
</small>
@@ -1376,7 +1425,7 @@ Baz = NewType("Baz", int | str) # error: invalid base for `typing.NewType`
Default level: <a href="../../rules#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#L1114" target="_blank">View source</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>
</small>
@@ -1426,7 +1475,7 @@ def foo(x: int) -> int: ...
Default level: <a href="../../rules#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#L1213" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1256" target="_blank">View source</a>
</small>
@@ -1452,7 +1501,7 @@ def f(a: int = ''): ...
Default level: <a href="../../rules#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#L1018" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1061" target="_blank">View source</a>
</small>
@@ -1483,7 +1532,7 @@ P2 = ParamSpec("S2") # error: ParamSpec name must match the variable it's assig
Default level: <a href="../../rules#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#L512" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L513" target="_blank">View source</a>
</small>
@@ -1517,7 +1566,7 @@ TypeError: Protocols can only inherit from other protocols, got <class 'int'>
Default level: <a href="../../rules#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#L1233" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1276" target="_blank">View source</a>
</small>
@@ -1566,7 +1615,7 @@ def g():
Default level: <a href="../../rules#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#L723" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L724" target="_blank">View source</a>
</small>
@@ -1591,7 +1640,7 @@ def func() -> int:
Default level: <a href="../../rules#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#L1276" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1319" target="_blank">View source</a>
</small>
@@ -1687,7 +1736,7 @@ class C: ...
Default level: <a href="../../rules#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.10">0.0.10</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-total-ordering" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2368" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2411" target="_blank">View source</a>
</small>
@@ -1733,7 +1782,7 @@ class MyClass:
Default level: <a href="../../rules#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#L1042" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1085" target="_blank">View source</a>
</small>
@@ -1760,7 +1809,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus
Default level: <a href="../../rules#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.29">0.0.1-alpha.29</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-type-arguments" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1508" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1551" target="_blank">View source</a>
</small>
@@ -1807,7 +1856,7 @@ Bar[int] # error: too few arguments
Default level: <a href="../../rules#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#L1315" 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>
@@ -1837,7 +1886,7 @@ TYPE_CHECKING = ''
Default level: <a href="../../rules#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#L1339" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1382" target="_blank">View source</a>
</small>
@@ -1867,7 +1916,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments
Default level: <a href="../../rules#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#L1391" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1434" target="_blank">View source</a>
</small>
@@ -1901,7 +1950,7 @@ f(10) # Error
Default level: <a href="../../rules#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#L1363" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1406" target="_blank">View source</a>
</small>
@@ -1935,7 +1984,7 @@ class C:
Default level: <a href="../../rules#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#L1419" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1462" target="_blank">View source</a>
</small>
@@ -1970,7 +2019,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar
Default level: <a href="../../rules#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.9">0.0.9</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20invalid-typed-dict-statement" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2207" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2250" target="_blank">View source</a>
</small>
@@ -2001,7 +2050,7 @@ class Foo(TypedDict):
Default level: <a href="../../rules#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#L1448" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1491" target="_blank">View source</a>
</small>
@@ -2026,7 +2075,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x'
Default level: <a href="../../rules#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#L2180" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2223" target="_blank">View source</a>
</small>
@@ -2059,7 +2108,7 @@ alice["age"] # KeyError
Default level: <a href="../../rules#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#L1467" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1510" target="_blank">View source</a>
</small>
@@ -2088,7 +2137,7 @@ func("string") # error: [no-matching-overload]
Default level: <a href="../../rules#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#L1549" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1592" target="_blank">View source</a>
</small>
@@ -2114,7 +2163,7 @@ for i in 34: # TypeError: 'int' object is not iterable
Default level: <a href="../../rules#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-subscriptable" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1490" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1533" target="_blank">View source</a>
</small>
@@ -2138,7 +2187,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt
Default level: <a href="../../rules#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.29">0.0.1-alpha.29</a> ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20override-of-final-method" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1722" 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>
@@ -2171,7 +2220,7 @@ class B(A):
Default level: <a href="../../rules#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#L1600" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1643" target="_blank">View source</a>
</small>
@@ -2198,7 +2247,7 @@ f(1, x=2) # Error raised here
Default level: <a href="../../rules#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#L1933" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1976" target="_blank">View source</a>
</small>
@@ -2225,7 +2274,7 @@ f(x=1) # Error raised here
Default level: <a href="../../rules#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#L1621" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1664" target="_blank">View source</a>
</small>
@@ -2253,7 +2302,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c'
Default level: <a href="../../rules#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#L183" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L184" target="_blank">View source</a>
</small>
@@ -2285,7 +2334,7 @@ A()[0] # TypeError: 'A' object is not subscriptable
Default level: <a href="../../rules#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.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#L1643" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1686" target="_blank">View source</a>
</small>
@@ -2322,7 +2371,7 @@ from module import a # ImportError: cannot import name 'a' from 'module'
Default level: <a href="../../rules#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#L1673" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1716" target="_blank">View source</a>
</small>
@@ -2386,7 +2435,7 @@ def test(): -> "int":
Default level: <a href="../../rules#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#L2107" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2150" target="_blank">View source</a>
</small>
@@ -2413,7 +2462,7 @@ cast(int, f()) # Redundant
Default level: <a href="../../rules#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#L2055" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2098" target="_blank">View source</a>
</small>
@@ -2443,7 +2492,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr
Default level: <a href="../../rules#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#L1699" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1742" target="_blank">View source</a>
</small>
@@ -2472,7 +2521,7 @@ class B(A): ... # Error raised here
Default level: <a href="../../rules#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/0.0.1-alpha.30">0.0.1-alpha.30</a>) ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20super-call-in-named-tuple-method" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1867" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1910" target="_blank">View source</a>
</small>
@@ -2506,7 +2555,7 @@ class F(NamedTuple):
Default level: <a href="../../rules#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#L1807" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1850" target="_blank">View source</a>
</small>
@@ -2533,7 +2582,7 @@ f("foo") # Error raised here
Default level: <a href="../../rules#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#L1785" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1828" target="_blank">View source</a>
</small>
@@ -2561,7 +2610,7 @@ def _(x: int):
Default level: <a href="../../rules#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#L1828" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1871" target="_blank">View source</a>
</small>
@@ -2607,7 +2656,7 @@ class A:
Default level: <a href="../../rules#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#L1894" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1937" target="_blank">View source</a>
</small>
@@ -2631,7 +2680,7 @@ reveal_type(1) # NameError: name 'reveal_type' is not defined
Default level: <a href="../../rules#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#L1912" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1955" target="_blank">View source</a>
</small>
@@ -2658,7 +2707,7 @@ f(x=1, y=2) # Error raised here
Default level: <a href="../../rules#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#L1954" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1997" target="_blank">View source</a>
</small>
@@ -2686,7 +2735,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo'
Default level: <a href="../../rules#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#L2128" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2171" target="_blank">View source</a>
</small>
@@ -2744,7 +2793,7 @@ def g():
Default level: <a href="../../rules#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#L1976" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2019" target="_blank">View source</a>
</small>
@@ -2769,7 +2818,7 @@ import foo # ModuleNotFoundError: No module named 'foo'
Default level: <a href="../../rules#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#L1995" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2038" target="_blank">View source</a>
</small>
@@ -2794,7 +2843,7 @@ print(x) # NameError: name 'x' is not defined
Default level: <a href="../../rules#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#L812" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L813" target="_blank">View source</a>
</small>
@@ -2833,7 +2882,7 @@ class D(C): ... # error: [unsupported-base]
Default level: <a href="../../rules#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#L1569" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1612" target="_blank">View source</a>
</small>
@@ -2870,7 +2919,7 @@ b1 < b2 < b1 # exception raised here
Default level: <a href="../../rules#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/1.0.0">1.0.0</a>) ·
<a href="https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20unsupported-dynamic-base" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L845" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L846" target="_blank">View source</a>
</small>
@@ -2911,7 +2960,7 @@ def factory(base: type[Base]) -> type:
Default level: <a href="../../rules#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#L2014" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2057" target="_blank">View source</a>
</small>
@@ -2975,7 +3024,7 @@ to `false` to prevent this rule from reporting unused `type: ignore` comments.
Default level: <a href="../../rules#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#L1157" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L1200" target="_blank">View source</a>
</small>
@@ -3038,7 +3087,7 @@ def foo(x: int | str) -> int | str:
Default level: <a href="../../rules#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#L2036" target="_blank">View source</a>
<a href="https://github.com/astral-sh/ruff/blob/main/crates%2Fty_python_semantic%2Fsrc%2Ftypes%2Fdiagnostic.rs#L2079" target="_blank">View source</a>
</small>

View File

@@ -3586,7 +3586,7 @@ quux.<CURSOR>
__init_subclass__ :: bound method type[Quux].__init_subclass__() -> None
__module__ :: str
__ne__ :: bound method Quux.__ne__(value: object, /) -> bool
__new__ :: def __new__(cls) -> Self@__new__
__new__ :: def __new__[Self](cls) -> Self
__reduce__ :: bound method Quux.__reduce__() -> str | tuple[Any, ...]
__reduce_ex__ :: bound method Quux.__reduce_ex__(protocol: SupportsIndex, /) -> str | tuple[Any, ...]
__repr__ :: bound method Quux.__repr__() -> str
@@ -3667,19 +3667,19 @@ C.<CURSOR>
__mro__ :: tuple[type, ...]
__name__ :: str
__ne__ :: def __ne__(self, value: object, /) -> bool
__new__ :: def __new__(cls) -> Self@__new__
__or__ :: bound method <class 'C'>.__or__[Self](value: Any, /) -> UnionType | Self@__or__
__new__ :: def __new__[Self](cls) -> Self
__or__ :: bound method <class 'C'>.__or__[Self](value: Any, /) -> UnionType | Self
__prepare__ :: bound method <class 'Meta'>.__prepare__(name: str, bases: tuple[type, ...], /, **kwds: Any) -> MutableMapping[str, object]
__qualname__ :: str
__reduce__ :: def __reduce__(self) -> str | tuple[Any, ...]
__reduce_ex__ :: def __reduce_ex__(self, protocol: SupportsIndex, /) -> str | tuple[Any, ...]
__repr__ :: def __repr__(self) -> str
__ror__ :: bound method <class 'C'>.__ror__[Self](value: Any, /) -> UnionType | Self@__ror__
__ror__ :: bound method <class 'C'>.__ror__[Self](value: Any, /) -> UnionType | Self
__setattr__ :: def __setattr__(self, name: str, value: Any, /) -> None
__sizeof__ :: def __sizeof__(self) -> int
__str__ :: def __str__(self) -> str
__subclasscheck__ :: bound method <class 'C'>.__subclasscheck__(subclass: type, /) -> bool
__subclasses__ :: bound method <class 'C'>.__subclasses__[Self]() -> list[Self@__subclasses__]
__subclasses__ :: bound method <class 'C'>.__subclasses__[Self]() -> list[Self]
__subclasshook__ :: bound method <class 'C'>.__subclasshook__(subclass: type, /) -> bool
__text_signature__ :: str | None
__type_params__ :: tuple[TypeVar | ParamSpec | TypeVarTuple, ...]
@@ -3737,18 +3737,18 @@ Meta.<CURSOR>
__mro__ :: tuple[type, ...]
__name__ :: str
__ne__ :: def __ne__(self, value: object, /) -> bool
__or__ :: def __or__[Self](self: Self@__or__, value: Any, /) -> UnionType | Self@__or__
__or__ :: def __or__[Self](self: Self, value: Any, /) -> UnionType | Self
__prepare__ :: bound method <class 'Meta'>.__prepare__(name: str, bases: tuple[type, ...], /, **kwds: Any) -> MutableMapping[str, object]
__qualname__ :: str
__reduce__ :: def __reduce__(self) -> str | tuple[Any, ...]
__reduce_ex__ :: def __reduce_ex__(self, protocol: SupportsIndex, /) -> str | tuple[Any, ...]
__repr__ :: def __repr__(self) -> str
__ror__ :: def __ror__[Self](self: Self@__ror__, value: Any, /) -> UnionType | Self@__ror__
__ror__ :: def __ror__[Self](self: Self, value: Any, /) -> UnionType | Self
__setattr__ :: def __setattr__(self, name: str, value: Any, /) -> None
__sizeof__ :: def __sizeof__(self) -> int
__str__ :: def __str__(self) -> str
__subclasscheck__ :: def __subclasscheck__(self, subclass: type, /) -> bool
__subclasses__ :: def __subclasses__[Self](self: Self@__subclasses__) -> list[Self@__subclasses__]
__subclasses__ :: def __subclasses__[Self](self: Self) -> list[Self]
__subclasshook__ :: bound method <class 'Meta'>.__subclasshook__(subclass: type, /) -> bool
__text_signature__ :: str | None
__type_params__ :: tuple[TypeVar | ParamSpec | TypeVarTuple, ...]
@@ -3866,19 +3866,19 @@ Quux.<CURSOR>
__mro__ :: tuple[type, ...]
__name__ :: str
__ne__ :: def __ne__(self, value: object, /) -> bool
__new__ :: def __new__(cls) -> Self@__new__
__or__ :: bound method <class 'Quux'>.__or__[Self](value: Any, /) -> UnionType | Self@__or__
__new__ :: def __new__[Self](cls) -> Self
__or__ :: bound method <class 'Quux'>.__or__[Self](value: Any, /) -> UnionType | Self
__prepare__ :: bound method <class 'type'>.__prepare__(name: str, bases: tuple[type, ...], /, **kwds: Any) -> MutableMapping[str, object]
__qualname__ :: str
__reduce__ :: def __reduce__(self) -> str | tuple[Any, ...]
__reduce_ex__ :: def __reduce_ex__(self, protocol: SupportsIndex, /) -> str | tuple[Any, ...]
__repr__ :: def __repr__(self) -> str
__ror__ :: bound method <class 'Quux'>.__ror__[Self](value: Any, /) -> UnionType | Self@__ror__
__ror__ :: bound method <class 'Quux'>.__ror__[Self](value: Any, /) -> UnionType | Self
__setattr__ :: def __setattr__(self, name: str, value: Any, /) -> None
__sizeof__ :: def __sizeof__(self) -> int
__str__ :: def __str__(self) -> str
__subclasscheck__ :: bound method <class 'Quux'>.__subclasscheck__(subclass: type, /) -> bool
__subclasses__ :: bound method <class 'Quux'>.__subclasses__[Self]() -> list[Self@__subclasses__]
__subclasses__ :: bound method <class 'Quux'>.__subclasses__[Self]() -> list[Self]
__subclasshook__ :: bound method <class 'Quux'>.__subclasshook__(subclass: type, /) -> bool
__text_signature__ :: str | None
__type_params__ :: tuple[TypeVar | ParamSpec | TypeVarTuple, ...]
@@ -3919,8 +3919,8 @@ Answer.<CURSOR>
__bool__ :: bound method <class 'Answer'>.__bool__() -> Literal[True]
__class__ :: <class 'EnumMeta'>
__contains__ :: bound method <class 'Answer'>.__contains__(value: object) -> bool
__copy__ :: def __copy__(self) -> Self@__copy__
__deepcopy__ :: def __deepcopy__(self, memo: Any) -> Self@__deepcopy__
__copy__ :: def __copy__[Self](self) -> Self
__deepcopy__ :: def __deepcopy__[Self](self, memo: Any) -> Self
__delattr__ :: def __delattr__(self, name: str, /) -> None
__dict__ :: dict[str, Any]
__dictoffset__ :: int
@@ -3930,34 +3930,34 @@ Answer.<CURSOR>
__flags__ :: int
__format__ :: def __format__(self, format_spec: str) -> str
__getattribute__ :: def __getattribute__(self, name: str, /) -> Any
__getitem__ :: bound method <class 'Answer'>.__getitem__[_EnumMemberT](name: str) -> _EnumMemberT@__getitem__
__getitem__ :: bound method <class 'Answer'>.__getitem__[_EnumMemberT](name: str) -> _EnumMemberT
__getstate__ :: def __getstate__(self) -> object
__hash__ :: def __hash__(self) -> int
__init__ :: def __init__(self) -> None
__init_subclass__ :: bound method <class 'Answer'>.__init_subclass__() -> None
__instancecheck__ :: bound method <class 'Answer'>.__instancecheck__(instance: Any, /) -> bool
__itemsize__ :: int
__iter__ :: bound method <class 'Answer'>.__iter__[_EnumMemberT]() -> Iterator[_EnumMemberT@__iter__]
__iter__ :: bound method <class 'Answer'>.__iter__[_EnumMemberT]() -> Iterator[_EnumMemberT]
__len__ :: bound method <class 'Answer'>.__len__() -> int
__members__ :: MappingProxyType[str, Answer]
__module__ :: str
__mro__ :: tuple[type, ...]
__name__ :: str
__ne__ :: def __ne__(self, value: object, /) -> bool
__new__ :: def __new__(cls, value: object) -> Self@__new__
__or__ :: bound method <class 'Answer'>.__or__[Self](value: Any, /) -> UnionType | Self@__or__
__new__ :: def __new__[Self](cls, value: object) -> Self
__or__ :: bound method <class 'Answer'>.__or__[Self](value: Any, /) -> UnionType | Self
__order__ :: str
__prepare__ :: bound method <class 'EnumMeta'>.__prepare__(cls: str, bases: tuple[type, ...], **kwds: Any) -> _EnumDict
__qualname__ :: str
__reduce__ :: def __reduce__(self) -> str | tuple[Any, ...]
__repr__ :: def __repr__(self) -> str
__reversed__ :: bound method <class 'Answer'>.__reversed__[_EnumMemberT]() -> Iterator[_EnumMemberT@__reversed__]
__ror__ :: bound method <class 'Answer'>.__ror__[Self](value: Any, /) -> UnionType | Self@__ror__
__reversed__ :: bound method <class 'Answer'>.__reversed__[_EnumMemberT]() -> Iterator[_EnumMemberT]
__ror__ :: bound method <class 'Answer'>.__ror__[Self](value: Any, /) -> UnionType | Self
__setattr__ :: def __setattr__(self, name: str, value: Any, /) -> None
__sizeof__ :: def __sizeof__(self) -> int
__str__ :: def __str__(self) -> str
__subclasscheck__ :: bound method <class 'Answer'>.__subclasscheck__(subclass: type, /) -> bool
__subclasses__ :: bound method <class 'Answer'>.__subclasses__[Self]() -> list[Self@__subclasses__]
__subclasses__ :: bound method <class 'Answer'>.__subclasses__[Self]() -> list[Self]
__subclasshook__ :: bound method <class 'Answer'>.__subclasshook__(subclass: type, /) -> bool
__text_signature__ :: str | None
__type_params__ :: tuple[TypeVar | ParamSpec | TypeVarTuple, ...]
@@ -3999,7 +3999,7 @@ quux.<CURSOR>
index :: bound method Quux.index(value: Any, start: SupportsIndex = 0, stop: SupportsIndex = ..., /) -> int
x :: int
y :: str
__add__ :: Overload[(value: tuple[int | str, ...], /) -> tuple[int | str, ...], (value: tuple[_T@__add__, ...], /) -> tuple[int | str | _T@__add__, ...]]
__add__ :: Overload[(value: tuple[int | str, ...], /) -> tuple[int | str, ...], [_T](value: tuple[_T, ...], /) -> tuple[int | str | _T, ...]]
__annotations__ :: dict[str, Any]
__class__ :: type[Quux]
__class_getitem__ :: bound method type[Quux].__class_getitem__(item: Any, /) -> GenericAlias

View File

@@ -9,15 +9,19 @@
```py
from typing_extensions import Annotated
def _(x: Annotated[int, "foo"]):
reveal_type(x) # revealed: int
def _(x: Annotated[int, lambda: 0 + 1 * 2 // 3, _(4)]):
reveal_type(x) # revealed: int
def _(x: Annotated[int, "arbitrary", "metadata", "elements", "are", "fine"]):
reveal_type(x) # revealed: int
def _(x: Annotated[tuple[str, int], bytes]):
reveal_type(x) # revealed: tuple[str, int]
```
@@ -29,10 +33,12 @@ It is invalid to parameterize `Annotated` with less than two arguments.
```py
from typing_extensions import Annotated
# error: [invalid-type-form] "`typing.Annotated` requires at least two arguments when used in a type expression"
def _(x: Annotated):
reveal_type(x) # revealed: Unknown
def _(flag: bool):
if flag:
X = Annotated
@@ -43,14 +49,17 @@ def _(flag: bool):
def f(y: X):
reveal_type(y) # revealed: Unknown | bool
# error: [invalid-type-form] "`typing.Annotated` requires at least two arguments when used in a type expression"
def _(x: Annotated | bool):
reveal_type(x) # revealed: Unknown | bool
# error: [invalid-type-form] "Special form `typing.Annotated` expected at least 2 arguments (one type and at least one metadata element)"
def _(x: Annotated[()]):
reveal_type(x) # revealed: Unknown
# error: [invalid-type-form]
def _(x: Annotated[int]):
# `Annotated[T]` is invalid and will raise an error at runtime,
@@ -59,6 +68,7 @@ def _(x: Annotated[int]):
# Same for the `(int,)` form below.
reveal_type(x) # revealed: int
# error: [invalid-type-form]
def _(x: Annotated[(int,)]):
reveal_type(x) # revealed: int
@@ -74,18 +84,24 @@ Inheriting from `Annotated[T, ...]` is equivalent to inheriting from `T` itself.
from typing_extensions import Annotated
from ty_extensions import reveal_mro
class C(Annotated[int, "foo"]): ...
# revealed: (<class 'C'>, <class 'int'>, <class 'object'>)
reveal_mro(C)
class D(Annotated[list[str], "foo"]): ...
# revealed: (<class 'D'>, <class 'list[str]'>, <class 'MutableSequence[str]'>, <class 'Sequence[str]'>, <class 'Reversible[str]'>, <class 'Collection[str]'>, <class 'Iterable[str]'>, <class 'Container[str]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(D)
class E(Annotated[list["E"], "metadata"]): ...
# error: [revealed-type] "Revealed MRO: (<class 'E'>, <class 'list[E]'>, <class 'MutableSequence[E]'>, <class 'Sequence[E]'>, <class 'Reversible[E]'>, <class 'Collection[E]'>, <class 'Iterable[E]'>, <class 'Container[E]'>, typing.Protocol, typing.Generic, <class 'object'>)"
reveal_mro(E)
```
@@ -96,9 +112,11 @@ reveal_mro(E)
from typing_extensions import Annotated
from ty_extensions import reveal_mro
# At runtime, this is an error.
# error: [invalid-base]
class C(Annotated): ...
reveal_mro(C) # revealed: (<class 'C'>, Unknown, <class 'object'>)
```

View File

@@ -10,6 +10,7 @@ from typing import Any
x: Any = 1
x = "foo"
def f():
reveal_type(x) # revealed: Any
```
@@ -24,6 +25,7 @@ from typing import Any as RenamedAny
x: RenamedAny = 1
x = "foo"
def f():
reveal_type(x) # revealed: Any
```
@@ -36,11 +38,14 @@ isn't a spelling of the Any type.
```py
class Any: ...
x: Any
def f():
reveal_type(x) # revealed: Any
# This verifies that we're not accidentally seeing typing.Any, since str is assignable
# to that but not to our locally defined class.
y: Any = "not an Any" # error: [invalid-assignment]
@@ -58,8 +63,10 @@ allowed, even when the unknown superclass is `int`. The assignment to `y` should
from typing import Any
from ty_extensions import reveal_mro
class SubclassOfAny(Any): ...
reveal_mro(SubclassOfAny) # revealed: (<class 'SubclassOfAny'>, Any, <class 'object'>)
x: SubclassOfAny = 1 # error: [invalid-assignment]
@@ -72,14 +79,18 @@ possibly be a subclass of `FinalClass`:
```py
from typing import final
@final
class FinalClass: ...
f: FinalClass = SubclassOfAny() # error: [invalid-assignment]
@final
class OtherFinalClass: ...
f: FinalClass | OtherFinalClass = SubclassOfAny() # error: [invalid-assignment]
```
@@ -88,33 +99,44 @@ A subclass of `Any` can also be assigned to arbitrary `Callable` and `Protocol`
```py
from typing import Callable, Any, Protocol
def takes_callable1(f: Callable):
f()
takes_callable1(SubclassOfAny())
def takes_callable2(f: Callable[[int], None]):
f(1)
takes_callable2(SubclassOfAny())
class CallbackProtocol(Protocol):
def __call__(self, x: int, /) -> None: ...
def takes_callback_proto(f: CallbackProtocol):
f(1)
takes_callback_proto(SubclassOfAny())
class OtherProtocol(Protocol):
x: int
@property
def foo(self) -> bytes: ...
@foo.setter
def foo(self, x: str) -> None: ...
def takes_other_protocol(f: OtherProtocol): ...
takes_other_protocol(SubclassOfAny())
```
@@ -123,9 +145,11 @@ A subclass of `Any` cannot be assigned to literal types, since those cannot be s
```py
from typing import Any, Literal
class MockAny(Any):
pass
x: Literal[1] = MockAny() # error: [invalid-assignment]
```
@@ -161,6 +185,7 @@ static_assert(is_assignable_to(TypeOf[Any], type))
```py
from typing import Any
# error: [invalid-type-form] "Special form `typing.Any` expected no type parameter"
def f(x: Any[int]):
reveal_type(x) # revealed: Unknown

View File

@@ -22,6 +22,7 @@ A bare `Callable` without any type arguments:
```py
from typing import Callable
def _(c: Callable):
reveal_type(c) # revealed: (...) -> Unknown
```
@@ -33,6 +34,7 @@ When it's not a list:
```py
from typing import Callable
# error: [invalid-type-form] "The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`"
def _(c: Callable[int, str]):
reveal_type(c) # revealed: (...) -> Unknown
@@ -63,6 +65,7 @@ Using a parameter list:
```py
from typing import Callable
# error: [invalid-type-form] "Special form `typing.Callable` expected exactly two arguments (parameter types and return type)"
def _(c: Callable[[int, str]]):
reveal_type(c) # revealed: (...) -> Unknown
@@ -111,6 +114,7 @@ which argument corresponds to either the parameters or the return type.
```py
from typing import Callable
# error: [invalid-type-form] "Special form `typing.Callable` expected exactly two arguments (parameter types and return type)"
def _(c: Callable[[int], str, str]):
reveal_type(c) # revealed: (...) -> Unknown
@@ -151,6 +155,7 @@ def _(c: Callable[
```py
from typing import Callable
# error: [invalid-type-form] "List literals are not allowed in this context in a type expression"
def _(c: Callable[[int], [str]]):
reveal_type(c) # revealed: (int, /) -> Unknown
@@ -180,6 +185,7 @@ A simple `Callable` with multiple parameters and a return type:
```py
from typing import Callable
def _(c: Callable[[int, str], int]):
reveal_type(c) # revealed: (int, str, /) -> int
```
@@ -189,6 +195,7 @@ def _(c: Callable[[int, str], int]):
```py
from typing import Callable, Union
def _(
c: Callable[[Union[int, str]], int] | None,
d: None | Callable[[Union[int, str]], int],
@@ -205,8 +212,10 @@ def _(
from typing import Callable, Union
from ty_extensions import Intersection, Not
class Foo: ...
def _(
c: Intersection[Callable[[Union[int, str]], int], int],
d: Intersection[int, Callable[[Union[int, str]], int]],
@@ -226,6 +235,7 @@ A nested `Callable` as one of the parameter types:
```py
from typing import Callable
def _(c: Callable[[Callable[[int], str]], int]):
reveal_type(c) # revealed: ((int, /) -> str, /) -> int
```
@@ -245,6 +255,7 @@ is a [gradual form] indicating that the type is consistent with any input signat
```py
from typing import Callable
def gradual_form(c: Callable[..., str]):
reveal_type(c) # revealed: (...) -> str
```
@@ -256,6 +267,7 @@ Using `Concatenate` as the first argument to `Callable`:
```py
from typing_extensions import Callable, Concatenate
def _(c: Callable[Concatenate[int, str, ...], int]):
# TODO: Should reveal the correct signature
reveal_type(c) # revealed: (...) -> int
@@ -306,6 +318,7 @@ Using a `ParamSpec` in a `Callable` annotation:
```py
from typing_extensions import Callable
def _[**P1](c: Callable[P1, int]):
reveal_type(P1.args) # revealed: P1@_.args
reveal_type(P1.kwargs) # revealed: P1@_.kwargs
@@ -320,6 +333,7 @@ from typing_extensions import ParamSpec
P2 = ParamSpec("P2")
def _(c: Callable[P2, int]):
reveal_type(c) # revealed: (**P2@_) -> int
```
@@ -333,6 +347,7 @@ from typing_extensions import Callable, TypeVarTuple
Ts = TypeVarTuple("Ts")
def _(c: Callable[[int, *Ts], int]):
# TODO: Should reveal the correct signature
reveal_type(c) # revealed: (...) -> int
@@ -343,6 +358,7 @@ And, using the legacy syntax using `Unpack`:
```py
from typing_extensions import Unpack
def _(c: Callable[[int, Unpack[Ts]], int]):
# TODO: Should reveal the correct signature
reveal_type(c) # revealed: (...) -> int
@@ -353,6 +369,7 @@ def _(c: Callable[[int, Unpack[Ts]], int]):
```py
from typing import Callable
def _(c: Callable[[int], int]):
reveal_type(c.__init__) # revealed: bound method object.__init__() -> None
reveal_type(c.__class__) # revealed: type
@@ -379,6 +396,7 @@ class MyCallable:
def __call__(self) -> None:
pass
f_wrong(MyCallable()) # raises `AttributeError` at runtime
```
@@ -388,6 +406,7 @@ of the attribute first:
```py
from inspect import getattr_static
def f_okay(c: Callable[[], None]):
if hasattr(c, "__qualname__"):
reveal_type(c.__qualname__) # revealed: object
@@ -414,13 +433,16 @@ def f_okay(c: Callable[[], None]):
```py
from ty_extensions import into_callable
class Base:
def __init__(self) -> None:
pass
class A(Base):
pass
# revealed: () -> A
reveal_type(into_callable(A))
```

View File

@@ -23,11 +23,14 @@ In (regular) source files, annotations are *not* deferred. This also tests that
```py
from __future__ import with_statement as annotations
# error: [unresolved-reference]
def get_foo() -> Foo: ...
class Foo: ...
reveal_type(get_foo()) # revealed: Unknown
```
@@ -38,11 +41,14 @@ If `__future__.annotations` is imported, annotations *are* deferred.
```py
from __future__ import annotations
def get_foo() -> Foo:
return Foo()
class Foo: ...
reveal_type(get_foo()) # revealed: Foo
```
@@ -56,6 +62,7 @@ python-version = "3.12"
```py
from __future__ import annotations
class Foo:
this: Foo
# error: [unresolved-reference]
@@ -77,9 +84,11 @@ class Foo:
def f(self, x: Foo):
return self
# error: [unresolved-reference]
def g(self) -> Bar:
return self
# error: [unresolved-reference]
def h[T: Bar](self):
pass
@@ -108,6 +117,7 @@ class Foo:
def h[T: Bar]():
# error: [unresolved-reference]
return Bar()
type Baz = Foo
```
@@ -132,6 +142,7 @@ class Foo:
# error: [unresolved-reference]
def f(self, x: Foo):
reveal_type(x) # revealed: Unknown
# error: [unresolved-reference]
def g(self) -> Foo:
_: Foo = self
@@ -145,9 +156,11 @@ class Foo:
# error: [unresolved-reference]
def f(self, x: Foo):
return self
# error: [unresolved-reference]
def g(self) -> Bar:
return self
# error: [unresolved-reference]
def h[T: Bar](self):
pass
@@ -176,8 +189,10 @@ class Foo:
def h[T: Bar]():
# error: [unresolved-reference]
return Bar()
type Qux = Foo
def _():
class C:
# error: [unresolved-reference]
@@ -192,9 +207,11 @@ def _():
```py
from __future__ import annotations
class A(B): # error: [unresolved-reference]
pass
class B:
pass
```
@@ -215,6 +232,7 @@ class B: ...
def f(mode: int = ParseMode.test):
pass
class ParseMode:
test = 1
```
@@ -246,6 +264,7 @@ def f(mode: int = NeverDefined.test): ...
class Foo(metaclass=SomeMeta):
pass
class SomeMeta(type):
pass
```
@@ -275,6 +294,7 @@ class Foo(metaclass=NeverDefined): ...
# error: [unresolved-reference]
f = lambda x=Foo(): x
class Foo:
pass
```

View File

@@ -11,6 +11,7 @@ Numbers = list[int]
# this as `list[int]` is more helpful, though:
reveal_type(Numbers) # revealed: <class 'list[int]'>
def _(numbers: Numbers) -> None:
reveal_type(numbers) # revealed: list[int]
```

View File

@@ -12,6 +12,7 @@ An annotation of `float` means `int | float`, so `int` is assignable to it:
def takes_float(x: float):
pass
def passes_int_to_float(x: int):
# no error!
takes_float(x)
@@ -31,10 +32,12 @@ It doesn't work the other way around:
def takes_int(x: int):
pass
def passes_float_to_int(x: float):
# error: [invalid-argument-type]
takes_int(x)
def assigns_float_to_int(x: float):
# error: [invalid-assignment]
y: int = x
@@ -57,34 +60,41 @@ to it (but not the other way around):
def takes_complex(x: complex):
pass
def passes_to_complex(x: float, y: int):
# no errors!
takes_complex(x)
takes_complex(y)
def assigns_to_complex(x: float, y: int):
# no errors!
a: complex = x
b: complex = y
def takes_int(x: int):
pass
def takes_float(x: float):
pass
def passes_complex(x: complex):
# error: [invalid-argument-type]
takes_int(x)
# error: [invalid-argument-type]
takes_float(x)
def assigns_complex(x: complex):
# error: [invalid-assignment]
y: int = x
# error: [invalid-assignment]
z: float = x
def f(x: complex):
reveal_type(x) # revealed: int | float | complex
```
@@ -98,6 +108,7 @@ be narrowed to `int` or `float`:
from typing_extensions import assert_type
from ty_extensions import JustFloat
def f(x: complex):
reveal_type(x) # revealed: int | float | complex

View File

@@ -9,8 +9,10 @@ import typing
from ty_extensions import AlwaysTruthy, AlwaysFalsy
from typing_extensions import Literal, Never
class A: ...
def _(
a: type[int],
b: AlwaysTruthy,
@@ -49,6 +51,7 @@ def _(
reveal_type(i_) # revealed: Unknown
reveal_type(j_) # revealed: Unknown
# Inspired by the conformance test suite at
# https://github.com/python/typing/blob/d4f39b27a4a47aac8b6d4019e1b0b5b3156fabdc/conformance/tests/aliases_implicit.py#L88-L122
B = [x for x in range(42)]
@@ -56,6 +59,7 @@ C = {x for x in range(42)}
D = {x: y for x, y in enumerate(range(42))}
E = (x for x in range(42))
def _(
b: B, # error: [invalid-type-form]
c: C, # error: [invalid-type-form]
@@ -74,11 +78,13 @@ def _(
def bar() -> None:
return None
def outer_sync(): # `yield` from is only valid syntax inside a synchronous function
def _(
a: (yield from [1]), # error: [invalid-type-form] "`yield from` expressions are not allowed in type expressions"
): ...
async def baz(): ...
async def outer_async(): # avoid unrelated syntax errors on `yield` and `await`
def _(
@@ -120,6 +126,7 @@ async def outer_async(): # avoid unrelated syntax errors on `yield` and `await`
reveal_type(p) # revealed: int | Unknown
reveal_type(q) # revealed: Unknown
class Mat:
def __init__(self, value: int):
self.value = value
@@ -127,6 +134,7 @@ class Mat:
def __matmul__(self, other) -> int:
return 42
def invalid_binary_operators(
a: "1" + "2", # error: [invalid-type-form] "Invalid binary operator `+` in type annotation"
b: 3 - 5.0, # error: [invalid-type-form] "Invalid binary operator `-` in type annotation"
@@ -186,10 +194,12 @@ def _(
reveal_type(h) # revealed: Unknown
reveal_type(i) # revealed: Unknown
# error: [invalid-type-form] "List literals are not allowed in this context in a type expression: Did you mean `list[int]`?"
class name_0[name_2: [int]]:
pass
# error: [invalid-type-form] "List literals are not allowed in this context in a type expression"
# error: [invalid-type-form] "Dict literals are not allowed in type expressions"
class name_4[name_1: [{}]]:
@@ -211,6 +221,7 @@ for this case:
```py
import datetime
def f(x: datetime): ... # error: [invalid-type-form]
```
@@ -225,6 +236,7 @@ class Image: ...
```py
from PIL import Image
def g(x: Image): ... # error: [invalid-type-form]
```

View File

@@ -18,16 +18,19 @@ a6: Literal[True]
a7: Literal[None]
a8: Literal[Literal[1]]
class Color(Enum):
RED = 0
GREEN = 1
BLUE = 2
b1: Literal[Color.RED]
MissingT = Enum("MissingT", {"MISSING": "MISSING"})
b2: Literal[MissingT.MISSING]
def f():
reveal_type(mode) # revealed: Literal["w", "r"]
reveal_type(a1) # revealed: Literal[26]
@@ -42,6 +45,7 @@ def f():
# TODO should be `Literal[MissingT.MISSING]`
reveal_type(b2) # revealed: @Todo(functional `Enum` syntax)
# error: [invalid-type-form]
invalid1: Literal[3 + 4]
# error: [invalid-type-form]
@@ -57,9 +61,11 @@ invalid4: Literal[
(1, 2, 3), # error: [invalid-type-form]
]
class NotAnEnum:
x: int = 1
# error: [invalid-type-form]
invalid5: Literal[NotAnEnum.x]
@@ -86,10 +92,12 @@ from enum import Enum
import mod
class E(Enum):
A = 1
B = 2
type SingleInt = Literal[1]
type SingleStr = Literal["foo"]
type SingleBytes = Literal[b"bar"]
@@ -105,6 +113,7 @@ type AnEnum2 = Literal[E.A, E.B]
type Bool1 = bool
type Bool2 = Literal[True, False]
def _(
single_int: Literal[SingleInt],
single_str: Literal[SingleStr],
@@ -149,10 +158,12 @@ type SingleInt = Literal[2]
from typing import Literal, TypeAlias
from enum import Enum
class E(Enum):
A = 1
B = 2
SingleInt: TypeAlias = Literal[1]
SingleStr: TypeAlias = Literal["foo"]
SingleBytes: TypeAlias = Literal[b"bar"]
@@ -165,6 +176,7 @@ AnEnum2: TypeAlias = Literal[E.A, E.B]
Bool1: TypeAlias = bool
Bool2: TypeAlias = Literal[True, False]
def _(
single_int: Literal[SingleInt],
single_str: Literal[SingleStr],
@@ -203,10 +215,12 @@ def _(
from typing import Literal
from enum import Enum
class E(Enum):
A = 1
B = 2
SingleInt = Literal[1]
SingleStr = Literal["foo"]
SingleBytes = Literal[b"bar"]
@@ -222,6 +236,7 @@ AnEnum2 = Literal[E.A, E.B]
Bool1 = bool
Bool2 = Literal[True, False]
def _(
single_int: Literal[SingleInt],
single_str: Literal[SingleStr],
@@ -258,6 +273,7 @@ the union of those types.
```py
from typing import Literal
def x(
a1: Literal[Literal[Literal[1, 2, 3], "foo"], 5, None],
a2: Literal["w"] | Literal["r"],
@@ -275,15 +291,21 @@ def x(
```py
from typing import Literal, Union
def foo(x: int) -> int:
return x + 1
def bar(s: str) -> str:
return s
class A: ...
class B: ...
def union_example(
x: Union[
# unknown type
@@ -333,6 +355,7 @@ from other import Literal
# error: [invalid-type-form] "Invalid subscript of object of type `_SpecialForm` in type expression"
a1: Literal[26]
def f():
reveal_type(a1) # revealed: Unknown
```
@@ -344,6 +367,7 @@ from typing_extensions import Literal
a1: Literal[26]
def f():
reveal_type(a1) # revealed: Literal[26]
```
@@ -353,6 +377,7 @@ def f():
```py
from typing import Literal
# error: [invalid-type-form] "`typing.Literal` requires at least one argument when used in a type expression"
def _(x: Literal):
reveal_type(x) # revealed: Unknown

View File

@@ -16,6 +16,7 @@ from typing_extensions import LiteralString
x: LiteralString
def f():
reveal_type(x) # revealed: LiteralString
```
@@ -54,6 +55,7 @@ Subclassing `LiteralString` leads to a runtime error.
```py
from typing_extensions import LiteralString
class C(LiteralString): ... # error: [invalid-base]
```
@@ -92,6 +94,7 @@ vice versa.
```py
from typing_extensions import Literal, LiteralString
def _(flag: bool):
foo_1: Literal["foo"] = "foo"
bar_1: LiteralString = foo_1 # fine
@@ -146,6 +149,7 @@ from typing import LiteralString
x: LiteralString = "foo"
def f():
reveal_type(x) # revealed: LiteralString
```

View File

@@ -9,9 +9,11 @@ interchangeably.
```py
from typing import NoReturn
def stop() -> NoReturn:
raise RuntimeError("no way")
# revealed: Never
reveal_type(stop())
```
@@ -28,6 +30,7 @@ a2: Never
b1: Any
b2: int
def f():
# revealed: Never
reveal_type(a1)

View File

@@ -9,6 +9,7 @@ from typing_extensions import NewType
UserId = NewType("UserId", int)
def _(user_id: UserId):
reveal_type(user_id) # revealed: UserId
```
@@ -38,10 +39,12 @@ 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)))
@@ -60,11 +63,14 @@ h(Bar(Foo(42)))
```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()))
@@ -88,16 +94,21 @@ 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]
```
@@ -112,19 +123,23 @@ 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
```
@@ -134,6 +149,7 @@ def g(x: CallableTypeOf[j]):
```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"
```
@@ -205,6 +221,7 @@ class Bar:
def __contains__(self, key: Foo) -> bool:
return True
reveal_type(Foo(42) + Bar()) # revealed: Foo
reveal_type(Bar() + Foo(42)) # revealed: Foo
reveal_type(Foo(42) < Bar()) # revealed: bool
@@ -279,12 +296,16 @@ type:
```py
from collections.abc import Callable
def f(_: Callable[[int | float], Foo]): ...
f(Foo)
def g(_: Callable[[int | float | complex], Bar]): ...
g(Bar)
```
@@ -321,6 +342,7 @@ class Bing:
def __contains__(self, key: Foo) -> bool:
return True
reveal_type(Foo(3.14) + Bing()) # revealed: Foo
reveal_type(Bing() + Foo(42)) # revealed: Foo
reveal_type(Foo(3.14) < Bing()) # revealed: bool
@@ -439,6 +461,7 @@ from typing import NewType
N = NewType("N", int)
def f(x: N):
reveal_type(isinstance(x, int)) # revealed: Literal[True]
```
@@ -470,6 +493,7 @@ from typing import NewType
X = NewType("X", int)
class Foo(X): ... # error: [invalid-base]
```
@@ -481,12 +505,15 @@ class Foo(X): ... # error: [invalid-base]
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:
@@ -530,14 +557,18 @@ reveal_type(Bar(42)) # revealed: Bar
```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]
```
@@ -578,11 +609,15 @@ from stub import N, A
n = N(A()) # fine
def f(x: A): ...
f(n) # fine
class Invalid: ...
bad = N(Invalid()) # error: [invalid-argument-type]
```

View File

@@ -12,6 +12,7 @@ a1: Optional[bool]
a2: Optional[Optional[bool]]
a3: Optional[None]
def f():
# revealed: int | None
reveal_type(a)
@@ -41,6 +42,7 @@ from typing_extensions import Optional
a: Optional[int]
def f():
# revealed: int | None
reveal_type(a)
@@ -51,6 +53,7 @@ def f():
```py
from typing import Optional
# error: [invalid-type-form] "`typing.Optional` requires exactly one argument when used in a type expression"
def f(x: Optional) -> None:
reveal_type(x) # revealed: Unknown

View File

@@ -14,6 +14,7 @@ python-version = "3.13"
```py
from typing import Self
class Shape:
def set_scale(self: Self, scale: float) -> Self:
reveal_type(self) # revealed: Self@set_scale
@@ -26,21 +27,26 @@ class Shape:
def inner() -> Self:
reveal_type(self) # revealed: Self@nested_func
return self
return inner()
def nested_func_without_enclosing_binding(self):
def inner(x: Self):
reveal_type(x) # revealed: Self@nested_func_without_enclosing_binding
inner(self)
reveal_type(Shape().nested_type()) # revealed: list[Shape]
reveal_type(Shape().nested_func()) # revealed: Shape
class Circle(Shape):
def set_scale(self: Self, scale: float) -> Self:
reveal_type(self) # revealed: Self@set_scale
return self
class Outer:
class Inner:
def foo(self: Self) -> Self:
@@ -62,6 +68,7 @@ python-version = "3.12"
```py
from typing import Self
class A:
def __init__(self):
reveal_type(self) # revealed: Self@__init__
@@ -103,6 +110,7 @@ class A:
@staticmethod
def a_staticmethod(x: int): ...
a = A()
reveal_type(a.implicit_self()) # revealed: A
@@ -123,9 +131,11 @@ Passing `self` implicitly also verifies the type:
```py
from typing import Never, Callable
class Strange:
def can_not_be_called(self: Never) -> None: ...
# error: [invalid-argument-type] "Argument to bound method `can_not_be_called` is incorrect: Expected `Never`, found `Strange`"
Strange().can_not_be_called()
```
@@ -147,6 +157,7 @@ The name `self` is not special in any way.
def some_decorator[**P, R](f: Callable[P, R]) -> Callable[P, R]:
return f
class B:
def name_does_not_matter(this) -> Self:
reveal_type(this) # revealed: Self@name_does_not_matter
@@ -184,12 +195,14 @@ class B:
@some_decorator
def decorated_static_method(self):
reveal_type(self) # revealed: Unknown
# TODO: On Python <3.10, this should ideally be rejected, because `staticmethod` objects were not callable.
@some_decorator
@staticmethod
def decorated_static_method_2(self):
reveal_type(self) # revealed: Unknown
reveal_type(B().name_does_not_matter()) # revealed: B
reveal_type(B().positional_only(1)) # revealed: B
reveal_type(B().keyword_only(x=1)) # revealed: B
@@ -197,6 +210,7 @@ reveal_type(B().decorated_method()) # revealed: B
reveal_type(B().a_property) # revealed: B
async def _():
reveal_type(await B().async_method()) # revealed: B
```
@@ -208,12 +222,14 @@ from typing import Self, Generic, TypeVar
T = TypeVar("T")
class G(Generic[T]):
def id(self) -> Self:
reveal_type(self) # revealed: Self@id
return self
reveal_type(G[int]().id()) # revealed: G[int]
reveal_type(G[str]().id()) # revealed: G[str]
```
@@ -224,15 +240,18 @@ Free functions and nested functions do not use implicit `Self`:
def not_a_method(self):
reveal_type(self) # revealed: Unknown
# error: [invalid-type-form]
def does_not_return_self(self) -> Self:
return self
class C:
def outer(self) -> None:
def inner(self):
reveal_type(self) # revealed: Unknown
reveal_type(not_a_method) # revealed: def not_a_method(self) -> Unknown
```
@@ -248,15 +267,18 @@ affect that subtitution. If we blindly substitute all occurrences of `Self`, we
```py
from typing import Self
class Foo[T]:
def foo(self: Self) -> T:
raise NotImplementedError
class Bar:
def bar(self: Self, x: Foo[Self]):
# revealed: bound method Foo[Self@bar].foo() -> Self@bar
reveal_type(x.foo)
def f[U: Bar](x: Foo[U]):
# revealed: bound method Foo[U@f].foo() -> U@f
reveal_type(x.foo)
@@ -272,10 +294,12 @@ python-version = "3.10"
```py
from typing_extensions import Self
class C:
def method(self: Self) -> Self:
return self
reveal_type(C().method()) # revealed: C
```
@@ -286,6 +310,7 @@ reveal_type(C().method()) # revealed: C
```py
from typing import Self
class Shape:
def foo(self: Self) -> Self:
return self
@@ -295,8 +320,10 @@ class Shape:
reveal_type(cls) # revealed: type[Self@bar]
return cls()
class Circle(Shape): ...
reveal_type(Shape().foo()) # revealed: Shape
reveal_type(Shape.bar()) # revealed: Shape
@@ -309,6 +336,7 @@ reveal_type(Circle.bar()) # revealed: Circle
```py
from typing import Self
class Shape:
def foo(self) -> Self:
return self
@@ -318,8 +346,10 @@ class Shape:
reveal_type(cls) # revealed: type[Self@bar]
return cls()
class Circle(Shape): ...
reveal_type(Shape().foo()) # revealed: Shape
reveal_type(Shape.bar()) # revealed: Shape
@@ -332,6 +362,7 @@ reveal_type(Circle.bar()) # revealed: Circle
```py
from typing import Self
class GenericShape[T]:
def foo(self) -> Self:
return self
@@ -346,8 +377,10 @@ class GenericShape[T]:
reveal_type(cls) # revealed: type[Self@baz]
return cls()
class GenericCircle[T](GenericShape[T]): ...
reveal_type(GenericShape().foo()) # revealed: GenericShape[Unknown]
reveal_type(GenericShape.bar()) # revealed: GenericShape[Unknown]
reveal_type(GenericShape[int].bar()) # revealed: GenericShape[int]
@@ -369,16 +402,19 @@ the return type should be the child's `Self` type variable, not the concrete chi
```py
from typing import Self
class Parent:
def copy(self) -> Self:
return self
class Child(Parent):
def copy(self) -> Self:
result = super().copy()
reveal_type(result) # revealed: Self@copy
return result
# When called on concrete types, Self is substituted correctly.
reveal_type(Child().copy()) # revealed: Child
```
@@ -388,11 +424,13 @@ The same applies to classmethods with `Self` return types:
```py
from typing import Self
class Parent:
@classmethod
def create(cls) -> Self:
return cls()
class Child(Parent):
@classmethod
def create(cls) -> Self:
@@ -400,6 +438,7 @@ class Child(Parent):
reveal_type(result) # revealed: Self@create
return result
# When called on concrete types, Self is substituted correctly.
reveal_type(Child.create()) # revealed: Child
```
@@ -412,6 +451,7 @@ TODO: The use of `Self` to annotate the `next_node` attribute should be
```py
from typing import Self
class LinkedList:
value: int
next_node: Self
@@ -422,6 +462,7 @@ class LinkedList:
# error: [invalid-return-type]
return self.next_node
reveal_type(LinkedList().next()) # revealed: LinkedList
```
@@ -432,8 +473,10 @@ from typing import Generic, TypeVar
T = TypeVar("T")
class C(Generic[T]):
foo: T
def method(self) -> None:
reveal_type(self) # revealed: Self@method
reveal_type(self.foo) # revealed: T@C
@@ -446,11 +489,14 @@ from typing import Self, Generic, TypeVar
T = TypeVar("T")
class Container(Generic[T]):
value: T
def set_value(self: Self, value: T) -> Self:
return self
int_container: Container[int] = Container[int]()
reveal_type(int_container) # revealed: Container[int]
reveal_type(int_container.set_value(1)) # revealed: Container[int]
@@ -466,26 +512,31 @@ a type that satisfies a bound.
```py
from typing import NewType
class Base: ...
class C[T: Base]:
x: T
def g(self) -> None:
pass
# Calling a method on a specialized instance should not produce an error
C[Base]().g()
# Test with a NewType bound
K = NewType("K", int)
class D[T: K]:
x: T
def h(self) -> None:
pass
# Calling a method on a specialized instance should not produce an error
D[K]().h()
```
@@ -499,6 +550,7 @@ TODO: <https://typing.python.org/en/latest/spec/generics.html#use-in-protocols>
```py
from typing import Self
class Shape:
def union(self: Self, other: Self | None):
reveal_type(other) # revealed: Self@union | None
@@ -512,10 +564,12 @@ This is a regression test for <https://github.com/astral-sh/ty/issues/1156>.
```py
from typing import Self
class Container[T = bytes]:
def __init__(self: Self, data: T | None = None) -> None:
self.data = data
reveal_type(Container()) # revealed: Container[bytes]
reveal_type(Container(1)) # revealed: Container[int]
reveal_type(Container("a")) # revealed: Container[str]
@@ -527,20 +581,25 @@ reveal_type(Container(b"a")) # revealed: Container[bytes]
```py
from typing import Self, TypeVar, Generic
class Container[T = bytes]:
def method(self) -> Self:
return self
def _(c: Container[str], d: Container):
reveal_type(c.method()) # revealed: Container[str]
reveal_type(d.method()) # revealed: Container[bytes]
T = TypeVar("T", default=bytes)
class LegacyContainer(Generic[T]):
def method(self) -> Self:
return self
def _(c: LegacyContainer[str], d: LegacyContainer):
reveal_type(c.method()) # revealed: LegacyContainer[str]
reveal_type(d.method()) # revealed: LegacyContainer[bytes]
@@ -555,12 +614,15 @@ from typing import Self, Generic, TypeVar
T = TypeVar("T")
# error: [invalid-type-form]
def x(s: Self): ...
# error: [invalid-type-form]
b: Self
# TODO: "Self" cannot be used in a function with a `self` or `cls` parameter that has a type annotation other than "Self"
class Foo:
# TODO: This `self: T` annotation should be rejected because `T` is not `Self`
@@ -578,11 +640,14 @@ class Foo:
# error: [invalid-return-type]
return Foo()
class Bar(Generic[T]): ...
# error: [invalid-type-form]
class Baz(Bar[Self]): ...
class MyMetaclass(type):
# TODO: reject the Self usage. because self cannot be used within a metaclass.
def __new__(cls) -> Self:
@@ -604,9 +669,11 @@ from __future__ import annotations
from typing import final
@final
class Disjoint: ...
class Explicit:
# TODO: We could emit a warning if the annotated type of `self` is disjoint from `Explicit`
def bad(self: Disjoint) -> None:
@@ -615,15 +682,18 @@ class Explicit:
def forward(self: Explicit) -> None:
reveal_type(self) # revealed: Explicit
# error: [invalid-argument-type] "Argument to bound method `bad` is incorrect: Expected `Disjoint`, found `Explicit`"
Explicit().bad()
Explicit().forward()
class ExplicitGeneric[T]:
def special(self: ExplicitGeneric[int]) -> None:
reveal_type(self) # revealed: ExplicitGeneric[int]
ExplicitGeneric[int]().special()
# TODO: this should be an `invalid-argument-type` error
@@ -638,6 +708,7 @@ specific type of the bound parameter.
```py
from typing import Self
class C:
def instance_method(self, other: Self) -> Self:
return self
@@ -646,13 +717,16 @@ class C:
def class_method(cls) -> Self:
return cls()
# revealed: bound method C.instance_method(other: C) -> C
reveal_type(C().instance_method)
# revealed: bound method <class 'C'>.class_method() -> C
reveal_type(C.class_method)
class D(C): ...
# revealed: bound method D.instance_method(other: D) -> D
reveal_type(D().instance_method)
# revealed: bound method <class 'D'>.class_method() -> D
@@ -666,13 +740,16 @@ bound at `C.f`.
from typing import Self
from ty_extensions import generic_context
class C[T]():
def f(self: Self):
def b(x: Self):
reveal_type(x) # revealed: Self@f
# revealed: None
reveal_type(generic_context(b))
# revealed: ty_extensions.GenericContext[Self@f]
reveal_type(generic_context(C.f))
```
@@ -684,13 +761,16 @@ Even if the `Self` annotation appears first in the nested function, it is the me
from typing import Self
from ty_extensions import generic_context
class C:
def f(self: "C"):
def b(x: Self):
reveal_type(x) # revealed: Self@f
# revealed: None
reveal_type(generic_context(b))
# revealed: None
reveal_type(generic_context(C.f))
```
@@ -702,9 +782,11 @@ This makes sure that we don't bind `self` if it's not a positional parameter:
```py
from ty_extensions import CallableTypeOf
class C:
def method(*args, **kwargs) -> None: ...
def _(c: CallableTypeOf[C().method]):
reveal_type(c) # revealed: (...) -> None
```

View File

@@ -12,11 +12,13 @@ from typing_extensions import TypeVarTuple
Ts = TypeVarTuple("Ts")
def append_int(*args: *Ts) -> tuple[*Ts, int]:
reveal_type(args) # revealed: @Todo(PEP 646)
return (*args, 1)
# TODO should be tuple[Literal[True], Literal["a"], int]
reveal_type(append_int(True, "a")) # revealed: tuple[@Todo(PEP 646), ...]
```

View File

@@ -10,6 +10,7 @@ All of the following symbols can be mapped one-to-one with the actual type:
```py
import typing
def f(
list_bare: typing.List,
list_parametrized: typing.List[int],
@@ -65,6 +66,7 @@ In case the incorrect number of type arguments is passed, a diagnostic is given.
```py
import typing
def f(
# error: [invalid-type-form] "Legacy alias `typing.List` expected exactly 1 argument, got 2"
incorrect_list: typing.List[int, int],
@@ -120,23 +122,31 @@ from ty_extensions import reveal_mro
### Built-ins
####################
class ListSubclass(typing.List): ...
# revealed: (<class 'ListSubclass'>, <class 'list[Unknown]'>, <class 'MutableSequence[Unknown]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(ListSubclass)
class DictSubclass(typing.Dict): ...
# revealed: (<class 'DictSubclass'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(DictSubclass)
class SetSubclass(typing.Set): ...
# revealed: (<class 'SetSubclass'>, <class 'set[Unknown]'>, <class 'MutableSet[Unknown]'>, <class 'AbstractSet[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(SetSubclass)
class FrozenSetSubclass(typing.FrozenSet): ...
# revealed: (<class 'FrozenSetSubclass'>, <class 'frozenset[Unknown]'>, <class 'AbstractSet[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(FrozenSetSubclass)
@@ -144,28 +154,38 @@ reveal_mro(FrozenSetSubclass)
### `collections`
####################
class ChainMapSubclass(typing.ChainMap): ...
# revealed: (<class 'ChainMapSubclass'>, <class 'ChainMap[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(ChainMapSubclass)
class CounterSubclass(typing.Counter): ...
# revealed: (<class 'CounterSubclass'>, <class 'Counter[Unknown]'>, <class 'dict[Unknown, int]'>, <class 'MutableMapping[Unknown, int]'>, <class 'Mapping[Unknown, int]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(CounterSubclass)
class DefaultDictSubclass(typing.DefaultDict): ...
# revealed: (<class 'DefaultDictSubclass'>, <class 'defaultdict[Unknown, Unknown]'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(DefaultDictSubclass)
class DequeSubclass(typing.Deque): ...
# revealed: (<class 'DequeSubclass'>, <class 'deque[Unknown]'>, <class 'MutableSequence[Unknown]'>, <class 'Sequence[Unknown]'>, <class 'Reversible[Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(DequeSubclass)
class OrderedDictSubclass(typing.OrderedDict): ...
# revealed: (<class 'OrderedDictSubclass'>, <class 'OrderedDict[Unknown, Unknown]'>, <class 'dict[Unknown, Unknown]'>, <class 'MutableMapping[Unknown, Unknown]'>, <class 'Mapping[Unknown, Unknown]'>, <class 'Collection[Unknown]'>, <class 'Iterable[Unknown]'>, <class 'Container[Unknown]'>, typing.Protocol, typing.Generic, <class 'object'>)
reveal_mro(OrderedDictSubclass)
```

View File

@@ -35,6 +35,7 @@ def f(v: tuple[int, "str"]):
def f(v: "Foo"):
reveal_type(v) # revealed: Foo
class Foo: ...
```
@@ -52,6 +53,7 @@ def f(v: "Foo"):
def f(v: int | "Foo"):
reveal_type(v) # revealed: int | Foo
class Foo: ...
```
@@ -60,10 +62,12 @@ class Foo: ...
```py
from typing import Literal
def f1(v: Literal["Foo", "Bar"], w: 'Literal["Foo", "Bar"]'):
reveal_type(v) # revealed: Literal["Foo", "Bar"]
reveal_type(w) # revealed: Literal["Foo", "Bar"]
class Foo: ...
```
@@ -104,6 +108,7 @@ def f1(
```py
from typing import Literal
def f(v: Literal["a", r"b", b"c", "d" "e", "\N{LATIN SMALL LETTER F}", "\x67", """h"""]):
reveal_type(v) # revealed: Literal["a", "b", "de", "f", "g", "h", b"c"]
```
@@ -113,12 +118,14 @@ def f(v: Literal["a", r"b", b"c", "d" "e", "\N{LATIN SMALL LETTER F}", "\x67", "
```py
MyType = int
class Aliases:
MyType = str
forward: "MyType" = "value"
not_forward: MyType = "value"
reveal_type(Aliases.forward) # revealed: str
reveal_type(Aliases.not_forward) # revealed: str
```
@@ -132,8 +139,10 @@ c: "Foo"
# error: [invalid-assignment] "Object of type `Literal[1]` is not assignable to `Foo`"
d: "Foo" = 1
class Foo: ...
c = Foo()
reveal_type(a) # revealed: Literal[1]
@@ -211,6 +220,7 @@ def valid(
reveal_type(a1) # revealed: int | str
reveal_type(a2) # revealed: int | str
def invalid(
# error: [invalid-syntax-in-forward-annotation]
a1: """

View File

@@ -15,6 +15,7 @@ a4: Union[Union[bytes, str]]
a5: Union[int]
a6: Union[()]
def f():
# revealed: int | str
reveal_type(a)
@@ -55,6 +56,7 @@ from typing_extensions import Union
a: Union[int, str]
def f():
# revealed: int | str
reveal_type(a)
@@ -65,6 +67,7 @@ def f():
```py
from typing import Union
# error: [invalid-type-form] "`typing.Union` requires at least one argument when used in a type expression"
def f(x: Union) -> None:
reveal_type(x) # revealed: Unknown
@@ -80,6 +83,7 @@ python-version = "3.10"
```py
X = int | str
def f(y: X):
reveal_type(y) # revealed: int | str
```

View File

@@ -12,34 +12,44 @@ P = ParamSpec("P")
Ts = TypeVarTuple("Ts")
R_co = TypeVar("R_co", covariant=True)
def f(*args: Unpack[Ts]) -> tuple[Unpack[Ts]]:
reveal_type(args) # revealed: tuple[@Todo(`Unpack[]` special form), ...]
return args
def i(callback: Callable[Concatenate[int, P], R_co], *args: P.args, **kwargs: P.kwargs) -> R_co:
reveal_type(args) # revealed: P@i.args
reveal_type(kwargs) # revealed: P@i.kwargs
return callback(42, *args, **kwargs)
class Foo:
def method(self, x: Self):
reveal_type(x) # revealed: Self@method
def ex2(msg: str):
def wrapper(fn: Callable[P, R_co]) -> Callable[P, R_co]:
def wrapped(*args: P.args, **kwargs: P.kwargs) -> R_co:
print(msg)
return fn(*args, **kwargs)
return wrapped
return wrapper
def ex3(msg: str):
P = ParamSpec("P")
def wrapper(fn: Callable[P, R_co]) -> Callable[P, R_co]:
def wrapped(*args: P.args, **kwargs: P.kwargs) -> R_co:
print(msg)
return fn(*args, **kwargs)
return wrapped
return wrapper
```
@@ -50,6 +60,7 @@ One thing that is supported is error messages for using special forms in type ex
```py
from typing_extensions import Unpack, TypeGuard, TypeIs, Concatenate, ParamSpec, Generic
def _(
a: Unpack, # error: [invalid-type-form] "`typing.Unpack` requires exactly one argument when used in a type expression"
b: TypeGuard, # error: [invalid-type-form] "`typing.TypeGuard` requires exactly one argument when used in a type expression"
@@ -77,14 +88,28 @@ from typing import Callable
from typing_extensions import Self, Unpack, TypeGuard, TypeIs, Concatenate, Generic
from ty_extensions import reveal_mro
class A(Self): ... # error: [invalid-base]
class B(Unpack): ... # error: [invalid-base]
class C(TypeGuard): ... # error: [invalid-base]
class D(TypeIs): ... # error: [invalid-base]
class E(Concatenate): ... # error: [invalid-base]
class F(Callable): ...
class G(Generic): ... # error: [invalid-base] "Cannot inherit from plain `Generic`"
reveal_mro(F) # revealed: (<class 'F'>, @Todo(Support for Callable as a base class), <class 'object'>)
```
@@ -105,6 +130,7 @@ T = TypeVar("T")
# error: [invalid-type-form] "Special form `typing.TypeAlias` expected no type parameter"
X: TypeAlias[T] = int
class Foo[T]:
# error: [invalid-type-form] "Special form `typing.Self` expected no type parameter"
# error: [invalid-type-form] "Special form `typing.Self` expected no type parameter"

View File

@@ -14,5 +14,6 @@ MyTypedDict = typing.TypedDict("MyTypedDict", {"foo": int})
MyNamedTuple1 = typing.NamedTuple("MyNamedTuple1", [("foo", int)])
MyNamedTuple2 = collections.namedtuple("MyNamedTuple2", ["foo"])
def f(a: MyEnum, b: MyTypedDict, c: MyNamedTuple1, d: MyNamedTuple2): ...
```

View File

@@ -11,6 +11,7 @@ from typing_extensions import Final, ReadOnly, TypedDict
X: Final = 42
Y: Final[int] = 42
class Bar(TypedDict):
z: ReadOnly[bytes]
```
@@ -22,6 +23,7 @@ One thing that is supported is error messages for using type qualifiers in type
```py
from typing_extensions import Final, ClassVar, Required, NotRequired, ReadOnly
def _(
# error: [invalid-type-form] "Type qualifier `typing.Final` is not allowed in type expressions (only in annotation expressions)"
a: Final | int,
@@ -42,7 +44,12 @@ You can't inherit from a type qualifier.
```py
from typing_extensions import Final, ClassVar, Required, NotRequired, ReadOnly
class A(Final): ... # error: [invalid-base]
class B(ClassVar): ... # error: [invalid-base]
class C(ReadOnly): ... # error: [invalid-base]
```

View File

@@ -239,10 +239,12 @@ python-version = "3.12"
```py
from typing import Any
class X[T]:
def __init__(self, value: T):
self.value = value
x1: X[int] = X(1)
reveal_type(x1) # revealed: X[int]
@@ -252,13 +254,16 @@ reveal_type(x2) # revealed: X[int | None]
x3: X[int | None] | None = X(1)
reveal_type(x3) # revealed: X[int | None]
def _[T](x1: X[T]):
x2: X[T | int] = X(x1.value)
reveal_type(x2) # revealed: X[T@_ | int]
x4: X[Any] = X(1)
reveal_type(x4) # revealed: X[Any]
def _(flag: bool):
x5: X[int | None] = X(1) if flag else X(2)
reveal_type(x5) # revealed: X[int | None]
@@ -267,10 +272,12 @@ def _(flag: bool):
```py
from dataclasses import dataclass
@dataclass
class Y[T]:
value: T
y1 = Y(value=1)
reveal_type(y1) # revealed: Y[int]
@@ -285,6 +292,7 @@ class Z[T]:
def __new__(cls, value: T):
return super().__new__(cls)
z1 = Z(1)
reveal_type(z1) # revealed: Z[int]
@@ -352,8 +360,10 @@ from __future__ import annotations
x: Foo
class Foo: ...
x = Foo()
reveal_type(x) # revealed: Foo
```
@@ -379,8 +389,10 @@ python-version = "3.14"
```py
x: Foo
class Foo: ...
x = Foo()
reveal_type(x) # revealed: Foo
```
@@ -404,9 +416,11 @@ python-version = "3.12"
```py
from typing import Literal, Sequence
def f[T](x: T) -> list[T]:
return [x]
x1 = f("a")
reveal_type(x1) # revealed: list[str]
@@ -428,9 +442,11 @@ x6: list[int] = f("a")
# error: [invalid-assignment] "Object of type `list[str]` is not assignable to `tuple[int]`"
x7: tuple[int] = f("a")
def f2[T: int](x: T) -> T:
return x
x8: int = f2(True)
reveal_type(x8) # revealed: Literal[True]
@@ -453,12 +469,15 @@ A function's arguments are also inferred using the type context:
```py
from typing import TypedDict
class TD(TypedDict):
x: int
def f[T](x: list[T]) -> T:
return x[0]
a: TD = f([{"x": 0}, {"x": 1}])
reveal_type(a) # revealed: TD
@@ -483,12 +502,15 @@ But not in a way that leads to assignability errors:
```py
from typing import TypedDict, Any
class TD(TypedDict, total=False):
x: str
class TD2(TypedDict):
x: str
def f(self, dt: dict[str, Any], key: str):
x1: TD = dt.get(key, {})
reveal_type(x1) # revealed: Any
@@ -525,15 +547,19 @@ python-version = "3.14"
```py
from typing import Any
def f[T](x: T) -> list[T]:
return [x]
def f2[T](x: T) -> list[T] | None:
return [x]
def f3[T](x: T) -> list[T] | dict[T, T]:
return [x]
a = f(1)
reveal_type(a) # revealed: list[int]
@@ -564,29 +590,37 @@ We only prefer the declared type if it is in non-covariant position.
class Bivariant[T]:
pass
class Covariant[T]:
def pop(self) -> T:
raise NotImplementedError
class Contravariant[T]:
def push(self, value: T) -> None:
pass
class Invariant[T]:
x: T
def bivariant[T](x: T) -> Bivariant[T]:
return Bivariant()
def covariant[T](x: T) -> Covariant[T]:
return Covariant()
def contravariant[T](x: T) -> Contravariant[T]:
return Contravariant()
def invariant[T](x: T) -> Invariant[T]:
return Invariant()
x1 = bivariant(1)
x2 = covariant(1)
x3 = contravariant(1)
@@ -614,6 +648,7 @@ class X[T]:
def pop(self) -> T:
raise NotImplementedError
x1: X[int | None] = X()
reveal_type(x1) # revealed: X[None]
```
@@ -650,16 +685,20 @@ reveal_type(x5) # revealed: list[Iterable[Any]]
x6: Iterable[list[Any]] = [[1, 2, 3]]
reveal_type(x6) # revealed: list[list[Any]]
class X[T]:
value: T
def __init__(self, value: T): ...
class A[T](X[T]): ...
def a[T](value: T) -> A[T]:
return A(value)
x7: A[object] = A(1)
reveal_type(x7) # revealed: A[object]
@@ -672,9 +711,11 @@ reveal_type(x9) # revealed: A[object]
x10: X[object] | None = a(1)
reveal_type(x10) # revealed: A[object]
def f[T](x: T) -> list[list[T]]:
return [[x]]
x11: Sequence[Sequence[Any]] = f(1)
reveal_type(x11) # revealed: list[list[int]]
@@ -695,28 +736,35 @@ python-version = "3.12"
```py
from typing import reveal_type, TypedDict
def identity[T](x: T) -> T:
return x
def _(narrow: dict[str, str], target: list[str] | dict[str, str] | None):
target = identity(narrow)
reveal_type(target) # revealed: dict[str, str]
def _(narrow: list[str], target: list[str] | dict[str, str] | None):
target = identity(narrow)
reveal_type(target) # revealed: list[str]
def _(narrow: list[str] | dict[str, str], target: list[str] | dict[str, str] | None):
target = identity(narrow)
reveal_type(target) # revealed: list[str] | dict[str, str]
class TD(TypedDict):
x: int
def _(target: list[TD] | dict[str, TD] | None):
target = identity([{"x": 1}])
reveal_type(target) # revealed: list[TD]
def _(target: list[TD] | dict[str, TD] | None):
target = identity({"x": {"x": 1}})
reveal_type(target) # revealed: dict[str, TD]
@@ -733,9 +781,11 @@ python-version = "3.12"
def identity[T](x: T) -> T:
return x
def lst[T](x: T) -> list[T]:
return [x]
def _(i: int):
a: int | None = i
b: int | None = identity(i)
@@ -765,9 +815,11 @@ def _(i: int):
reveal_type(b) # revealed: list[Unknown]
reveal_type(c) # revealed: list[Unknown]
def f[T](x: list[T]) -> T:
return x[0]
def _(a: int, b: str, c: int | str):
x1: int = f(lst(a))
reveal_type(x1) # revealed: int

View File

@@ -23,14 +23,17 @@ class C:
def __isub__(self, other: int) -> str:
return "Hello, world!"
x = C()
x -= 1
reveal_type(x) # revealed: str
class C:
def __iadd__(self, other: str) -> int:
return 1
x = C()
x += "Hello"
reveal_type(x) # revealed: int
@@ -45,6 +48,7 @@ class C:
def __isub__(self, other: str) -> int:
return 42
x = C()
# error: [unsupported-operator] "Operator `-=` is not supported between objects of type `C` and `Literal[1]`"
x -= 1
@@ -58,9 +62,12 @@ reveal_type(x) # revealed: int
def _(flag: bool):
class Foo:
if flag:
def __iadd__(self, other: int) -> str:
return "Hello, world!"
else:
def __iadd__(self, other: int) -> int:
return 42
@@ -76,6 +83,7 @@ def _(flag: bool):
def _(flag: bool):
class Foo:
if flag:
def __iadd__(self, other: str) -> int:
return 42
@@ -94,7 +102,9 @@ def _(flag: bool):
class Foo:
def __add__(self, other: str) -> str:
return "Hello, world!"
if flag:
def __iadd__(self, other: str) -> int:
return 42
@@ -111,7 +121,9 @@ def _(flag1: bool, flag2: bool):
class Foo:
def __add__(self, other: int) -> str:
return "Hello, world!"
if flag1:
def __iadd__(self, other: int) -> int:
return 42
@@ -148,7 +160,9 @@ def f(flag: bool, flag2: bool):
class Foo:
def __add__(self, other: int) -> str:
return "Hello, world!"
if flag:
def __iadd__(self, other: int) -> int:
return 42
@@ -175,8 +189,10 @@ class Meta(type):
def __iadd__(cls, other: int) -> str:
return ""
class C(metaclass=Meta): ...
cls = C
cls += 1

View File

@@ -6,6 +6,7 @@
async def retrieve() -> int:
return 42
async def main():
result = await retrieve()
@@ -19,9 +20,11 @@ from typing import TypeVar
T = TypeVar("T")
async def persist(x: T) -> T:
return x
async def f(x: int):
result = await persist(x)
@@ -36,9 +39,11 @@ async def f(x: int):
import asyncio
import concurrent.futures
def blocking_function() -> int:
return 42
async def main():
loop = asyncio.get_event_loop()
with concurrent.futures.ThreadPoolExecutor() as pool:
@@ -51,9 +56,11 @@ async def main():
```py
import asyncio
async def f() -> int:
return 1
async def main():
task = asyncio.create_task(f())
@@ -67,9 +74,11 @@ async def main():
```py
import asyncio
async def task(name: str) -> int:
return len(name)
async def main():
(a, b) = await asyncio.gather(
task("A"),
@@ -113,6 +122,7 @@ final type of the `await` expression, we retrieve that third argument of the `Ge
```py
from typing import Generator
def _():
result = yield from retrieve().__await__()
reveal_type(result) # revealed: int
@@ -127,5 +137,6 @@ not just `Unknown`:
async def f():
pass
reveal_type(f()) # revealed: CoroutineType[Any, Any, Unknown]
```

File diff suppressed because it is too large Load Diff

View File

@@ -18,9 +18,11 @@ python-version = "3.12"
```py
from typing import Literal
def list1[T](x: T) -> list[T]:
return [x]
l1: list[Literal[1]] = list1(1)
reveal_type(l1) # revealed: list[Literal[1]]
@@ -30,6 +32,7 @@ reveal_type(l2) # revealed: list[int]
l3: list[int | str] | None = list1(1)
reveal_type(l3) # revealed: list[int | str]
def _(l: list[int] | None = None):
l1 = l or list()
reveal_type(l1) # revealed: (list[int] & ~AlwaysFalsy) | list[Unknown]
@@ -38,10 +41,18 @@ def _(l: list[int] | None = None):
# it would be better if this were `list[int]`? (https://github.com/astral-sh/ty/issues/136)
reveal_type(l2) # revealed: (list[int] & ~AlwaysFalsy) | list[Unknown]
def f[T](x: T, cond: bool) -> T | list[T]:
return x if cond else [x]
l5: int | list[int] = f(1, True)
a: list[int] = [1, 2, *(3, 4, 5)]
reveal_type(a) # revealed: list[int]
b: list[list[int]] = [[1], [2], *([3], [4])]
reveal_type(b) # revealed: list[list[int]]
```
`typed_dict.py`:
@@ -49,9 +60,11 @@ l5: int | list[int] = f(1, True)
```py
from typing import TypedDict
class TD(TypedDict):
x: int
d1 = {"x": 1}
d2: TD = {"x": 1}
d3: dict[str, int] = {"x": 1}
@@ -63,9 +76,11 @@ reveal_type(d2) # revealed: TD
reveal_type(d3) # revealed: dict[str, int]
reveal_type(d4) # revealed: TD
def _() -> TD:
return {"x": 1}
def _() -> TD:
# error: [missing-typed-dict-key] "Missing required key 'x' in TypedDict `TD` constructor"
# error: [invalid-return-type]
@@ -82,12 +97,15 @@ python-version = "3.12"
```py
from typing import overload, Callable
def list1[T](x: T) -> list[T]:
return [x]
def get_data() -> dict | None:
return {}
def wrap_data() -> list[dict]:
if not (res := get_data()):
return list1({})
@@ -97,15 +115,18 @@ def wrap_data() -> list[dict]:
# by bidirectional type inference using the annotated return type, and the type of `res` is not used.
return list1(res)
def wrap_data2() -> list[dict] | None:
if not (res := get_data()):
return None
reveal_type(list1(res)) # revealed: list[dict[Unknown, Unknown] & ~AlwaysFalsy]
return list1(res)
def deco[T](func: Callable[[], T]) -> Callable[[], T]:
return func
def outer() -> Callable[[], list[dict]]:
@deco
def inner() -> list[dict]:
@@ -113,8 +134,10 @@ def outer() -> Callable[[], list[dict]]:
return list1({})
reveal_type(list1(res)) # revealed: list[dict[Unknown, Unknown] & ~AlwaysFalsy]
return list1(res)
return inner
@overload
def f(x: int) -> list[int]: ...
@overload
@@ -126,15 +149,19 @@ def f(x: int | str) -> list[int] | list[str]:
else:
return list1(x)
reveal_type(f(1)) # revealed: list[int]
reveal_type(f("a")) # revealed: list[str]
async def g() -> list[int | str]:
return list1(1)
def h[T](x: T, cond: bool) -> T | list[T]:
return i(x, cond)
def i[T](x: T, cond: bool) -> T | list[T]:
return x if cond else [x]
```
@@ -154,6 +181,7 @@ Function parameter annotations:
```py
def b(x: list[Literal[1]]): ...
b([1])
```
@@ -164,6 +192,7 @@ class C:
def __init__(self, x: list[Literal[1]]): ...
def foo(self, x: list[Literal[1]]): ...
C([1]).foo([1])
```
@@ -181,6 +210,7 @@ class E:
a: list[Literal[1]]
b: list[Literal[1]]
def _(e: E):
e.a = [1]
E.b = [1]
@@ -205,6 +235,7 @@ Both meta and class/instance attribute annotations are used as type context:
```py
from typing import Literal, Any
class DataDescriptor:
def __get__(self, instance: object, owner: type | None = None) -> list[Literal[1]]:
return []
@@ -212,9 +243,11 @@ class DataDescriptor:
def __set__(self, instance: object, value: list[Literal[1]]) -> None:
pass
def lst[T](x: T) -> list[T]:
return [x]
def _(flag: bool):
class Meta(type):
if flag:
@@ -236,15 +269,19 @@ For union targets, each element of the union is considered as a separate type co
```py
from typing import Literal
class X:
x: list[int | str]
class Y:
x: list[int | None]
def lst[T](x: T) -> list[T]:
return [x]
def _(xy: X | Y):
xy.x = lst(1)
```
@@ -263,12 +300,14 @@ calls:
def f[T](x: T) -> list[T]:
return [x]
class A:
def __new__(cls, value: list[int | str]):
return super().__new__(cls, value)
def __init__(self, value: list[int | None]): ...
A(f(1))
# error: [invalid-argument-type] "Argument to function `__new__` is incorrect: Expected `list[int | str]`, found `list[list[Unknown]]`"
@@ -289,6 +328,7 @@ The type context is propagated through both branches of conditional expressions:
def f[T](x: T) -> list[T]:
raise NotImplementedError
def _(flag: bool):
x1 = f(1) if flag else f(2)
reveal_type(x1) # revealed: list[int]
@@ -304,19 +344,24 @@ The key and value parameters types are used as type context for `__setitem__` du
```py
from typing import TypedDict
class Bar(TypedDict):
baz: float
def _(x: dict[str, Bar]):
x["foo"] = reveal_type({"baz": 2}) # revealed: Bar
class X:
def __setitem__(self, key: Bar, value: Bar): ...
def _(x: X):
# revealed: Bar
x[reveal_type({"baz": 1})] = reveal_type({"baz": 2}) # revealed: Bar
# TODO: Support type context with union subscripting.
def _(x: X | dict[Bar, Bar]):
# error: [invalid-assignment]
@@ -339,6 +384,7 @@ Diagnostics unrelated to the type-context are only reported once:
def f[T](x: T) -> list[T]:
return [x]
def a(x: list[bool], y: list[bool]): ...
def b(x: list[int], y: list[int]): ...
def c(x: list[int], y: list[int]): ...
@@ -379,12 +425,15 @@ def _(a: object, b: object, flag: bool):
```py
from typing import TypedDict
class TD(TypedDict):
y: int
class X:
td: TD
def _(x: X, flag: bool):
if flag:
y = 1

View File

@@ -109,6 +109,7 @@ def _(a: bool):
```py
import random
def _(a: bool):
def lhs_is_int(x: int):
reveal_type(x | a) # revealed: int

View File

@@ -11,8 +11,11 @@ python-version = "3.10"
```py
class A: ...
class B: ...
reveal_type(A | B) # revealed: <types.UnionType special-form 'A | B'>
```
@@ -25,8 +28,11 @@ python-version = "3.9"
```py
class A: ...
class B: ...
# error: "Operator `|` is not supported between objects of type `<class 'A'>` and `<class 'B'>`"
reveal_type(A | B) # revealed: Unknown
```
@@ -40,16 +46,23 @@ python-version = "3.12"
```py
class A: ...
class B: ...
def _(sub_a: type[A], sub_b: type[B]):
reveal_type(A | sub_b) # revealed: <types.UnionType special-form>
reveal_type(sub_a | B) # revealed: <types.UnionType special-form>
reveal_type(sub_a | sub_b) # revealed: <types.UnionType special-form>
class C[T]: ...
class D[T]: ...
reveal_type(C | D) # revealed: <types.UnionType special-form 'C[Unknown] | D[Unknown]'>
reveal_type(C[int] | D[str]) # revealed: <types.UnionType special-form 'C[int] | D[str]'>

View File

@@ -5,6 +5,7 @@
```py
from typing import Literal
class Yes:
def __add__(self, other) -> Literal["+"]:
return "+"
@@ -45,9 +46,13 @@ class Yes:
def __floordiv__(self, other) -> Literal["//"]:
return "//"
class Sub(Yes): ...
class No: ...
# Yes implements all of the dunder methods.
reveal_type(Yes() + Yes()) # revealed: Literal["+"]
reveal_type(Yes() - Yes()) # revealed: Literal["-"]
@@ -140,6 +145,7 @@ reveal_type(No() // Yes()) # revealed: Unknown
```py
from typing import Literal
class Yes:
def __add__(self, other) -> Literal["+"]:
return "+"
@@ -180,6 +186,7 @@ class Yes:
def __floordiv__(self, other) -> Literal["//"]:
return "//"
class Sub(Yes):
def __radd__(self, other) -> Literal["r+"]:
return "r+"
@@ -220,6 +227,7 @@ class Sub(Yes):
def __rfloordiv__(self, other) -> Literal["r//"]:
return "r//"
class No:
def __radd__(self, other) -> Literal["r+"]:
return "r+"
@@ -260,6 +268,7 @@ class No:
def __rfloordiv__(self, other) -> Literal["r//"]:
return "r//"
# Subclass reflected dunder methods take precedence over the superclass's regular dunders.
reveal_type(Yes() + Sub()) # revealed: Literal["r+"]
reveal_type(Yes() - Sub()) # revealed: Literal["r-"]
@@ -302,13 +311,18 @@ class's type, i.e. `type`.)
```py
from typing import Literal
class Yes:
def __add__(self, other) -> Literal["+"]:
return "+"
class Sub(Yes): ...
class No: ...
# error: [unsupported-operator] "Operator `+` is not supported between two objects of type `<class 'Yes'>`"
reveal_type(Yes + Yes) # revealed: Unknown
# error: [unsupported-operator] "Operator `+` is not supported between two objects of type `<class 'Sub'>`"
@@ -322,22 +336,30 @@ reveal_type(No + No) # revealed: Unknown
```py
from typing import Literal
class Yes:
def __add__(self, other) -> Literal["+"]:
return "+"
class Sub(Yes): ...
class No: ...
def yes() -> type[Yes]:
return Yes
def sub() -> type[Sub]:
return Sub
def no() -> type[No]:
return No
# error: [unsupported-operator] "Operator `+` is not supported between two objects of type `type[Yes]`"
reveal_type(yes() + yes()) # revealed: Unknown
# error: [unsupported-operator] "Operator `+` is not supported between two objects of type `type[Sub]`"
@@ -352,6 +374,7 @@ reveal_type(no() + no()) # revealed: Unknown
def f():
pass
# error: [unsupported-operator] "Operator `+` is not supported between two objects of type `def f() -> Unknown`"
reveal_type(f + f) # revealed: Unknown
# error: [unsupported-operator] "Operator `-` is not supported between two objects of type `def f() -> Unknown`"
@@ -398,8 +421,10 @@ class A: ...
```py
import mod1
class A: ...
# error: [unsupported-operator] "Operator `+` is not supported between objects of type `mod2.A` and `mod1.A`"
A() + mod1.A()
```

View File

@@ -53,8 +53,10 @@ class A:
def __or__(self, other) -> "A":
return self
class B: ...
reveal_type(A() + B()) # revealed: A
reveal_type(A() - B()) # revealed: A
reveal_type(A() * B()) # revealed: A
@@ -115,8 +117,10 @@ class A:
def __ror__(self, other) -> "A":
return self
class B: ...
reveal_type(B() + A()) # revealed: A
reveal_type(B() - A()) # revealed: A
reveal_type(B() * A()) # revealed: A
@@ -144,8 +148,10 @@ class A:
def __rsub__(self, other) -> int:
return 1
class B: ...
reveal_type(A() + B()) # revealed: int
reveal_type(B() - A()) # revealed: int
```
@@ -160,12 +166,15 @@ class A:
def __add__(self, other: "B") -> int:
return 42
class B:
def __radd__(self, other: "A") -> str:
return "foo"
reveal_type(A() + B()) # revealed: int
# Edge case: C is a subtype of C, *but* if the two sides are of *equal* types,
# the lhs *still* takes precedence
class C:
@@ -175,6 +184,7 @@ class C:
def __radd__(self, other: "C") -> str:
return "foo"
reveal_type(C() + C()) # revealed: int
```
@@ -191,17 +201,22 @@ class A:
def __radd__(self, other) -> str:
return "foo"
class MyString(str): ...
class B(A):
def __radd__(self, other) -> MyString:
return MyString()
reveal_type(A() + B()) # revealed: MyString
# N.B. Still a subtype of `A`, even though `A` does not appear directly in the class's `__bases__`
class C(B): ...
reveal_type(A() + C()) # revealed: MyString
```
@@ -218,8 +233,10 @@ class A:
def __radd__(self, other) -> int:
return 42
class B(A): ...
reveal_type(A() + B()) # revealed: str
```
@@ -240,10 +257,12 @@ class A:
def __sub__(self, other: "A") -> "A":
return A()
class B:
def __rsub__(self, other: A) -> "B":
return B()
reveal_type(A() - B()) # revealed: B
```
@@ -256,9 +275,11 @@ class A:
def __call__(self, other) -> int:
return 42
class B:
__add__ = A()
reveal_type(B() + B()) # revealed: Unknown | int
```
@@ -269,6 +290,7 @@ the callable is declared:
class B2:
__add__: A = A()
reveal_type(B2() + B2()) # revealed: int
```
@@ -287,6 +309,7 @@ reveal_type(3.14 + 3j) # revealed: int | float | complex
reveal_type(42 + 4.2) # revealed: int | float
reveal_type(3 + 3j) # revealed: int | float | complex
def _(x: bool, y: int):
reveal_type(x + y) # revealed: int
reveal_type(4.2 + x) # revealed: int | float
@@ -306,6 +329,7 @@ class A:
def __radd__(self, other) -> "A":
return self
reveal_type(A() + 1) # revealed: A
reveal_type(1 + A()) # revealed: A
@@ -341,12 +365,15 @@ from does_not_exist import Foo # error: [unresolved-import]
reveal_type(Foo) # revealed: Unknown
class X:
def __add__(self, other: object) -> int:
return 42
class Y(Foo): ...
# TODO: Should be `int | Unknown`; see above discussion.
reveal_type(X() + Y()) # revealed: int
```
@@ -359,6 +386,7 @@ reveal_type(X() + Y()) # revealed: int
class NotBoolable:
__bool__: int = 3
a = NotBoolable()
# error: [unsupported-bool-conversion]
@@ -372,6 +400,7 @@ When operating on class objects, the corresponding dunder methods are looked up
```py
from __future__ import annotations
class Meta(type):
def __add__(self, other: Meta) -> int:
return 1
@@ -382,9 +411,13 @@ class Meta(type):
def __getitem__(self, key: int) -> str:
return "a"
class A(metaclass=Meta): ...
class B(metaclass=Meta): ...
reveal_type(A + B) # revealed: int
# error: [unsupported-operator] "Operator `-` is not supported between objects of type `<class 'A'>` and `<class 'B'>`"
reveal_type(A - B) # revealed: Unknown
@@ -408,10 +441,12 @@ The magic method must exist on the class, not just on the instance:
def add_impl(self, other) -> int:
return 1
class A:
def __init__(self):
self.__add__ = add_impl
# error: [unsupported-operator] "Operator `+` is not supported between two objects of type `A`"
# revealed: Unknown
reveal_type(A() + A())
@@ -422,6 +457,7 @@ reveal_type(A() + A())
```py
class A: ...
# error: [unsupported-operator]
# revealed: Unknown
reveal_type(A() + A())
@@ -436,12 +472,15 @@ class A:
def __add__(self, other) -> int:
return 1
class B:
def __radd__(self, other) -> int:
return 1
class C: ...
# error: [unsupported-operator]
# revealed: Unknown
reveal_type(C() + A())
@@ -463,6 +502,7 @@ class Foo:
def __radd__(self, other: "Foo") -> "Foo":
return self
# error: [unsupported-operator]
# revealed: Unknown
reveal_type(Foo() + Foo())

View File

@@ -16,6 +16,7 @@ reveal_type(7 ^ 2) # revealed: Literal[5]
# error: [unsupported-operator] "Operator `+` is not supported between objects of type `Literal[2]` and `Literal["f"]`"
reveal_type(2 + "f") # revealed: Unknown
def lhs(x: int):
reveal_type(x + 1) # revealed: int
reveal_type(x - 4) # revealed: int
@@ -24,6 +25,7 @@ def lhs(x: int):
reveal_type(x / 3) # revealed: int | float
reveal_type(x % 3) # revealed: int
def rhs(x: int):
reveal_type(2 + x) # revealed: int
reveal_type(3 - x) # revealed: int
@@ -32,6 +34,7 @@ def rhs(x: int):
reveal_type(-3 / x) # revealed: int | float
reveal_type(5 % x) # revealed: int
def both(x: int):
reveal_type(x + x) # revealed: int
reveal_type(x - x) # revealed: int
@@ -52,6 +55,7 @@ reveal_type(2**2) # revealed: Literal[4]
reveal_type(1 ** (largest_u32 + 1)) # revealed: int
reveal_type(2**largest_u32) # revealed: int
def variable(x: int):
reveal_type(x**2) # revealed: int
reveal_type(2**x) # revealed: Any
@@ -134,8 +138,10 @@ bool(1) / False
# error: "Cannot divide object of type `float` by zero"
reveal_type(1.0 / 0) # revealed: int | float
class MyInt(int): ...
# No error for a subclass of int
reveal_type(MyInt(3) / 0) # revealed: int | float
```

View File

@@ -8,6 +8,7 @@ reveal_type(() + (1, 2)) # revealed: tuple[Literal[1, 2], ...]
reveal_type((1, 2) + ()) # revealed: tuple[Literal[1, 2], ...]
reveal_type(() + ()) # revealed: tuple[()]
def _(x: tuple[int, str], y: tuple[None, tuple[int]]):
reveal_type(x + y) # revealed: tuple[int | str | None | tuple[int], ...]
reveal_type(y + x) # revealed: tuple[None | tuple[int] | int | str, ...]
@@ -38,6 +39,7 @@ ThreeFour = tuple[Literal[3], Literal[4]]
IntTuple = tuple[int, ...]
StrTuple = tuple[str, ...]
def _(one_two: OneTwo, x: IntTuple, y: StrTuple, three_four: ThreeFour):
reveal_type(x + x) # revealed: tuple[int, ...]
reveal_type(x + y) # revealed: tuple[int | str, ...]

View File

@@ -28,6 +28,7 @@ the possible outcomes:
```py
from typing import Literal
def f3(two_or_three: Literal[2, 3], a_or_b: Literal["a", "b"]):
reveal_type(two_or_three + two_or_three) # revealed: Literal[4, 5, 6]
reveal_type(two_or_three**two_or_three) # revealed: Literal[4, 8, 9, 27]

View File

@@ -39,14 +39,17 @@ If a symbol has a declared type (`int`), we use that even if there is a more pre
```py
from typing import Any
def any() -> Any: ...
class Public:
a: int = 1
b: str = 2 # error: [invalid-assignment]
c: Any = 3
d: int = any()
reveal_type(Public.a) # revealed: int
reveal_type(Public.b) # revealed: str
reveal_type(Public.c) # revealed: Any
@@ -60,10 +63,12 @@ If a symbol is declared and *possibly* unbound, we trust the declared type witho
```py
from typing import Any
def any() -> Any: ...
def flag() -> bool:
return True
class Public:
a: int
b: str
@@ -76,6 +81,7 @@ class Public:
c = 3
d = any()
reveal_type(Public.a) # revealed: int
reveal_type(Public.b) # revealed: str
reveal_type(Public.c) # revealed: Any
@@ -90,10 +96,12 @@ is available somehow and simply use the declared type.
```py
from typing import Any
class Public:
a: int
b: Any
reveal_type(Public.a) # revealed: int
reveal_type(Public.b) # revealed: Any
```
@@ -108,10 +116,12 @@ inferred types:
```py
from typing import Any
def any() -> Any: ...
def flag() -> bool:
return True
class Public:
a = 1
b = 2
@@ -123,6 +133,7 @@ class Public:
c: str # error: [invalid-declaration]
d: int
reveal_type(Public.a) # revealed: int
reveal_type(Public.b) # revealed: Literal[2] | Any
reveal_type(Public.c) # revealed: Literal[3] | Unknown
@@ -143,9 +154,11 @@ error for both `a` and `b`:
```py
from typing import Any
def flag() -> bool:
return True
class Public:
if flag():
a: Any = 1
@@ -153,6 +166,7 @@ class Public:
else:
b: str
# error: [possibly-missing-attribute]
reveal_type(Public.a) # revealed: Literal[1] | Any
# error: [possibly-missing-attribute]
@@ -173,10 +187,12 @@ seems inconsistent when compared to the case just above.
def flag() -> bool:
return True
class Public:
if flag():
a: int
# TODO: this should raise an error. Once we fix this, update the section description and the table
# on top of this document.
reveal_type(Public.a) # revealed: int
@@ -202,6 +218,7 @@ class Public:
# Implicitly declared with `Unknown`, due to the usage of an unknown name in the annotation:
b: SomeUnknownName = 1 # error: [unresolved-reference]
reveal_type(Public.a) # revealed: Unknown | Literal[1]
reveal_type(Public.b) # revealed: Unknown
@@ -218,11 +235,13 @@ inconsistent when compared to the "possibly-undeclared-and-possibly-unbound" cas
def flag() -> bool:
return True
class Public:
if flag:
a = 1
b: SomeUnknownName = 1 # error: [unresolved-reference]
# TODO: these should raise an error. Once we fix this, update the section description and the table
# on top of this document.
reveal_type(Public.a) # revealed: Unknown | Literal[1]
@@ -241,6 +260,7 @@ class Public:
if False:
a: int = 1
# error: [unresolved-attribute]
reveal_type(Public.a) # revealed: Unknown

View File

@@ -3,9 +3,11 @@
```py
from typing import Callable
def _(c: Callable[[], int]):
reveal_type(c()) # revealed: int
def _(c: Callable[[int, str], int]):
reveal_type(c(1, "a")) # revealed: int

View File

@@ -6,6 +6,7 @@
class NotBool:
__bool__ = None
# error: [too-many-positional-arguments] "Too many positional arguments to class `bool`: expected 1, got 2"
bool(1, 2)
@@ -68,10 +69,12 @@ from enum import Enum
from types import FunctionType
from typing import TypeVar
class Answer(Enum):
NO = 0
YES = 1
reveal_type(isinstance(True, bool)) # revealed: Literal[True]
reveal_type(isinstance(True, int)) # revealed: Literal[True]
reveal_type(isinstance(True, object)) # revealed: Literal[True]
@@ -82,17 +85,27 @@ reveal_type(isinstance(Answer.NO, Answer)) # revealed: Literal[True]
reveal_type(isinstance((1, 2), tuple)) # revealed: Literal[True]
def f(): ...
reveal_type(isinstance(f, FunctionType)) # revealed: Literal[True]
reveal_type(isinstance("", int)) # revealed: bool
class A: ...
class SubclassOfA(A): ...
class OtherSubclassOfA(A): ...
class B: ...
reveal_type(isinstance(A, type)) # revealed: Literal[True]
a = A()
@@ -106,6 +119,7 @@ s = SubclassOfA()
reveal_type(isinstance(s, SubclassOfA)) # revealed: Literal[True]
reveal_type(isinstance(s, A)) # revealed: Literal[True]
def _(x: A | B, y: list[int]):
reveal_type(isinstance(y, list)) # revealed: Literal[True]
reveal_type(isinstance(x, A)) # revealed: bool
@@ -116,10 +130,12 @@ def _(x: A | B, y: list[int]):
reveal_type(x) # revealed: B & ~A
reveal_type(isinstance(x, B)) # revealed: Literal[True]
T = TypeVar("T")
T_bound_A = TypeVar("T_bound_A", bound=A)
T_constrained = TypeVar("T_constrained", SubclassOfA, OtherSubclassOfA)
def _(
x: T,
x_bound_a: T_bound_A,

View File

@@ -10,11 +10,14 @@ class Multiplier:
def __call__(self, number: int) -> int:
return number * self.factor
a = Multiplier(2)(3)
reveal_type(a) # revealed: int
class Unit: ...
b = Unit()(3.0) # error: "Object of type `Unit` is not callable"
reveal_type(b) # revealed: Unknown
```
@@ -25,6 +28,7 @@ reveal_type(b) # revealed: Unknown
def _(flag: bool):
class PossiblyNotCallable:
if flag:
def __call__(self) -> int:
return 1
@@ -38,6 +42,7 @@ def _(flag: bool):
```py
def _(flag: bool):
if flag:
class PossiblyUnbound:
def __call__(self) -> int:
return 1
@@ -53,6 +58,7 @@ def _(flag: bool):
class NonCallable:
__call__ = 1
a = NonCallable()
# error: [call-non-callable] "Object of type `Literal[1]` is not callable"
reveal_type(a()) # revealed: Unknown
@@ -66,6 +72,7 @@ def _(flag: bool):
if flag:
__call__ = 1
else:
def __call__(self) -> int:
return 1
@@ -83,6 +90,7 @@ class C:
def __call__(self, x: int) -> int:
return 1
c = C()
# error: 15 [invalid-argument-type] "Argument to bound method `__call__` is incorrect: Expected `int`, found `Literal["foo"]`"
@@ -97,6 +105,7 @@ class C:
def __call__(self: int) -> int:
return 1
c = C()
# error: 13 [invalid-argument-type] "Argument to bound method `__call__` is incorrect: Expected `int`, found `C`"
@@ -111,6 +120,7 @@ reveal_type(c()) # revealed: int
def outer(cond1: bool):
class Test:
if cond1:
def __call__(self): ...
class Other:

View File

@@ -16,10 +16,12 @@ the first argument:
from ty_extensions import CallableTypeOf
from typing import Callable
class C1:
def method(self: C1, x: int) -> str:
return str(x)
def _(
accessed_on_class: CallableTypeOf[C1.method],
accessed_on_instance: CallableTypeOf[C1().method],
@@ -37,9 +39,11 @@ class NonDescriptorCallable2:
def __call__(self, c2: C2, x: int) -> str:
return str(x)
class C2:
non_descriptor_callable: NonDescriptorCallable2 = NonDescriptorCallable2()
def _(
accessed_on_class: CallableTypeOf[C2.non_descriptor_callable],
accessed_on_instance: CallableTypeOf[C2().non_descriptor_callable],
@@ -55,9 +59,11 @@ class NonDescriptorCallable3:
def __call__(self, c3: C3, x: int) -> str:
return str(x)
class C3:
def method(self: C3, x: int) -> str:
return str(x)
non_descriptor_callable: NonDescriptorCallable3 = NonDescriptorCallable3()
callable_m: Callable[[C3, int], str] = method
@@ -113,9 +119,11 @@ intention that it shouldn't influence the method's descriptor behavior. For exam
```py
from typing import Callable
def memoize[**P, R](f: Callable[P, R]) -> Callable[P, R]:
raise NotImplementedError
class C1:
def method(self, x: int) -> str:
return str(x)
@@ -124,6 +132,7 @@ class C1:
def method_decorated(self, x: int) -> str:
return str(x)
C1().method(1)
C1().method_decorated(1)
@@ -135,11 +144,13 @@ This also works with an argumentless `Callable` annotation:
def memoize2(f: Callable) -> Callable:
raise NotImplementedError
class C2:
@memoize2
def method_decorated(self, x: int) -> str:
return str(x)
C2().method_decorated(1)
```
@@ -148,14 +159,17 @@ And with unions of `Callable` types:
```py
from typing import Callable
def expand(f: Callable[[C3, int], int]) -> Callable[[C3, int], int] | Callable[[C3, int], str]:
raise NotImplementedError
class C3:
@expand
def method_decorated(self, x: int) -> int:
return x
reveal_type(C3().method_decorated(1)) # revealed: int | str
```
@@ -167,11 +181,14 @@ but here we emit errors:
def memoize3(f: Callable[[C4, int], str]) -> Callable[[C4, int], str]:
raise NotImplementedError
class C4:
def method(self, x: int) -> str:
return str(x)
method_decorated = memoize3(method)
# error: [missing-argument]
# error: [invalid-argument-type]
C4().method_decorated(1)
@@ -194,12 +211,15 @@ class SquareCalculator:
def __call__(self, x: float) -> int:
return self.post_process(x * x)
def square_then(c: Callable[[float], int]) -> Callable[[float], int]:
return SquareCalculator(c)
class Calculator:
square_then_round = square_then(round)
reveal_type(Calculator().square_then_round(3.14)) # revealed: Unknown | int
```
@@ -212,12 +232,15 @@ example. We generally treat dunder attributes as bound-method descriptors since
```py
from typing import Callable
def pow_impl(tensor: Tensor, exponent: int) -> Tensor:
raise NotImplementedError
class Tensor:
__pow__: Callable[[Tensor, int], Tensor] = pow_impl
Tensor() ** 2
```
@@ -229,9 +252,11 @@ treat it as a bound-method descriptor:
def make_comparison_operator(name: str) -> Callable[[Matrix, Matrix], bool]:
raise NotImplementedError
class Matrix:
__lt__ = make_comparison_operator("lt")
Matrix() < Matrix()
```
@@ -245,14 +270,17 @@ function-like:
```py
from typing import Callable
def my_lossy_decorator(fn: Callable[..., int]) -> Callable[..., int]:
return fn
class MyClass:
@my_lossy_decorator
def method(self) -> int:
return 42
reveal_type(MyClass().method) # revealed: (...) -> int
reveal_type(MyClass().method.__name__) # revealed: str
```
@@ -267,9 +295,11 @@ behavior.
```py
from typing import Callable
def callable_identity[**P, R](func: Callable[P, R]) -> Callable[P, R]:
return func
class C:
@callable_identity
@classmethod
@@ -281,6 +311,7 @@ class C:
def f2(cls, x: int) -> str:
return "a"
# error: [too-many-positional-arguments]
# error: [invalid-argument-type]
C.f1(C, 1)
@@ -303,18 +334,22 @@ The callable type of a type object is not function-like.
from typing import ClassVar
from ty_extensions import CallableTypeOf
class WithNew:
def __new__(self, x: int) -> WithNew:
return super().__new__(WithNew)
class WithInit:
def __init__(self, x: int) -> None:
pass
class C:
with_new: ClassVar[CallableTypeOf[WithNew]]
with_init: ClassVar[CallableTypeOf[WithInit]]
C.with_new(1)
C().with_new(1)
C.with_init(1)

View File

@@ -45,6 +45,7 @@ reveal_type(object(1)) # revealed: object
```py
class Foo: ...
reveal_type(Foo()) # revealed: Foo
# error: [too-many-positional-arguments] "Too many positional arguments to bound method `__init__`: expected 1, got 2"
@@ -58,6 +59,7 @@ class Foo:
def __new__(cls, x: int) -> "Foo":
return object.__new__(cls)
reveal_type(Foo(1)) # revealed: Foo
# error: [missing-argument] "No argument provided for required parameter `x` of function `__new__`"
@@ -74,12 +76,15 @@ constructor from it.
```py
from typing_extensions import Self
class Base:
def __new__(cls, x: int) -> Self:
return cls()
class Foo(Base): ...
reveal_type(Foo(1)) # revealed: Foo
# error: [missing-argument] "No argument provided for required parameter `x` of function `__new__`"
@@ -94,8 +99,11 @@ reveal_type(Foo(1, 2)) # revealed: Foo
def _(flag: bool) -> None:
class Foo:
if flag:
def __new__(cls, x: int): ...
else:
def __new__(cls, x: int, y: int = 1): ...
reveal_type(Foo(1)) # revealed: Foo
@@ -118,13 +126,16 @@ class SomeCallable:
obj.x = x
return obj
class Descriptor:
def __get__(self, instance, owner) -> SomeCallable:
return SomeCallable()
class Foo:
__new__: Descriptor = Descriptor()
reveal_type(Foo(1)) # revealed: Foo
# error: [missing-argument] "No argument provided for required parameter `x` of bound method `__call__`"
reveal_type(Foo()) # revealed: Foo
@@ -139,9 +150,11 @@ class Callable:
def __call__(self, cls, x: int) -> "Foo":
return object.__new__(cls)
class Foo:
__new__ = Callable()
reveal_type(Foo(1)) # revealed: Foo
# error: [missing-argument] "No argument provided for required parameter `x` of bound method `__call__`"
reveal_type(Foo()) # revealed: Foo
@@ -155,6 +168,7 @@ reveal_type(Foo()) # revealed: Foo
def _(flag: bool) -> None:
class Foo:
if flag:
def __new__(cls):
return object.__new__(cls)
@@ -172,6 +186,7 @@ def _(flag: bool) -> None:
def _(flag: bool) -> None:
class Callable:
if flag:
def __call__(self, cls, x: int) -> "Foo":
return object.__new__(cls)
@@ -194,6 +209,7 @@ If the class has an `__init__` method, we can infer the signature of the constru
class Foo:
def __init__(self, x: int): ...
reveal_type(Foo(1)) # revealed: Foo
# error: [missing-argument] "No argument provided for required parameter `x` of bound method `__init__`"
@@ -211,8 +227,10 @@ constructor from it.
class Base:
def __init__(self, x: int): ...
class Foo(Base): ...
reveal_type(Foo(1)) # revealed: Foo
# error: [missing-argument] "No argument provided for required parameter `x` of bound method `__init__`"
@@ -227,8 +245,11 @@ reveal_type(Foo(1, 2)) # revealed: Foo
def _(flag: bool) -> None:
class Foo:
if flag:
def __init__(self, x: int): ...
else:
def __init__(self, x: int, y: int = 1): ...
reveal_type(Foo(1)) # revealed: Foo
@@ -253,13 +274,16 @@ class SomeCallable:
def __call__(self, x: int) -> str:
return "a"
class Descriptor:
def __get__(self, instance, owner) -> SomeCallable:
return SomeCallable()
class Foo:
__init__: Descriptor = Descriptor()
reveal_type(Foo(1)) # revealed: Foo
# error: [missing-argument] "No argument provided for required parameter `x` of bound method `__call__`"
reveal_type(Foo()) # revealed: Foo
@@ -274,9 +298,11 @@ class Callable:
def __call__(self, x: int) -> None:
pass
class Foo:
__init__ = Callable()
reveal_type(Foo(1)) # revealed: Foo
# error: [missing-argument] "No argument provided for required parameter `x` of bound method `__call__`"
reveal_type(Foo()) # revealed: Foo
@@ -288,6 +314,7 @@ reveal_type(Foo()) # revealed: Foo
def _(flag: bool) -> None:
class Callable:
if flag:
def __call__(self, x: int) -> None:
pass
@@ -320,6 +347,7 @@ class Foo:
def __init__(self, x: int): ...
# error: [missing-argument] "No argument provided for required parameter `x` of function `__new__`"
# error: [missing-argument] "No argument provided for required parameter `x` of bound method `__init__`"
reveal_type(Foo()) # revealed: Foo
@@ -340,6 +368,7 @@ class Foo:
def __init__(self, x: int) -> None:
self.x = x
# error: [missing-argument] "No argument provided for required parameter `x` of bound method `__init__`"
reveal_type(Foo()) # revealed: Foo
reveal_type(Foo(1)) # revealed: Foo
@@ -353,6 +382,7 @@ reveal_type(Foo(1, 2)) # revealed: Foo
```py
import abc
class Foo:
def __new__(cls) -> "Foo":
return object.__new__(cls)
@@ -360,12 +390,14 @@ class Foo:
def __init__(self, x):
self.x = 42
# error: [missing-argument] "No argument provided for required parameter `x` of bound method `__init__`"
reveal_type(Foo()) # revealed: Foo
# error: [too-many-positional-arguments] "Too many positional arguments to function `__new__`: expected 1, got 2"
reveal_type(Foo(42)) # revealed: Foo
class Foo2:
def __new__(cls, x) -> "Foo2":
return object.__new__(cls)
@@ -373,12 +405,14 @@ class Foo2:
def __init__(self):
pass
# error: [missing-argument] "No argument provided for required parameter `x` of function `__new__`"
reveal_type(Foo2()) # revealed: Foo2
# error: [too-many-positional-arguments] "Too many positional arguments to bound method `__init__`: expected 1, got 2"
reveal_type(Foo2(42)) # revealed: Foo2
class Foo3(metaclass=abc.ABCMeta):
def __new__(cls) -> "Foo3":
return object.__new__(cls)
@@ -386,12 +420,14 @@ class Foo3(metaclass=abc.ABCMeta):
def __init__(self, x):
self.x = 42
# error: [missing-argument] "No argument provided for required parameter `x` of bound method `__init__`"
reveal_type(Foo3()) # revealed: Foo3
# error: [too-many-positional-arguments] "Too many positional arguments to function `__new__`: expected 1, got 2"
reveal_type(Foo3(42)) # revealed: Foo3
class Foo4(metaclass=abc.ABCMeta):
def __new__(cls, x) -> "Foo4":
return object.__new__(cls)
@@ -399,6 +435,7 @@ class Foo4(metaclass=abc.ABCMeta):
def __init__(self):
pass
# error: [missing-argument] "No argument provided for required parameter `x` of function `__new__`"
reveal_type(Foo4()) # revealed: Foo4
@@ -415,6 +452,7 @@ meta-type, never on the type itself).
```py
from typing_extensions import Literal
class Meta(type):
def __new__(mcls, name, bases, namespace, /, **kwargs):
return super().__new__(mcls, name, bases, namespace)
@@ -422,8 +460,10 @@ class Meta(type):
def __lt__(cls, other) -> Literal[True]:
return True
class C(metaclass=Meta): ...
# No error is raised here, since we don't implicitly call `Meta.__new__`
reveal_type(C()) # revealed: C

View File

@@ -16,10 +16,12 @@ as the `instance` argument to `__get__`. A desugared version of `obj[key]` is ro
```py
from typing import Any
def find_name_in_mro(typ: type, name: str) -> Any:
# See implementation in https://docs.python.org/3/howto/descriptor.html#invocation-from-an-instance
pass
def getitem_desugared(obj: object, key: object) -> object:
getitem_callable = find_name_in_mro(type(obj), "__getitem__")
if hasattr(getitem_callable, "__get__"):
@@ -40,9 +42,11 @@ class Meta(type):
def __getitem__(cls, key: int) -> str:
return str(key)
class DunderOnMetaclass(metaclass=Meta):
pass
reveal_type(DunderOnMetaclass[0]) # revealed: str
```
@@ -53,6 +57,7 @@ class ClassWithNormalDunder:
def __getitem__(self, key: int) -> str:
return str(key)
# error: [not-subscriptable]
ClassWithNormalDunder[0]
```
@@ -68,6 +73,7 @@ class ClassWithNormalDunder:
def __getitem__(self, key: int) -> str:
return str(key)
class_with_normal_dunder = ClassWithNormalDunder()
reveal_type(class_with_normal_dunder[0]) # revealed: str
@@ -79,10 +85,12 @@ Which can be demonstrated by trying to attach a dunder method to an instance, wh
def external_getitem(instance, key: int) -> str:
return str(key)
class ThisFails:
def __init__(self):
self.__getitem__ = external_getitem
this_fails = ThisFails()
# error: [not-subscriptable] "Cannot subscript object of type `ThisFails` with no `__getitem__` method"
@@ -101,9 +109,11 @@ The instance-level method is also not called when the class-level method is pres
def external_getitem1(instance, key) -> str:
return "a"
def external_getitem2(key) -> int:
return 1
def _(flag: bool):
class ThisFails:
if flag:
@@ -129,9 +139,11 @@ Class-level annotations with no value assigned are considered to be accessible o
```py
from typing import Callable
class C:
__call__: Callable[..., None]
C()()
_: Callable[..., None] = C()
@@ -142,10 +154,12 @@ And of course the same is true if we have only an implicit assignment inside a m
```py
from typing import Callable
class C:
def __init__(self):
self.__call__ = lambda *a, **kw: None
# error: [call-non-callable]
C()()
@@ -162,9 +176,11 @@ class SomeCallable:
def __call__(self, key: int) -> str:
return str(key)
class ClassWithNonMethodDunder:
__getitem__: SomeCallable = SomeCallable()
class_with_callable_dunder = ClassWithNonMethodDunder()
reveal_type(class_with_callable_dunder[0]) # revealed: str
@@ -178,17 +194,21 @@ that the `instance` argument is on object of type `ClassWithDescriptorDunder`:
```py
from __future__ import annotations
class SomeCallable:
def __call__(self, key: int) -> str:
return str(key)
class Descriptor:
def __get__(self, instance: ClassWithDescriptorDunder, owner: type[ClassWithDescriptorDunder]) -> SomeCallable:
return SomeCallable()
class ClassWithDescriptorDunder:
__getitem__: Descriptor = Descriptor()
class_with_descriptor_dunder = ClassWithDescriptorDunder()
reveal_type(class_with_descriptor_dunder[0]) # revealed: str
@@ -208,6 +228,7 @@ class C:
# error: [invalid-assignment]
self.__getitem__ = None
# This is still fine, and simply calls the `__getitem__` method on the class
reveal_type(C()[0]) # revealed: str
```
@@ -218,9 +239,12 @@ reveal_type(C()[0]) # revealed: str
def _(flag: bool):
class C:
if flag:
def __getitem__(self, key: int) -> str:
return str(key)
else:
def __getitem__(self, key: int) -> bytes:
return bytes()
@@ -228,11 +252,13 @@ def _(flag: bool):
reveal_type(c[0]) # revealed: str | bytes
if flag:
class D:
def __getitem__(self, key: int) -> str:
return str(key)
else:
class D:
def __getitem__(self, key: int) -> bytes:
return bytes()
@@ -250,14 +276,17 @@ regular method calls.
def external_getitem(instance, key: int) -> str:
return str(key)
class NotSubscriptable1:
def __init__(self, value: int):
self.__getitem__ = external_getitem
class NotSubscriptable2:
def __init__(self, value: int):
self.__getitem__ = external_getitem
def _(union: NotSubscriptable1 | NotSubscriptable2):
# error: [not-subscriptable] "Cannot subscript object of type `NotSubscriptable2` with no `__getitem__` method"
# error: [not-subscriptable] "Cannot subscript object of type `NotSubscriptable1` with no `__getitem__` method"
@@ -270,6 +299,7 @@ def _(union: NotSubscriptable1 | NotSubscriptable2):
def _(flag: bool):
class C:
if flag:
def __getitem__(self, key: int) -> str:
return str(key)

View File

@@ -6,6 +6,7 @@
def get_int() -> int:
return 42
reveal_type(get_int()) # revealed: int
```
@@ -15,6 +16,7 @@ reveal_type(get_int()) # revealed: int
async def get_int_async() -> int:
return 42
reveal_type(get_int_async()) # revealed: CoroutineType[Any, Any, int]
```
@@ -29,6 +31,7 @@ python-version = "3.12"
def get_int[T]() -> int:
return 42
reveal_type(get_int()) # revealed: int
```
@@ -37,16 +40,20 @@ reveal_type(get_int()) # revealed: int
```py
from typing import Callable
def foo() -> int:
return 42
def decorator(func) -> Callable[[], int]:
return foo
@decorator
def bar() -> str:
return "bar"
reveal_type(bar()) # revealed: int
```
@@ -62,8 +69,10 @@ x = nonsense() # error: "Object of type `Literal[123]` is not callable"
```py
def _(flag: bool):
if flag:
def foo() -> int:
return 42
# error: [possibly-unresolved-reference]
reveal_type(foo()) # revealed: int
```
@@ -89,6 +98,7 @@ still continue to use the old convention, so it is supported by ty as well.
```py
def f(__x: int): ...
f(1)
# error: [positional-only-parameter-as-kwarg]
f(__x=1)
@@ -99,6 +109,7 @@ But not if they follow a non-positional-only parameter:
```py
def g(x: int, __y: str): ...
g(x=1, __y="foo")
```
@@ -107,6 +118,7 @@ And also not if they both start and end with `__`:
```py
def h(__x__: str): ...
h(__x__="foo")
```
@@ -115,6 +127,7 @@ And if *any* parameters use the new PEP-570 convention, the old convention does
```py
def i(x: str, /, __y: int): ...
i("foo", __y=42) # fine
```
@@ -125,11 +138,13 @@ class C:
def method(self, __x: int): ...
@classmethod
def class_method(cls, __x: str): ...
# (the name of the first parameter is irrelevant;
# a staticmethod works the same as a free function in the global scope)
@staticmethod
def static_method(self, __x: int): ...
# error: [positional-only-parameter-as-kwarg]
C().method(__x=1)
# error: [positional-only-parameter-as-kwarg]
@@ -153,8 +168,10 @@ def takes_at_least_one(x: int, *args) -> None: ...
def takes_at_least_two(x: int, y: int, *args) -> None: ...
def takes_at_least_two_positional_only(x: int, y: int, /, *args) -> None: ...
# Test all of the above with a number of different splatted argument types
def _(args: list[int]) -> None:
takes_zero(*args)
takes_one(*args)
@@ -169,6 +186,7 @@ def _(args: list[int]) -> None:
takes_at_least_two(*args)
takes_at_least_two_positional_only(*args)
def _(args: tuple[int, ...]) -> None:
takes_zero(*args)
takes_one(*args)
@@ -196,8 +214,10 @@ def takes_at_least_one(x: int, *args) -> None: ...
def takes_at_least_two(x: int, y: int, *args) -> None: ...
def takes_at_least_two_positional_only(x: int, y: int, /, *args) -> None: ...
# Test all of the above with a number of different splatted argument types
def _(args: tuple[int]) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args)
@@ -210,6 +230,7 @@ def _(args: tuple[int]) -> None:
takes_at_least_two(*args) # error: [missing-argument]
takes_at_least_two_positional_only(*args) # error: [missing-argument]
def _(args: tuple[int, int]) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args) # error: [too-many-positional-arguments]
@@ -222,6 +243,7 @@ def _(args: tuple[int, int]) -> None:
takes_at_least_two(*args)
takes_at_least_two_positional_only(*args)
def _(args: tuple[int, str]) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args) # error: [too-many-positional-arguments]
@@ -249,10 +271,13 @@ def takes_at_least_one(x: int, *args) -> None: ...
def takes_at_least_two(x: int, y: int, *args) -> None: ...
def takes_at_least_two_positional_only(x: int, y: int, /, *args) -> None: ...
# Test all of the above with a number of different splatted argument types
class SingleElementTuple(tuple[int]): ...
def _(args: SingleElementTuple) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
@@ -270,8 +295,10 @@ def _(args: SingleElementTuple) -> None:
takes_at_least_two(*args) # error: [missing-argument]
takes_at_least_two_positional_only(*args) # error: [missing-argument]
class TwoElementIntTuple(tuple[int, int]): ...
def _(args: TwoElementIntTuple) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args) # error: [too-many-positional-arguments]
@@ -284,8 +311,10 @@ def _(args: TwoElementIntTuple) -> None:
takes_at_least_two(*args)
takes_at_least_two_positional_only(*args)
class IntStrTuple(tuple[int, str]): ...
def _(args: IntStrTuple) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
@@ -326,8 +355,10 @@ def takes_at_least_one(x: int, *args) -> None: ...
def takes_at_least_two(x: int, y: int, *args) -> None: ...
def takes_at_least_two_positional_only(x: int, y: int, /, *args) -> None: ...
# Test all of the above with a number of different splatted argument types
def _(args: tuple[int, *tuple[int, ...]]) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args)
@@ -340,6 +371,7 @@ def _(args: tuple[int, *tuple[int, ...]]) -> None:
takes_at_least_two(*args)
takes_at_least_two_positional_only(*args)
def _(args: tuple[int, *tuple[str, ...]]) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args)
@@ -352,6 +384,7 @@ def _(args: tuple[int, *tuple[str, ...]]) -> None:
takes_at_least_two(*args) # error: [invalid-argument-type]
takes_at_least_two_positional_only(*args) # error: [invalid-argument-type]
def _(args: tuple[int, int, *tuple[int, ...]]) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args) # error: [too-many-positional-arguments]
@@ -364,6 +397,7 @@ def _(args: tuple[int, int, *tuple[int, ...]]) -> None:
takes_at_least_two(*args)
takes_at_least_two_positional_only(*args)
def _(args: tuple[int, int, *tuple[str, ...]]) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args) # error: [too-many-positional-arguments]
@@ -376,6 +410,7 @@ def _(args: tuple[int, int, *tuple[str, ...]]) -> None:
takes_at_least_two(*args)
takes_at_least_two_positional_only(*args)
def _(args: tuple[int, *tuple[int, ...], int]) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args) # error: [too-many-positional-arguments]
@@ -388,6 +423,7 @@ def _(args: tuple[int, *tuple[int, ...], int]) -> None:
takes_at_least_two(*args)
takes_at_least_two_positional_only(*args)
def _(args: tuple[int, *tuple[str, ...], int]) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args) # error: [too-many-positional-arguments]
@@ -420,10 +456,13 @@ def takes_at_least_one(x: int, *args) -> None: ...
def takes_at_least_two(x: int, y: int, *args) -> None: ...
def takes_at_least_two_positional_only(x: int, y: int, /, *args) -> None: ...
# Test all of the above with a number of different splatted argument types
class IntStarInt(tuple[int, *tuple[int, ...]]): ...
def _(args: IntStarInt) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args)
@@ -436,8 +475,10 @@ def _(args: IntStarInt) -> None:
takes_at_least_two(*args)
takes_at_least_two_positional_only(*args)
class IntStarStr(tuple[int, *tuple[str, ...]]): ...
def _(args: IntStarStr) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
@@ -460,8 +501,10 @@ def _(args: IntStarStr) -> None:
# error: [invalid-argument-type]
takes_at_least_two_positional_only(*args)
class IntIntStarInt(tuple[int, int, *tuple[int, ...]]): ...
def _(args: IntIntStarInt) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args) # error: [too-many-positional-arguments]
@@ -474,8 +517,10 @@ def _(args: IntIntStarInt) -> None:
takes_at_least_two(*args)
takes_at_least_two_positional_only(*args)
class IntIntStarStr(tuple[int, int, *tuple[str, ...]]): ...
def _(args: IntIntStarStr) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
@@ -497,8 +542,10 @@ def _(args: IntIntStarStr) -> None:
takes_at_least_two_positional_only(*args)
class IntStarIntInt(tuple[int, *tuple[int, ...], int]): ...
def _(args: IntStarIntInt) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args) # error: [too-many-positional-arguments]
@@ -511,8 +558,10 @@ def _(args: IntStarIntInt) -> None:
takes_at_least_two(*args)
takes_at_least_two_positional_only(*args)
class IntStarStrInt(tuple[int, *tuple[str, ...], int]): ...
def _(args: IntStarStrInt) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
@@ -542,6 +591,7 @@ def _(args: IntStarStrInt) -> None:
```py
from typing import Literal
def takes_zero() -> None: ...
def takes_one(x: str) -> None: ...
def takes_two(x: str, y: str) -> None: ...
@@ -553,8 +603,10 @@ def takes_at_least_one(x: str, *args) -> None: ...
def takes_at_least_two(x: str, y: str, *args) -> None: ...
def takes_at_least_two_positional_only(x: str, y: str, /, *args) -> None: ...
# Test all of the above with a number of different splatted argument types
def _(args: Literal["a"]) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args)
@@ -571,6 +623,7 @@ def _(args: Literal["a"]) -> None:
takes_at_least_two(*args) # error: [missing-argument]
takes_at_least_two_positional_only(*args) # error: [missing-argument]
def _(args: Literal["ab"]) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args) # error: [too-many-positional-arguments]
@@ -583,6 +636,7 @@ def _(args: Literal["ab"]) -> None:
takes_at_least_two(*args)
takes_at_least_two_positional_only(*args)
def _(args: Literal["abc"]) -> None:
takes_zero(*args) # error: [too-many-positional-arguments]
takes_one(*args) # error: [too-many-positional-arguments]
@@ -599,6 +653,7 @@ def _(args: Literal["abc"]) -> None:
takes_at_least_two(*args)
takes_at_least_two_positional_only(*args)
def _(args: str) -> None:
takes_zero(*args)
takes_one(*args)
@@ -620,6 +675,7 @@ def _(args: str) -> None:
def f(x: int) -> int:
return 1
# error: 15 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `Literal["foo"]`"
reveal_type(f("foo")) # revealed: int
```
@@ -630,6 +686,7 @@ reveal_type(f("foo")) # revealed: int
def f(x: int, /) -> int:
return 1
# error: 15 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `Literal["foo"]`"
reveal_type(f("foo")) # revealed: int
```
@@ -640,6 +697,7 @@ reveal_type(f("foo")) # revealed: int
def f(*args: int) -> int:
return 1
# error: 15 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `Literal["foo"]`"
reveal_type(f("foo")) # revealed: int
```
@@ -655,6 +713,7 @@ python-version = "3.11"
def f(*args: int) -> int:
return 1
def _(args: list[str]) -> None:
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `str`"
reveal_type(f(*args)) # revealed: int
@@ -701,6 +760,7 @@ A union of heterogeneous tuples provided to a variadic parameter:
# - <https://github.com/home-assistant/core/blob/bde4eb50111a72f9717fe73ee5929e50eb06911b/homeassistant/components/lovelace/websocket.py#L50-L59>
# - <https://github.com/pydata/xarray/blob/3572f4e70f2b12ef9935c1f8c3c1b74045d2a092/xarray/tests/test_groupby.py#L3058-L3059>
def f2(a: str, b: bool): ...
def f3(coinflip: bool):
if coinflip:
@@ -722,8 +782,10 @@ def f3(coinflip: bool):
# error: [invalid-argument-type] "Argument to function `f2` is incorrect: Expected `bool`, found `Literal[True] | tuple[Literal[True]]`"
f2(*other_args)
def f4(a=None, b=None, c=None, d=None, e=None): ...
my_args = ((1, 2), (3, 4), (5, 6))
for tup in my_args:
@@ -750,6 +812,7 @@ python-version = "3.11"
def f(x: int, *args: str) -> int:
return 1
def _(
args1: list[int],
args2: tuple[int],
@@ -785,6 +848,7 @@ def _(
def f(x: int) -> int:
return 1
# error: 15 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `Literal["foo"]`"
reveal_type(f(x="foo")) # revealed: int
```
@@ -795,6 +859,7 @@ reveal_type(f(x="foo")) # revealed: int
def f(*, x: int) -> int:
return 1
# error: 15 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `Literal["foo"]`"
reveal_type(f(x="foo")) # revealed: int
```
@@ -805,6 +870,7 @@ reveal_type(f(x="foo")) # revealed: int
def f(**kwargs: int) -> int:
return 1
# error: 15 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `Literal["foo"]`"
reveal_type(f(x="foo")) # revealed: int
```
@@ -815,6 +881,7 @@ reveal_type(f(x="foo")) # revealed: int
def f(x: int = 1, y: str = "foo") -> int:
return 1
# error: 15 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `str`, found `Literal[2]`"
# error: 20 [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `Literal["bar"]`"
reveal_type(f(y=2, x="bar")) # revealed: int
@@ -827,10 +894,16 @@ reveal_type(f(y=2, x="bar")) # revealed: int
```py
from typing import Sized
class Foo: ...
class Bar: ...
class Baz: ...
def f(x: Sized): ...
def g(
a: str | Foo,
@@ -852,6 +925,7 @@ def g(
def f() -> int:
return 1
# error: 15 [too-many-positional-arguments] "Too many positional arguments to function `f`: expected 0, got 1"
reveal_type(f("foo")) # revealed: int
```
@@ -862,6 +936,7 @@ reveal_type(f("foo")) # revealed: int
def f() -> int:
return 1
# error: 15 [too-many-positional-arguments] "Too many positional arguments to function `f`: expected 0, got 2"
reveal_type(f("foo", "bar")) # revealed: int
```
@@ -872,6 +947,7 @@ reveal_type(f("foo", "bar")) # revealed: int
def f(*args: int) -> int:
return 1
reveal_type(f(1, 2, 3)) # revealed: int
```
@@ -881,6 +957,7 @@ reveal_type(f(1, 2, 3)) # revealed: int
def f(**kwargs: int) -> int:
return 1
reveal_type(f(foo=1, bar=2)) # revealed: int
```
@@ -892,6 +969,7 @@ reveal_type(f(foo=1, bar=2)) # revealed: int
def f(x: int) -> int:
return 1
# error: 13 [missing-argument] "No argument provided for required parameter `x` of function `f`"
reveal_type(f()) # revealed: int
```
@@ -902,6 +980,7 @@ reveal_type(f()) # revealed: int
def f(x: int, y: str = "foo") -> int:
return 1
# error: 13 [missing-argument] "No argument provided for required parameter `x` of function `f`"
reveal_type(f()) # revealed: int
```
@@ -912,6 +991,7 @@ reveal_type(f()) # revealed: int
def f(x: int = 1) -> int:
return 1
reveal_type(f()) # revealed: int
```
@@ -921,6 +1001,7 @@ reveal_type(f()) # revealed: int
def f(x: int, *y: str) -> int:
return 1
# error: 13 [missing-argument] "No argument provided for required parameter `x` of function `f`"
reveal_type(f()) # revealed: int
```
@@ -931,6 +1012,7 @@ reveal_type(f()) # revealed: int
def f(*args: int) -> int:
return 1
reveal_type(f()) # revealed: int
```
@@ -940,6 +1022,7 @@ reveal_type(f()) # revealed: int
def f(**kwargs: int) -> int:
return 1
reveal_type(f()) # revealed: int
```
@@ -949,6 +1032,7 @@ reveal_type(f()) # revealed: int
def f(x: int, y: int) -> int:
return 1
# error: 13 [missing-argument] "No arguments provided for required parameters `x`, `y` of function `f`"
reveal_type(f()) # revealed: int
```
@@ -959,6 +1043,7 @@ reveal_type(f()) # revealed: int
def f(x: int) -> int:
return 1
# error: 20 [unknown-argument] "Argument `y` does not match any known parameter of function `f`"
reveal_type(f(x=1, y=2)) # revealed: int
```
@@ -969,6 +1054,7 @@ reveal_type(f(x=1, y=2)) # revealed: int
def f(x: int) -> int:
return 1
# error: 18 [parameter-already-assigned] "Multiple values provided for parameter `x` of function `f`"
reveal_type(f(1, x=2)) # revealed: int
```
@@ -1044,6 +1130,7 @@ def empty() -> None: ...
def _(kwargs: dict[str, int]) -> None:
empty(**kwargs)
empty(**{})
empty(**dict())
```
@@ -1053,15 +1140,19 @@ empty(**dict())
```py
from typing_extensions import TypedDict
def f(**kwargs: int) -> None: ...
class Foo(TypedDict):
a: int
b: int
def _(kwargs: dict[str, int]) -> None:
f(**kwargs)
f(**{"foo": 1})
f(**dict(foo=1))
f(**Foo(a=1, b=2))
@@ -1085,14 +1176,17 @@ def _(kwargs: dict[str, int]) -> None:
```py
from typing_extensions import TypedDict
class Foo(TypedDict):
a: int
b: int
def f(a: int, b: int) -> None: ...
def _(kwargs: dict[str, int]) -> None:
f(**kwargs)
f(**{"a": 1, "b": 2})
f(**dict(a=1, b=2))
f(**Foo(a=1, b=2))
@@ -1103,14 +1197,17 @@ f(**Foo(a=1, b=2))
```py
from typing_extensions import TypedDict
class Foo(TypedDict):
a: int
b: int
def f(*, a: int, b: int) -> None: ...
def _(kwargs: dict[str, int]) -> None:
f(**kwargs)
f(**{"a": 1, "b": 2})
f(**dict(a=1, b=2))
f(**Foo(a=1, b=2))
@@ -1135,6 +1232,7 @@ def _(kwargs1: dict[str, int], kwargs2: dict[str, int], kwargs3: dict[str, str],
```py
class B: ...
def f(*, a: int, b: B, **kwargs: int) -> None: ...
def _(kwargs: dict[str, int]):
# Make sure that the `b` argument is not being matched against `kwargs` by passing an integer
@@ -1160,16 +1258,20 @@ def _(kwargs1: dict[str, int], kwargs2: dict[str, str]):
```py
from typing_extensions import NotRequired, TypedDict
class Foo1(TypedDict):
a: int
b: str
class Foo2(TypedDict):
a: int
b: NotRequired[str]
def f(**kwargs: int) -> None: ...
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `str`"
f(**Foo1(a=1, b="b"))
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `int`, found `str`"
@@ -1183,11 +1285,16 @@ The keys of the mapping passed to a double-starred argument must be strings.
```py
from collections.abc import Mapping
def f(**kwargs: int) -> None: ...
class DictSubclass(dict[int, int]): ...
class MappingSubclass(Mapping[int, int]): ...
class MappingProtocol:
def keys(self) -> list[int]:
return [1]
@@ -1195,10 +1302,12 @@ class MappingProtocol:
def __getitem__(self, key: int) -> int:
return 1
def _(kwargs: dict[int, int]) -> None:
# error: [invalid-argument-type] "Argument expression after ** must be a mapping with `str` key type: Found `int`"
f(**kwargs)
# error: [invalid-argument-type] "Argument expression after ** must be a mapping with `str` key type: Found `int`"
f(**DictSubclass())
# error: [invalid-argument-type] "Argument expression after ** must be a mapping with `str` key type: Found `int`"
@@ -1211,8 +1320,11 @@ The key can also be a custom type that inherits from `str`.
```py
class SubStr(str): ...
class SubInt(int): ...
def _(kwargs1: dict[SubStr, int], kwargs2: dict[SubInt, int]) -> None:
f(**kwargs1)
# error: [invalid-argument-type] "Argument expression after ** must be a mapping with `str` key type: Found `SubInt`"
@@ -1225,6 +1337,7 @@ Or, it can be a type that is assignable to `str`.
from typing import Any
from ty_extensions import Unknown
def _(kwargs1: dict[Any, int], kwargs2: dict[Unknown, int]) -> None:
f(**kwargs1)
f(**kwargs2)
@@ -1235,11 +1348,16 @@ def _(kwargs1: dict[Any, int], kwargs2: dict[Unknown, int]) -> None:
```py
from collections.abc import Mapping
def f(**kwargs: str) -> None: ...
class DictSubclass(dict[str, int]): ...
class MappingSubclass(Mapping[str, int]): ...
class MappingProtocol:
def keys(self) -> list[str]:
return ["foo"]
@@ -1247,6 +1365,7 @@ class MappingProtocol:
def __getitem__(self, key: str) -> int:
return 1
def _(kwargs: dict[str, int]) -> None:
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `str`, found `int`"
f(**kwargs)
@@ -1263,6 +1382,7 @@ def _(kwargs: dict[str, int]) -> None:
```py
from ty_extensions import Unknown
def f(**kwargs: int) -> None: ...
def _(kwargs: Unknown):
f(**kwargs)
@@ -1273,8 +1393,10 @@ def _(kwargs: Unknown):
```py
def f(**kwargs: int) -> None: ...
class A: ...
class InvalidMapping:
def keys(self) -> A:
return A()
@@ -1282,6 +1404,7 @@ class InvalidMapping:
def __getitem__(self, key: str) -> int:
return 1
def _(kwargs: dict[str, int] | int):
# error: [invalid-argument-type] "Argument expression after ** must be a mapping type: Found `dict[str, int] | int`"
f(**kwargs)
@@ -1299,9 +1422,11 @@ from typing import TypeVar
_T = TypeVar("_T")
def f(**kwargs: _T) -> _T:
return kwargs["a"]
def _(kwargs: dict[str, int]) -> None:
reveal_type(f(**kwargs)) # revealed: int
```
@@ -1314,12 +1439,15 @@ from typing_extensions import TypedDict
_T = TypeVar("_T")
class Foo(TypedDict):
a: int
b: str
def f(**kwargs: _T) -> _T:
return kwargs["a"]
reveal_type(f(**Foo(a=1, b="b"))) # revealed: int | str
```

View File

@@ -10,10 +10,12 @@ Consider the following example:
```py
import inspect
class Descriptor:
def __get__(self, instance, owner) -> str:
return "a"
class C:
normal: int = 1
descriptor: Descriptor = Descriptor()
@@ -70,6 +72,7 @@ class D:
def __init__(self) -> None:
self.instance_attr: int = 1
reveal_type(inspect.getattr_static(D(), "instance_attr")) # revealed: int
```
@@ -79,8 +82,10 @@ And attributes on metaclasses can be accessed when probing the class:
class Meta(type):
attr: int = 1
class E(metaclass=Meta): ...
reveal_type(inspect.getattr_static(E, "attr")) # revealed: int
```
@@ -98,9 +103,11 @@ back to `Any`:
```py
import inspect
class C:
x: int = 1
def _(attr_name: str):
reveal_type(inspect.getattr_static(C(), attr_name)) # revealed: Any
reveal_type(inspect.getattr_static(C(), attr_name, 1)) # revealed: Any
@@ -127,6 +134,7 @@ inspect.getattr_static(C(), "x", "default-arg", "one too many")
```py
import inspect
def _(flag: bool):
class C:
if flag:
@@ -141,6 +149,7 @@ def _(flag: bool):
import inspect
from typing import Any
def _(a: Any, tuple_of_any: tuple[Any]):
reveal_type(inspect.getattr_static(a, "x", "default")) # revealed: Any | Literal["default"]

View File

@@ -83,6 +83,7 @@ When we access methods from derived classes, they will be bound to instances of
class D(C):
pass
reveal_type(D().f) # revealed: bound method D.f(x: int) -> str
```
@@ -107,10 +108,12 @@ class Base:
def method_on_base(self, x: int | None) -> str:
return "a"
class Derived(Base):
def method_on_derived(self, x: bytes) -> tuple[int, str]:
return (1, "a")
reveal_type(Base().method_on_base(1)) # revealed: str
reveal_type(Base.method_on_base(Base(), 1)) # revealed: str
@@ -159,6 +162,7 @@ reveal_type(b"abcde".startswith(b"abc")) # revealed: bool
```py
from typing_extensions import LiteralString
def f(s: LiteralString) -> None:
reveal_type(s.find("a")) # revealed: int
```
@@ -175,14 +179,17 @@ def f(t: tuple[int, str]) -> None:
```py
from typing import Any
class A:
def f(self) -> int:
return 1
class B:
def f(self) -> str:
return "a"
def f(a_or_b: A | B, any_or_a: Any | A):
reveal_type(a_or_b.f) # revealed: (bound method A.f() -> int) | (bound method B.f() -> str)
reveal_type(a_or_b.f()) # revealed: int | str
@@ -216,14 +223,18 @@ class:
from typing import Protocol, Literal
from ty_extensions import AlwaysFalsy
class Foo: ...
class SupportsStr(Protocol):
def __str__(self) -> str: ...
class Falsy(Protocol):
def __bool__(self) -> Literal[False]: ...
def _(a: object, b: SupportsStr, c: Falsy, d: AlwaysFalsy, e: None, f: Foo | None):
a.__str__()
b.__str__()
@@ -253,10 +264,12 @@ Here, we test that this signature is enforced correctly:
```py
from inspect import getattr_static
class C:
def f(self, x: int) -> str:
return "a"
method_wrapper = getattr_static(C, "f").__get__
reveal_type(method_wrapper) # revealed: <method-wrapper '__get__' of function 'f'>
@@ -298,13 +311,16 @@ the class itself. This also creates a bound method that is bound to the class ob
```py
from __future__ import annotations
class Meta(type):
def f(cls, arg: int) -> str:
return "a"
class C(metaclass=Meta):
pass
reveal_type(C.f) # revealed: bound method <class 'C'>.f(arg: int) -> str
reveal_type(C.f(1)) # revealed: str
```
@@ -321,10 +337,12 @@ A metaclass function can be shadowed by a method on the class:
```py
from typing import Any, Literal
class D(metaclass=Meta):
def f(arg: int) -> Literal["a"]:
return "a"
reveal_type(D.f(1)) # revealed: Literal["a"]
```
@@ -334,11 +352,14 @@ If the class method is possibly missing, we union the return types:
def flag() -> bool:
return True
class E(metaclass=Meta):
if flag():
def f(arg: int) -> Any:
return "a"
reveal_type(E.f(1)) # revealed: str | Any
```
@@ -352,11 +373,13 @@ the class object itself:
```py
from __future__ import annotations
class C:
@classmethod
def f(cls: type[C], x: int) -> str:
return "a"
reveal_type(C.f) # revealed: bound method <class 'C'>.f(x: int) -> str
reveal_type(C().f) # revealed: bound method type[C].f(x: int) -> str
```
@@ -385,6 +408,7 @@ class D:
# This function is wrongly annotated, it should be `type[D]` instead of `D`
pass
# error: [invalid-argument-type] "Argument to bound method `f` is incorrect: Expected `D`, found `<class 'D'>`"
D.f()
```
@@ -395,6 +419,7 @@ When a class method is accessed on a derived class, it is bound to that derived
class Derived(C):
pass
reveal_type(Derived.f) # revealed: bound method <class 'Derived'>.f(x: int) -> str
reveal_type(Derived().f) # revealed: bound method type[Derived].f(x: int) -> str
@@ -410,10 +435,12 @@ currently don't model this explicitly:
```py
from inspect import getattr_static
class C:
@classmethod
def f(cls): ...
reveal_type(getattr_static(C, "f")) # revealed: def f(cls) -> Unknown
# revealed: <method-wrapper '__get__' of function 'f'>
reveal_type(getattr_static(C, "f").__get__)
@@ -447,6 +474,7 @@ class method:
def does_nothing[T](f: T) -> T:
return f
class C:
@classmethod
@does_nothing
@@ -458,28 +486,254 @@ class C:
def f2(cls, x: int) -> str:
return "a"
reveal_type(C.f1(1)) # revealed: str
reveal_type(C().f1(1)) # revealed: str
reveal_type(C.f2(1)) # revealed: str
reveal_type(C().f2(1)) # revealed: str
```
### Classmethods with `Self` and callable-returning decorators
When a classmethod is decorated with a decorator that returns a callable type (like
`@contextmanager`), `Self` in the return type should correctly resolve to the subclass when accessed
on a derived class.
```py
from contextlib import contextmanager
from typing import Iterator
from typing_extensions import Self
class Base:
@classmethod
@contextmanager
def create(cls) -> Iterator[Self]:
yield cls()
class Child(Base): ...
reveal_type(Base.create()) # revealed: _GeneratorContextManager[Base, None, None]
with Base.create() as base:
reveal_type(base) # revealed: Base
reveal_type(Base().create()) # revealed: _GeneratorContextManager[Base, None, None]
with Base().create() as base:
reveal_type(base) # revealed: Base
reveal_type(Child.create()) # revealed: _GeneratorContextManager[Child, None, None]
with Child.create() as child:
reveal_type(child) # revealed: Child
reveal_type(Child().create()) # revealed: _GeneratorContextManager[Child, None, None]
with Child().create() as child:
reveal_type(child) # revealed: Child
```
### `__init_subclass__`
The [`__init_subclass__`] method is implicitly a classmethod:
```toml
[environment]
python-version = "3.12"
```
```py
class Base:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
cls.custom_attribute: int = 0
class Derived(Base):
pass
reveal_type(Derived.custom_attribute) # revealed: int
```
Subclasses must be constructed with arguments matching the required arguments of the base
`__init_subclass__` method.
```py
class Empty: ...
class RequiresArg:
def __init_subclass__(cls, arg: int): ...
class NoArg:
def __init_subclass__(cls): ...
# Single-base definitions
class MissingArg(RequiresArg): ... # error: [missing-argument]
class InvalidType(RequiresArg, arg="foo"): ... # error: [invalid-argument-type]
class Valid(RequiresArg, arg=1): ...
# error: [missing-argument]
# error: [unknown-argument]
class IncorrectArg(RequiresArg, not_arg="foo"): ...
```
For multiple inheritance, the first resolved `__init_subclass__` method is used.
```py
class Empty: ...
class RequiresArg:
def __init_subclass__(cls, arg: int): ...
class NoArg:
def __init_subclass__(cls): ...
class Valid(NoArg, RequiresArg): ...
class MissingArg(RequiresArg, NoArg): ... # error: [missing-argument]
class InvalidType(RequiresArg, NoArg, arg="foo"): ... # error: [invalid-argument-type]
class Valid(RequiresArg, NoArg, arg=1): ...
# Ensure base class without __init_subclass__ is ignored
class Valid(Empty, NoArg): ...
class Valid(Empty, RequiresArg, NoArg, arg=1): ...
class MissingArg(Empty, RequiresArg): ... # error: [missing-argument]
class MissingArg(Empty, RequiresArg, NoArg): ... # error: [missing-argument]
class InvalidType(Empty, RequiresArg, NoArg, arg="foo"): ... # error: [invalid-argument-type]
# Multiple inheritance with args
class Base(Empty, RequiresArg, NoArg, arg=1): ...
class Valid(Base, arg=1): ...
class MissingArg(Base): ... # error: [missing-argument]
class InvalidType(Base, arg="foo"): ... # error: [invalid-argument-type]
```
Keyword splats are allowed if their type can be determined:
```py
from typing import TypedDict
class RequiresKwarg:
def __init_subclass__(cls, arg: int): ...
class WrongArg(TypedDict):
kwarg: int
class InvalidType(TypedDict):
arg: str
wrong_arg: WrongArg = {"kwarg": 5}
# error: [missing-argument]
# error: [unknown-argument]
class MissingArg(RequiresKwarg, **wrong_arg): ...
invalid_type: InvalidType = {"arg": "foo"}
# error: [invalid-argument-type]
class InvalidType(RequiresKwarg, **invalid_type): ...
```
So are generics:
```py
from typing import Generic, TypeVar, Literal, overload
class Base[T]:
def __init_subclass__(cls, arg: T): ...
class Valid(Base[int], arg=1): ...
class InvalidType(Base[int], arg="x"): ... # error: [invalid-argument-type]
# Old generic syntax
T = TypeVar("T")
class Base(Generic[T]):
def __init_subclass__(cls, arg: T) -> None: ...
class Valid(Base[int], arg=1): ...
class InvalidType(Base[int], arg="x"): ... # error: [invalid-argument-type]
```
So are overloads:
```py
class Base:
@overload
def __init_subclass__(cls, mode: Literal["a"], arg: int) -> None: ...
@overload
def __init_subclass__(cls, mode: Literal["b"], arg: str) -> None: ...
def __init_subclass__(cls, mode: str, arg: int | str) -> None: ...
class Valid(Base, mode="a", arg=5): ...
class Valid(Base, mode="b", arg="foo"): ...
class InvalidType(Base, mode="b", arg=5): ... # error: [no-matching-overload]
```
The `metaclass` keyword is ignored, as it has special meaning and is not passed to
`__init_subclass__` at runtime.
```py
class Base:
def __init_subclass__(cls, arg: int): ...
class Valid(Base, arg=5, metaclass=object): ...
```
## `@staticmethod`
### Basic
@@ -490,11 +744,13 @@ true whether it's accessed on the class or on an instance of the class.
```py
from __future__ import annotations
class C:
@staticmethod
def f(x: int) -> str:
return "a"
reveal_type(C.f) # revealed: def f(x: int) -> str
reveal_type(C().f) # revealed: def f(x: int) -> str
```
@@ -521,6 +777,7 @@ When a static method is accessed on a derived class, it behaves identically:
class Derived(C):
pass
reveal_type(Derived.f) # revealed: def f(x: int) -> str
reveal_type(Derived().f) # revealed: def f(x: int) -> str
@@ -533,6 +790,7 @@ reveal_type(Derived().f(1)) # revealed: str
```py
from inspect import getattr_static
class C:
@staticmethod
def f(): ...
@@ -568,9 +826,11 @@ static method:
```py
from __future__ import annotations
def does_nothing[T](f: T) -> T:
return f
class C:
@staticmethod
@does_nothing
@@ -582,6 +842,7 @@ class C:
def f2(x: int) -> str:
return "a"
reveal_type(C.f1(1)) # revealed: str
reveal_type(C().f1(1)) # revealed: str
reveal_type(C.f2(1)) # revealed: str
@@ -595,6 +856,7 @@ bind `self`:
from contextlib import contextmanager
from collections.abc import Iterator
class D:
@staticmethod
@contextmanager
@@ -606,6 +868,7 @@ class D:
with self.ctx(10) as x:
reveal_type(x) # revealed: int
# Accessing via class works
reveal_type(D.ctx(5)) # revealed: _GeneratorContextManager[int, None, None]
@@ -621,17 +884,18 @@ argument:
```py
from typing_extensions import Self
reveal_type(object.__new__) # revealed: def __new__(cls) -> Self@__new__
reveal_type(object().__new__) # revealed: def __new__(cls) -> Self@__new__
# revealed: Overload[(cls, x: str | Buffer | SupportsInt | SupportsIndex | SupportsTrunc = 0, /) -> Self@__new__, (cls, x: str | bytes | bytearray, /, base: SupportsIndex) -> Self@__new__]
reveal_type(object.__new__) # revealed: def __new__[Self](cls) -> Self
reveal_type(object().__new__) # revealed: def __new__[Self](cls) -> Self
# revealed: Overload[[Self](cls, x: str | Buffer | SupportsInt | SupportsIndex | SupportsTrunc = 0, /) -> Self, [Self](cls, x: str | bytes | bytearray, /, base: SupportsIndex) -> Self]
reveal_type(int.__new__)
# revealed: Overload[(cls, x: str | Buffer | SupportsInt | SupportsIndex | SupportsTrunc = 0, /) -> Self@__new__, (cls, x: str | bytes | bytearray, /, base: SupportsIndex) -> Self@__new__]
# revealed: Overload[[Self](cls, x: str | Buffer | SupportsInt | SupportsIndex | SupportsTrunc = 0, /) -> Self, [Self](cls, x: str | bytes | bytearray, /, base: SupportsIndex) -> Self]
reveal_type((42).__new__)
class X:
def __init__(self, val: int): ...
def make_another(self) -> Self:
reveal_type(self.__new__) # revealed: def __new__(cls) -> Self@__new__
reveal_type(self.__new__) # revealed: def __new__[Self](cls) -> Self
return self.__new__(type(self))
```
@@ -645,8 +909,10 @@ import types
from typing import Callable
from ty_extensions import static_assert, CallableTypeOf, is_assignable_to, TypeOf
def f(obj: type) -> None: ...
class MyClass:
@property
def my_property(self) -> int:
@@ -655,6 +921,7 @@ class MyClass:
@my_property.setter
def my_property(self, value: int | str) -> None: ...
static_assert(is_assignable_to(types.FunctionType, Callable))
# revealed: <wrapper-descriptor '__get__' of 'function' objects>
@@ -706,6 +973,7 @@ static_assert(is_assignable_to(TypeOf[str.startswith], Callable))
reveal_type("foo".startswith)
static_assert(is_assignable_to(TypeOf["foo".startswith], Callable))
def _(
a: CallableTypeOf[types.FunctionType.__get__],
b: CallableTypeOf[f],

View File

@@ -5,6 +5,7 @@ The type `Never` is callable with an arbitrary set of arguments. The result is a
```py
from typing_extensions import Never
def f(never: Never):
reveal_type(never()) # revealed: Never
reveal_type(never(1)) # revealed: Never

View File

@@ -15,6 +15,7 @@ reveal_type(open("", "rb")) # revealed: BufferedReader[_BufferedReaderStream]
with open("foo.pickle", "rb") as f:
x = pickle.load(f) # fine
def _(mode: str):
reveal_type(open("", mode)) # revealed: IO[Any]
```

View File

@@ -174,6 +174,7 @@ def f(x: C) -> C: ...
```py
from overloaded import A, B, C, f
def _(ab: A | B, ac: A | C, bc: B | C):
reveal_type(f(ab)) # revealed: A | B
reveal_type(f(*(ab,))) # revealed: A | B
@@ -213,6 +214,7 @@ def f(x: B, y: D) -> D: ...
```py
from overloaded import A, B, C, D, f
def _(a_b: A | B):
reveal_type(f(a_b, C())) # revealed: A | C
reveal_type(f(*(a_b, C()))) # revealed: A | C
@@ -220,6 +222,7 @@ def _(a_b: A | B):
reveal_type(f(a_b, D())) # revealed: B | D
reveal_type(f(*(a_b, D()))) # revealed: B | D
# But, if it doesn't, it should expand the second argument and try again:
def _(a_b: A | B, c_d: C | D):
reveal_type(f(a_b, c_d)) # revealed: A | B | C | D
@@ -252,6 +255,7 @@ def f(x: B, y: D) -> D: ...
```py
from overloaded import A, B, C, D, f
def _(a: A, bc: B | C, cd: C | D):
# This also tests that partial matching works correctly as the argument type expansion results
# in matching the first and second overloads, but not the third one.
@@ -285,6 +289,7 @@ def f(x: _T) -> _T: ...
```py
from overloaded import A, f
def _(x: int, y: A | int):
reveal_type(f(x)) # revealed: int
reveal_type(f(*(x,))) # revealed: int
@@ -317,6 +322,7 @@ def f[T](x: T) -> T: ...
```py
from overloaded import B, f
def _(x: int, y: B | int):
reveal_type(f(x)) # revealed: int
reveal_type(f(*(x,))) # revealed: int
@@ -344,6 +350,7 @@ def f(x: Literal[False]) -> F: ...
```py
from overloaded import f
def _(flag: bool):
reveal_type(f(True)) # revealed: T
reveal_type(f(*(True,))) # revealed: T
@@ -380,6 +387,7 @@ def f(x: tuple[B, int], y: tuple[int, Literal[False]]) -> D: ...
```py
from overloaded import A, B, f
def _(x: tuple[A | B, int], y: tuple[int, bool]):
reveal_type(f(x, y)) # revealed: A | B | C | D
reveal_type(f(*(x, y))) # revealed: A | B | C | D
@@ -407,6 +415,7 @@ def f(x: type[B]) -> B: ...
```py
from overloaded import A, B, f
def _(x: type[A | B]):
reveal_type(x) # revealed: type[A] | type[B]
reveal_type(f(x)) # revealed: A | B
@@ -445,6 +454,7 @@ def f(x: Literal[SomeEnum.C]) -> C: ...
from typing import Literal
from overloaded import SomeEnum, A, B, C, f
def _(x: SomeEnum, y: Literal[SomeEnum.A, SomeEnum.C]):
reveal_type(f(SomeEnum.A)) # revealed: A
reveal_type(f(*(SomeEnum.A,))) # revealed: A
@@ -498,6 +508,7 @@ reveal_type(f(b=0)) # revealed: OnlyBSpecified
f(a=0, b=0) # error: [no-matching-overload]
def _(missing: Literal[Missing.Value], missing_or_present: Literal[Missing.Value] | int):
reveal_type(f(a=missing, b=missing)) # revealed: BothMissing
reveal_type(f(a=missing)) # revealed: BothMissing
@@ -546,6 +557,7 @@ def f(x: MyEnumSubclass) -> MyEnumSubclass: ...
```py
from overloaded import MyEnumSubclass, ActualEnum, f
def _(actual_enum: ActualEnum, my_enum_instance: MyEnumSubclass):
reveal_type(f(actual_enum)) # revealed: Both
reveal_type(f(*(actual_enum,))) # revealed: Both
@@ -584,6 +596,7 @@ def f(x: B) -> B: ...
```py
from overloaded import A, B, C, D, f
def _(ab: A | B, ac: A | C, cd: C | D):
reveal_type(f(ab)) # revealed: A | B
reveal_type(f(*(ab,))) # revealed: A | B
@@ -644,6 +657,7 @@ class Foo:
from overloaded import A, B, C, Foo, f
from typing_extensions import Any, reveal_type
def _(ab: A | B, a: int | Any):
reveal_type(f(a1=a, a2=a, a3=a)) # revealed: C
reveal_type(f(A(), a1=a, a2=a, a3=a)) # revealed: A
@@ -732,6 +746,7 @@ def _(ab: A | B, a: int | Any):
)
)
def _(foo: Foo, ab: A | B, a: int | Any):
reveal_type(foo.f(a1=a, a2=a, a3=a)) # revealed: C
reveal_type(foo.f(A(), a1=a, a2=a, a3=a)) # revealed: A
@@ -843,6 +858,7 @@ def f(x: B, /, **kwargs: int) -> B: ...
from overloaded import A, B, f
from typing_extensions import reveal_type
def _(a: int | None):
reveal_type(
# error: [no-matching-overload]
@@ -906,17 +922,20 @@ from overloaded import f
# Test all of the above with a number of different splatted argument types
def _(t: tuple[int, str]) -> None:
# This correctly produces an error because the first element of the union has a precise arity of
# 2, which matches the first overload, but the second element of the tuple doesn't match the
# second parameter type, yielding an `invalid-argument-type` error.
f(*t) # error: [invalid-argument-type]
def _(t: tuple[int, str, int]) -> None:
# This correctly produces no error because the first element of the union has a precise arity of
# 3, which matches the second overload.
f(*t)
def _(t: tuple[int, str] | tuple[int, str, int]) -> None:
# This produces an error because the expansion produces two argument lists: `[*tuple[int, str]]`
# and `[*tuple[int, str, int]]`. The first list produces produces a type checking error as
@@ -954,6 +973,7 @@ def f(*args: int) -> int: ...
```py
from overloaded import f
def _(x1: int, x2: int, args: list[int]):
reveal_type(f(x1)) # revealed: tuple[int]
reveal_type(f(x1, x2)) # revealed: tuple[int, int]
@@ -986,6 +1006,7 @@ def f(x1: int, *args: int) -> tuple[int, ...]: ...
```py
from overloaded import f
def _(x1: int, x2: int, args1: list[int], args2: tuple[int, *tuple[int, ...]]):
reveal_type(f(x1, x2)) # revealed: tuple[int, int]
reveal_type(f(*(x1, x2))) # revealed: tuple[int, int]
@@ -1013,6 +1034,7 @@ def f(**kwargs: int) -> int: ...
```py
from overloaded import f
def _(x1: int, x2: int, kwargs: dict[str, int]):
reveal_type(f(x1=x1)) # revealed: int
reveal_type(f(x1=x1, x2=x2)) # revealed: tuple[int, int]
@@ -1044,10 +1066,12 @@ def f(**kwargs: int) -> tuple[int, ...]: ...
from typing import TypedDict
from overloaded import f
class Foo(TypedDict):
x: int
y: int
def _(foo: Foo, kwargs: dict[str, int]):
reveal_type(f(**foo)) # revealed: tuple[int, int]
reveal_type(f(**kwargs)) # revealed: tuple[int, ...]
@@ -1089,6 +1113,7 @@ from overloaded import f
reveal_type(f(1)) # revealed: str
reveal_type(f(*(1,))) # revealed: str
def _(list_int: list[int], list_any: list[Any]):
reveal_type(f(list_int)) # revealed: int
reveal_type(f(*(list_int,))) # revealed: int
@@ -1124,6 +1149,7 @@ from overloaded import f
reveal_type(f(1)) # revealed: str
reveal_type(f(*(1,))) # revealed: str
def _(list_int: list[int], list_any: list[Any]):
# All materializations of `list[int]` are assignable to `list[int]`, so it matches the first
# overload.
@@ -1166,6 +1192,7 @@ reveal_type(f(*((1, "b"),))) # revealed: int
reveal_type(f((1, 2))) # revealed: int
reveal_type(f(*((1, 2),))) # revealed: int
def _(int_str: tuple[int, str], int_any: tuple[int, Any], any_any: tuple[Any, Any]):
# All materializations are assignable to first overload, so second and third overloads are
# eliminated
@@ -1206,6 +1233,7 @@ class Foo:
from module import Foo
from typing_extensions import LiteralString
def f(a: Foo, b: list[str], c: list[LiteralString], e):
reveal_type(e) # revealed: Unknown
reveal_type(a.join(b)) # revealed: str
@@ -1245,6 +1273,7 @@ from typing import Any
from overloaded import A, f
def _(list_int: list[int], list_any: list[Any], int_str: tuple[int, str], int_any: tuple[int, Any], any_any: tuple[Any, Any]):
# All materializations of both argument types are assignable to the first overload, so the
# second and third overloads are filtered out
@@ -1293,6 +1322,7 @@ from typing_extensions import LiteralString
from overloaded import f
def _(literal: LiteralString, string: str, any: Any):
reveal_type(f(literal)) # revealed: LiteralString
reveal_type(f(*(literal,))) # revealed: LiteralString
@@ -1331,6 +1361,7 @@ from typing import Any
from overloaded import f
def _(list_int: list[int], list_str: list[str], list_any: list[Any], any: Any):
reveal_type(f(list_int)) # revealed: A
reveal_type(f(*(list_int,))) # revealed: A
@@ -1365,6 +1396,7 @@ from typing import Any
from overloaded import f
def _(integer: int, string: str, any: Any, list_any: list[Any]):
reveal_type(f(integer, string)) # revealed: int
reveal_type(f(*(integer, string))) # revealed: int
@@ -1407,11 +1439,13 @@ from typing import Any
from overloaded import A, B
def _(a_int: A[int], a_str: A[str], a_any: A[Any]):
reveal_type(a_int.method()) # revealed: int
reveal_type(a_str.method()) # revealed: int
reveal_type(a_any.method()) # revealed: int
def _(b_int: B[int], b_str: B[str], b_any: B[Any]):
reveal_type(b_int.method()) # revealed: int
reveal_type(b_str.method()) # revealed: str
@@ -1458,6 +1492,7 @@ from typing import Any
from overloaded import f1, f2, f3, f4
def _(arg: list[Any]):
# Matches both overload and the return types are equivalent
reveal_type(f1(*arg)) # revealed: A
@@ -1520,10 +1555,12 @@ reveal_type(f1(1)) # revealed: tuple[Literal[1]]
reveal_type(f1(1, 2)) # revealed: tuple[Literal[1], Literal[2]]
reveal_type(f1(1, 2, 3)) # revealed: tuple[Literal[1], Literal[2], Literal[3]]
def _(args1: list[int], args2: list[Any]):
reveal_type(f1(*args1)) # revealed: tuple[Any, ...]
reveal_type(f1(*args2)) # revealed: tuple[Any, ...]
reveal_type(f2()) # revealed: tuple[Any, ...]
reveal_type(f2(1, 2)) # revealed: tuple[Literal[1], Literal[2]]
# TODO: Should be `tuple[Literal[1], Literal[2]]`
@@ -1574,6 +1611,7 @@ from typing import Any
from overloaded import f
def _(any: Any):
reveal_type(f(any, flag=True)) # revealed: int
reveal_type(f(*(any,), flag=True)) # revealed: int
@@ -1602,6 +1640,7 @@ from typing import Any, Literal
from overloaded import f
def _(any: Any):
reveal_type(f(any, flag=True)) # revealed: int
reveal_type(f(*(any,), flag=True)) # revealed: int
@@ -1609,9 +1648,11 @@ def _(any: Any):
reveal_type(f(any, flag=False)) # revealed: str
reveal_type(f(*(any,), flag=False)) # revealed: str
def _(args: tuple[Any, Literal[True]]):
reveal_type(f(*args)) # revealed: int
def _(args: tuple[Any, Literal[False]]):
reveal_type(f(*args)) # revealed: str
```
@@ -1656,6 +1697,7 @@ from typing import Any
from overloaded import A, B, f
def _(arg: tuple[A | B, Any]):
reveal_type(f(arg)) # revealed: A | B
reveal_type(f(*(arg,))) # revealed: A | B
@@ -1691,6 +1733,7 @@ from typing import Any
from overloaded import A, B, C, f
def _(arg: tuple[A | B, Any]):
reveal_type(f(arg)) # revealed: A | Unknown
reveal_type(f(*(arg,))) # revealed: A | Unknown
@@ -1725,6 +1768,7 @@ from typing import Any
from overloaded import A, B, C, f
def _(arg: tuple[A | B, Any]):
reveal_type(f(arg)) # revealed: Unknown
reveal_type(f(*(arg,))) # revealed: Unknown
@@ -1742,9 +1786,11 @@ Type inference accounts for parameter type annotations across all overloads.
```py
from typing import TypedDict, overload
class T(TypedDict):
x: int
@overload
def f(a: list[T], b: int) -> int: ...
@overload
@@ -1752,9 +1798,11 @@ def f(a: list[dict[str, int]], b: str) -> str: ...
def f(a: list[dict[str, int]] | list[T], b: int | str) -> int | str:
return 1
def int_or_str() -> int | str:
return 1
x = f([{"x": 1}], int_or_str())
reveal_type(x) # revealed: int | str
@@ -1767,9 +1815,11 @@ Non-matching overloads do not produce diagnostics:
```py
from typing import TypedDict, overload
class T(TypedDict):
x: int
@overload
def f(a: T, b: int) -> int: ...
@overload
@@ -1777,6 +1827,7 @@ def f(a: dict[str, int], b: str) -> str: ...
def f(a: T | dict[str, int], b: int | str) -> int | str:
return 1
x = f({"y": 1}, "a")
reveal_type(x) # revealed: str
```
@@ -1784,11 +1835,13 @@ reveal_type(x) # revealed: str
```py
from typing import SupportsRound, overload
@overload
def takes_str_or_float(x: str): ...
@overload
def takes_str_or_float(x: float): ...
def takes_str_or_float(x: float | str): ...
takes_str_or_float(round(1.0))
```

View File

@@ -31,11 +31,13 @@ Dataclasses support the `__replace__` protocol:
from dataclasses import dataclass
from copy import replace
@dataclass
class Point:
x: int
y: int
reveal_type(Point.__replace__) # revealed: (self: Point, *, x: int = ..., y: int = ...) -> Point
```
@@ -80,10 +82,12 @@ NamedTuples also support the `__replace__` protocol:
from typing import NamedTuple
from copy import replace
class Point(NamedTuple):
x: int
y: int
reveal_type(Point.__replace__) # revealed: (self: Self, *, x: int = ..., y: int = ...) -> Self
```

View File

@@ -39,6 +39,7 @@ And similiarly, we should still infer `bool` if the instance or the prefix are n
```py
from typing_extensions import LiteralString
def _(string_instance: str, literalstring: LiteralString):
reveal_type(string_instance.startswith("a")) # revealed: bool
reveal_type(literalstring.startswith("a")) # revealed: bool

View File

@@ -7,6 +7,7 @@
```py
class C: ...
def _(subclass_of_c: type[C]):
reveal_type(subclass_of_c()) # revealed: C
```
@@ -17,6 +18,7 @@ def _(subclass_of_c: type[C]):
class C:
def __init__(self, x: int): ...
def _(subclass_of_c: type[C]):
reveal_type(subclass_of_c(1)) # revealed: C
@@ -34,6 +36,7 @@ def _(subclass_of_c: type[C]):
from typing import Any
from ty_extensions import Unknown
def _(subclass_of_any: type[Any], subclass_of_unknown: type[Unknown]):
reveal_type(subclass_of_any()) # revealed: Any
reveal_type(subclass_of_any("any", "args", 1, 2)) # revealed: Any
@@ -45,8 +48,11 @@ def _(subclass_of_any: type[Any], subclass_of_unknown: type[Unknown]):
```py
class A: ...
class B: ...
def _(subclass_of_ab: type[A | B]):
reveal_type(subclass_of_ab()) # revealed: A | B
```

View File

@@ -17,8 +17,11 @@ from the first argument:
```py
class Base: ...
class Mixin: ...
# We synthesize a class type using the name argument
Foo = type("Foo", (), {})
reveal_type(Foo) # revealed: <class 'Foo'>
@@ -44,9 +47,11 @@ name = "IndirectClass"
IndirectClass = type(name, (), {})
reveal_type(IndirectClass) # revealed: <class 'IndirectClass'>
# Works with base classes too
class Base: ...
base_name = "DerivedClass"
DerivedClass = type(base_name, (Base,), {})
reveal_type(DerivedClass) # revealed: <class 'DerivedClass'>
@@ -59,8 +64,10 @@ Each `type()` call produces a distinct class type, even if they have the same na
```py
from ty_extensions import static_assert, is_equivalent_to
class Base: ...
Foo1 = type("Foo", (Base,), {})
Foo2 = type("Foo", (Base,), {})
@@ -71,15 +78,17 @@ static_assert(not is_equivalent_to(Foo1, Foo2))
foo1 = Foo1()
foo2 = Foo2()
def takes_foo1(x: Foo1) -> None: ...
def takes_foo2(x: Foo2) -> None: ...
takes_foo1(foo1) # OK
takes_foo2(foo2) # OK
# error: [invalid-argument-type] "Argument to function `takes_foo1` is incorrect: Expected `mdtest_snippet.Foo @ src/mdtest_snippet.py:5`, found `mdtest_snippet.Foo @ src/mdtest_snippet.py:6`"
# error: [invalid-argument-type] "Argument to function `takes_foo1` is incorrect: Expected `mdtest_snippet.Foo @ src/mdtest_snippet.py:5:8`, found `mdtest_snippet.Foo @ src/mdtest_snippet.py:6:8`"
takes_foo1(foo2)
# error: [invalid-argument-type] "Argument to function `takes_foo2` is incorrect: Expected `mdtest_snippet.Foo @ src/mdtest_snippet.py:6`, found `mdtest_snippet.Foo @ src/mdtest_snippet.py:5`"
# error: [invalid-argument-type] "Argument to function `takes_foo2` is incorrect: Expected `mdtest_snippet.Foo @ src/mdtest_snippet.py:6:8`, found `mdtest_snippet.Foo @ src/mdtest_snippet.py:5:8`"
takes_foo2(foo1)
```
@@ -95,9 +104,11 @@ class Base:
def base_method(self) -> str:
return "hello"
class Mixin:
mixin_attr: str = "mixin"
Foo = type("Foo", (Base,), {})
foo = Foo()
@@ -115,17 +126,124 @@ reveal_type(bar.base_attr) # revealed: int
reveal_type(bar.mixin_attr) # revealed: str
```
Attributes from the namespace dict (third argument) are not tracked. Like Pyright, we error when
attempting to access them:
Attributes from the namespace dict (third argument) are tracked:
```py
class Base: ...
Foo = type("Foo", (Base,), {"custom_attr": 42})
foo = Foo()
# error: [unresolved-attribute] "Object of type `Foo` has no attribute `custom_attr`"
reveal_type(foo.custom_attr) # revealed: Unknown
Foo = type("Foo", (Base,), {"custom_attr": 42})
# Class attribute access
reveal_type(Foo.custom_attr) # revealed: Literal[42]
# Instance attribute access
foo = Foo()
reveal_type(foo.custom_attr) # revealed: Literal[42]
```
When the namespace dict is not a literal (e.g., passed as a parameter), attribute access returns
`Unknown` since we can't know what attributes might be defined:
```py
from typing import Any
class DynamicBase: ...
def f(attributes: dict[str, Any]):
X = type("X", (DynamicBase,), attributes)
reveal_type(X) # revealed: <class 'X'>
# Attribute access returns Unknown since the namespace is dynamic
reveal_type(X.foo) # revealed: Unknown
x = X()
reveal_type(x.bar) # revealed: Unknown
```
When a namespace dictionary is partially dynamic (e.g., a dict literal with spread or non-literal
keys), static attributes have precise types while unknown attributes return `Unknown`:
```py
from typing import Any
def f(extra_attrs: dict[str, Any], y: str):
X = type("X", (), {"a": 42, **extra_attrs})
# Static attributes in the namespace dictionary have precise types,
# but the dictionary was not entirely static, so other attributes
# are still available and resolve to `Unknown`:
reveal_type(X().a) # revealed: Literal[42]
reveal_type(X().whatever) # revealed: Unknown
Y = type("Y", (), {"a": 56, y: 72})
reveal_type(Y().a) # revealed: Literal[56]
reveal_type(Y().whatever) # revealed: Unknown
```
When a `TypedDict` is passed as the namespace argument, we synthesize a class type with the known
keys from the `TypedDict` as attributes. Since `TypedDict` instances are "open" (they can have
arbitrary additional string keys), unknown attributes return `Unknown`:
```py
from typing import TypedDict
class Namespace(TypedDict):
z: int
def g(attributes: Namespace):
Y = type("Y", (), attributes)
reveal_type(Y) # revealed: <class 'Y'>
# Known keys from the TypedDict are tracked as attributes
reveal_type(Y.z) # revealed: int
y = Y()
reveal_type(y.z) # revealed: int
# Unknown attributes return Unknown since TypedDicts are open
reveal_type(Y.unknown) # revealed: Unknown
reveal_type(y.unknown) # revealed: Unknown
```
## Closed TypedDicts (PEP-728)
TODO: We don't support the PEP-728 `closed=True` keyword argument to `TypedDict` yet. When we do, a
closed TypedDict namespace should NOT be marked as dynamic, and accessing unknown attributes should
emit an error instead of returning `Unknown`.
```py
from typing import TypedDict
class ClosedNamespace(TypedDict, closed=True):
x: int
y: str
def h(ns: ClosedNamespace):
X = type("X", (), ns)
reveal_type(X) # revealed: <class 'X'>
# Known keys from the TypedDict are tracked as attributes
reveal_type(X.x) # revealed: int
reveal_type(X.y) # revealed: str
x = X()
reveal_type(x.x) # revealed: int
reveal_type(x.y) # revealed: str
# TODO: Once we support `closed=True`, these should emit errors instead of returning Unknown
reveal_type(X.unknown) # revealed: Unknown
reveal_type(x.unknown) # revealed: Unknown
```
## Inheritance from dynamic classes
@@ -136,11 +254,14 @@ Regular classes can inherit from dynamic classes:
class Base:
base_attr: int = 1
DynamicClass = type("DynamicClass", (Base,), {})
class Child(DynamicClass):
child_attr: str = "child"
child = Child()
# Attributes from the dynamic class's base are accessible
@@ -149,19 +270,24 @@ reveal_type(child.base_attr) # revealed: int
# The child class's own attributes are accessible
reveal_type(child.child_attr) # revealed: str
# Child instances are subtypes of DynamicClass instances
def takes_dynamic(x: DynamicClass) -> None: ...
takes_dynamic(child) # No error - Child is a subtype of DynamicClass
# isinstance narrows to the dynamic class instance type
def check_isinstance(x: object) -> None:
if isinstance(x, DynamicClass):
reveal_type(x) # revealed: DynamicClass
# Dynamic class inheriting from int narrows correctly with isinstance
IntSubclass = type("IntSubclass", (int,), {})
def check_int_subclass(x: IntSubclass | str) -> None:
if isinstance(x, int):
# IntSubclass inherits from int, so it's included in the narrowed type
@@ -178,8 +304,10 @@ from both):
```py
class Base: ...
Foo = type("Foo", (Base,), {})
def check_disjointness(x: Foo | int) -> None:
if isinstance(x, int):
reveal_type(x) # revealed: int
@@ -200,9 +328,11 @@ StrClass = type("StrClass", (str,), {})
static_assert(is_disjoint_from(type[IntClass], type[StrClass]))
static_assert(is_disjoint_from(type[StrClass], type[IntClass]))
# Dynamic classes that share a common base are not disjoint.
class Base: ...
Foo = type("Foo", (Base,), {})
Bar = type("Bar", (Base,), {})
@@ -218,6 +348,7 @@ class Base:
def method(self) -> int:
return 42
DynamicChild = type("DynamicChild", (Base,), {})
# Using dynamic class as pivot with dynamic class instance owner
@@ -225,10 +356,12 @@ fc = DynamicChild()
reveal_type(super(DynamicChild, fc)) # revealed: <super: <class 'DynamicChild'>, DynamicChild>
reveal_type(super(DynamicChild, fc).method()) # revealed: int
# Regular class inheriting from dynamic class
class RegularChild(DynamicChild):
pass
rc = RegularChild()
reveal_type(super(RegularChild, rc)) # revealed: <super: <class 'RegularChild'>, RegularChild>
reveal_type(super(RegularChild, rc).method()) # revealed: int
@@ -246,6 +379,7 @@ Dynamic classes can inherit from other dynamic classes:
class Base:
base_attr: int = 1
# Create a dynamic class that inherits from a regular class.
Parent = type("Parent", (Base,), {})
reveal_type(Parent) # revealed: <class 'Parent'>
@@ -259,9 +393,11 @@ child = ChildCls()
reveal_type(child) # revealed: ChildCls
reveal_type(child.base_attr) # revealed: int
# Child instances are subtypes of `Parent` instances.
def takes_parent(x: Parent) -> None: ...
takes_parent(child) # No error - `ChildCls` is a subtype of `Parent`
```
@@ -274,12 +410,14 @@ dataclass-like and have the synthesized `__dataclass_fields__` attribute:
from dataclasses import Field
from typing_extensions import dataclass_transform
@dataclass_transform()
class DataclassBase:
"""Base class decorated with @dataclass_transform()."""
pass
# A dynamic class inheriting from a dataclass_transform base
DynamicModel = type("DynamicModel", (DataclassBase,), {})
@@ -309,9 +447,11 @@ from typing import Generic, TypeVar
T = TypeVar("T")
class Container(Generic[T]):
value: T
# Dynamic class inheriting from a generic class specialization
IntContainer = type("IntContainer", (Container[int],), {})
reveal_type(IntContainer) # revealed: <class 'IntContainer'>
@@ -328,6 +468,7 @@ reveal_type(container.value) # revealed: int
```py
class Base: ...
Foo = type("Foo", (Base,), {})
foo = Foo()
@@ -340,6 +481,7 @@ reveal_type(type(foo)) # revealed: type[Foo]
```py
class Base: ...
Foo = type("Foo", (Base,), {})
foo = Foo()
@@ -352,6 +494,7 @@ reveal_type(foo.__class__) # revealed: type[Foo]
```py
class StaticClass: ...
DynamicClass = type("DynamicClass", (), {})
# Both static and dynamic classes have `type` as their metaclass
@@ -366,12 +509,15 @@ Dynamic instances are subtypes of `object`:
```py
class Base: ...
Foo = type("Foo", (Base,), {})
foo = Foo()
# All dynamic instances are subtypes of object
def takes_object(x: object) -> None: ...
takes_object(foo) # No error - Foo is a subtype of object
# Even dynamic classes with no explicit bases are subtypes of object
@@ -423,6 +569,7 @@ produces slightly different error messages than assigned dynamic class creation:
```py
class Base: ...
# error: 6 [invalid-argument-type] "Argument to class `type` is incorrect: Expected `str`, found `Literal[b"Foo"]`"
type(b"Foo", (), {})
@@ -445,9 +592,11 @@ diagnostic about the unsupported base, rather than cascading errors:
```py
from ty_extensions import reveal_mro
class Base:
base_attr: int = 1
def f(x: type[Base]):
# error: [unsupported-dynamic-base] "Unsupported class base"
Child = type("Child", (x,), {})
@@ -469,6 +618,7 @@ MRO errors are detected and reported:
```py
class A: ...
# Duplicate bases are detected
# error: [duplicate-base] "Duplicate base class <class 'A'> in class `Dup`"
Dup = type("Dup", (A, A), {})
@@ -487,14 +637,22 @@ X = type("X", (Bar, Baz), {})
```py
class A: ...
class B(A): ...
class C(A): ...
# This creates an inconsistent MRO because D would need B before C (from first base)
# but also C before B (from second base inheritance through A)
class X(B, C): ...
class Y(C, B): ...
# error: [inconsistent-mro] "Cannot create a consistent method resolution order (MRO) for class `Conflict` with bases `[<class 'X'>, <class 'Y'>]`"
Conflict = type("Conflict", (X, Y), {})
```
@@ -505,15 +663,156 @@ Metaclass conflicts are detected and reported:
```py
class Meta1(type): ...
class Meta2(type): ...
class A(metaclass=Meta1): ...
class B(metaclass=Meta2): ...
# error: [conflicting-metaclass] "The metaclass of a derived class (`Bad`) must be a subclass of the metaclasses of all its bases, but `Meta1` (metaclass of base class `<class 'A'>`) and `Meta2` (metaclass of base class `<class 'B'>`) have no subclass relationship"
Bad = type("Bad", (A, B), {})
```
## Cyclic dynamic class definitions
## `__slots__` in namespace dictionary
Dynamic classes can define `__slots__` in the namespace dictionary. Non-empty `__slots__` makes the
class a "disjoint base", which prevents it from being used alongside other disjoint bases in a class
hierarchy:
```py
# Dynamic class with non-empty __slots__
Slotted = type("Slotted", (), {"__slots__": ("x", "y")})
slotted = Slotted()
reveal_type(slotted) # revealed: Slotted
# Classes with empty __slots__ are not disjoint bases
EmptySlots = type("EmptySlots", (), {"__slots__": ()})
# Classes with no __slots__ are not disjoint bases
NoSlots = type("NoSlots", (), {})
# String __slots__ are treated as a single slot (non-empty)
StringSlots = type("StringSlots", (), {"__slots__": "x"})
```
Dynamic classes with non-empty `__slots__` cannot coexist with other disjoint bases:
```py
class RegularSlotted:
__slots__ = ("a",)
DynSlotted = type("DynSlotted", (), {"__slots__": ("b",)})
# error: [instance-layout-conflict]
class Conflict(
RegularSlotted,
DynSlotted,
): ...
```
Two dynamic classes with non-empty `__slots__` also conflict:
```py
A = type("A", (), {"__slots__": ("x",)})
B = type("B", (), {"__slots__": ("y",)})
# error: [instance-layout-conflict]
class Conflict(
A,
B,
): ...
```
`instance-layout-conflict` errors are also emitted for classes that inherit from dynamic classes
with disjoint bases:
```py
from typing import Any
class DisjointBase1:
__slots__ = ("a",)
class DisjointBase2:
__slots__ = ("b",)
def f(ns: dict[str, Any]):
cls1 = type("cls1", (DisjointBase1,), ns)
cls2 = type("cls2", (DisjointBase2,), ns)
# error: [instance-layout-conflict]
cls3 = type("cls3", (cls1, cls2), {})
# error: [instance-layout-conflict]
class Cls4(cls1, cls2): ...
```
When the namespace dictionary is dynamic (not a literal), we can't determine if `__slots__` is
defined, so no diagnostic is emitted:
```py
from typing import Any
class SlottedBase:
__slots__ = ("a",)
def f(ns: dict[str, Any]):
# The namespace might or might not contain __slots__, so no error is emitted
Dynamic = type("Dynamic", (), ns)
# No error: we can't prove there's a conflict since ns might not have __slots__
class MaybeConflict(SlottedBase, Dynamic): ...
```
## `instance-layout-conflict` diagnostic snapshots
<!-- snapshot-diagnostics -->
When the bases are a tuple literal, the diagnostic includes annotations for each conflicting base:
```py
class A:
__slots__ = ("x",)
class B:
__slots__ = ("y",)
# error: [instance-layout-conflict]
X = type("X", (A, B), {})
```
When the bases are not a tuple literal (e.g., a variable), the diagnostic is emitted without
per-base annotations:
```py
class C:
__slots__ = ("x",)
class D:
__slots__ = ("y",)
bases: tuple[type[C], type[D]] = (C, D)
# error: [instance-layout-conflict]
Y = type("Y", bases, {})
```
## Cyclic functional class definitions
Self-referential class definitions using `type()` are detected. The name being defined is referenced
in the bases tuple before it's available:
@@ -535,13 +834,14 @@ def make_class(name: str):
reveal_type(cls) # revealed: <class '<unknown>'>
return cls
def make_classes(name1: str, name2: str):
cls1 = type(name1, (), {})
cls2 = type(name2, (), {})
def inner(x: cls1): ...
# error: [invalid-argument-type] "Argument to function `inner` is incorrect: Expected `mdtest_snippet.<locals of function 'make_classes'>.<unknown> @ src/mdtest_snippet.py:8`, found `mdtest_snippet.<locals of function 'make_classes'>.<unknown> @ src/mdtest_snippet.py:9`"
# error: [invalid-argument-type] "Argument to function `inner` is incorrect: Expected `mdtest_snippet.<locals of function 'make_classes'>.<unknown> @ src/mdtest_snippet.py:8:12`, found `mdtest_snippet.<locals of function 'make_classes'>.<unknown> @ src/mdtest_snippet.py:9:12`"
inner(cls2())
```
@@ -567,9 +867,13 @@ any attribute access returns `Unknown`:
```py
from ty_extensions import reveal_mro
class Base1: ...
class Base2: ...
def make_class(bases: tuple[type, ...]):
# Class literal is created with Unknown base in MRO
cls = type("Cls", bases, {})
@@ -592,6 +896,7 @@ classes:
class Base:
attr: int = 1
bases = (Base,)
Cls = type("Cls", bases, {})
reveal_type(Cls) # revealed: <class 'Cls'>
@@ -607,13 +912,15 @@ Unpacking arguments with `*args` or `**kwargs`:
```py
from ty_extensions import reveal_mro
class Base: ...
# Unpacking a tuple for bases
bases_tuple = (Base,)
Cls1 = type("Cls1", (*bases_tuple,), {})
reveal_type(Cls1) # revealed: <class 'Cls1'>
reveal_mro(Cls1) # revealed: (<class 'Cls1'>, @Todo(StarredExpression), <class 'object'>)
reveal_mro(Cls1) # revealed: (<class 'Cls1'>, <class 'Base'>, <class 'object'>)
# Unpacking a dict for the namespace - the dict contents are not tracked anyway
namespace = {"attr": 1}
@@ -662,6 +969,7 @@ This will be fixed when we support all `type()` calls (including inline) via gen
```py
class Base: ...
# TODO: Should infer `<class 'T'>` instead of `type`
T: type = type("T", (), {})
reveal_type(T) # revealed: type
@@ -684,9 +992,11 @@ synthesized:
from typing import Protocol
from ty_extensions import reveal_mro
class MyProtocol(Protocol):
def method(self) -> int: ...
ProtoImpl = type("ProtoImpl", (MyProtocol,), {})
reveal_type(ProtoImpl) # revealed: <class 'ProtoImpl'>
reveal_mro(ProtoImpl) # revealed: (<class 'ProtoImpl'>, <class 'MyProtocol'>, typing.Protocol, typing.Generic, <class 'object'>)
@@ -702,10 +1012,12 @@ reveal_type(instance) # revealed: ProtoImpl
from typing_extensions import TypedDict
from ty_extensions import reveal_mro
class MyDict(TypedDict):
name: str
age: int
DictSubclass = type("DictSubclass", (MyDict,), {})
reveal_type(DictSubclass) # revealed: <class 'DictSubclass'>
reveal_mro(DictSubclass) # revealed: (<class 'DictSubclass'>, <class 'MyDict'>, typing.TypedDict, <class 'object'>)
@@ -718,10 +1030,12 @@ reveal_mro(DictSubclass) # revealed: (<class 'DictSubclass'>, <class 'MyDict'>,
from typing import NamedTuple
from ty_extensions import reveal_mro
class Point(NamedTuple):
x: int
y: int
Point3D = type("Point3D", (Point,), {})
reveal_type(Point3D) # revealed: <class 'Point3D'>
# fmt: off
@@ -736,13 +1050,16 @@ reveal_mro(Point3D) # revealed: (<class 'Point3D'>, <class 'Point'>, <class 'tu
# that type() cannot provide. This applies even to empty enums.
from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
class EmptyEnum(Enum):
pass
# TODO: We should emit a diagnostic here - type() cannot create Enum subclasses
ExtendedColor = type("ExtendedColor", (Color,), {})
reveal_type(ExtendedColor) # revealed: <class 'ExtendedColor'>
@@ -764,10 +1081,12 @@ class Base:
super().__init_subclass__(**kwargs)
cls.config = required_arg
# Regular class definition - this works and passes the argument
class Child(Base, required_arg="value"):
pass
# The dynamically assigned attribute has Unknown in its type
reveal_type(Child.config) # revealed: Unknown | str
@@ -804,8 +1123,10 @@ When a base class has a custom metaclass, the dynamic class inherits that metacl
class MyMeta(type):
custom_attr: str = "meta"
class Base(metaclass=MyMeta): ...
# Dynamic class inherits the metaclass from Base
Dynamic = type("Dynamic", (Base,), {})
reveal_type(Dynamic) # revealed: <class 'Dynamic'>

File diff suppressed because one or more lines are too long

View File

@@ -28,18 +28,25 @@ python-version = "3.12"
from __future__ import annotations
from ty_extensions import reveal_mro
class A:
def a(self): ...
aa: int = 1
class B(A):
def b(self): ...
bb: int = 2
class C(B):
def c(self): ...
cc: int = 3
reveal_mro(C) # revealed: (<class 'C'>, <class 'B'>, <class 'A'>, <class 'object'>)
super(C, C()).a
@@ -68,19 +75,24 @@ synthesized `Protocol`s that cannot be upcast to, or interpreted as, a non-`obje
import types
from typing_extensions import Callable, TypeIs, Literal, NewType, TypedDict
def f(): ...
class Foo[T]:
def method(self): ...
@property
def some_property(self): ...
type Alias = int
class SomeTypedDict(TypedDict):
x: int
y: bytes
N = NewType("N", int)
# revealed: <super: <class 'object'>, FunctionType>
@@ -100,9 +112,11 @@ 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)
def _(x: object, y: SomeTypedDict, z: Callable[[int, str], bool]):
if hasattr(x, "bar"):
# revealed: <Protocol with members 'bar'>
@@ -124,6 +138,7 @@ def _(x: object, y: SomeTypedDict, z: Callable[[int, str], bool]):
# revealed: <super: <class 'object'>, dict[Literal["x", "y"], int | bytes]>
reveal_type(super(object, y))
# The first argument to `super()` must be an actual class object;
# instances of `GenericAlias` are not accepted at runtime:
#
@@ -139,6 +154,7 @@ class Super:
def method(self) -> int:
return 42
class Sub(Super):
def method(self: Sub) -> int:
# revealed: <super: <class 'Sub'>, Sub>
@@ -161,11 +177,13 @@ python-version = "3.12"
```py
from __future__ import annotations
class A:
def __init__(self, a: int): ...
@classmethod
def f(cls): ...
class B(A):
def __init__(self, a: int):
reveal_type(super()) # revealed: <super: <class 'B'>, Self@__init__>
@@ -177,6 +195,7 @@ class B(A):
reveal_type(super()) # revealed: <super: <class 'B'>, type[Self@f]>
super().f()
super(B, B(42)).__init__(42)
super(B, B).f()
```
@@ -188,6 +207,7 @@ import enum
from typing import Any, Self, Never, Protocol, Callable
from ty_extensions import Intersection
class BuilderMeta(type):
def __new__(
cls: type[Any],
@@ -200,6 +220,7 @@ class BuilderMeta(type):
# revealed: Any
return reveal_type(s.__new__(cls, name, bases, dct))
class BuilderMeta2(type):
def __new__(
cls: type[BuilderMeta2],
@@ -211,6 +232,7 @@ class BuilderMeta2(type):
s = reveal_type(super())
return reveal_type(s.__new__(cls, name, bases, dct)) # revealed: BuilderMeta2
class Foo[T]:
x: T
@@ -265,12 +287,14 @@ class Foo[T]:
# revealed: Unknown
reveal_type(super())
return self
# TypeVar bounded by `type[Foo]` rather than `Foo`
# TODO: Should error on signature - `self` is annotated as a class type, not an instance type
def method11[S: type[Foo[int]]](self: S, other: S) -> S:
# Delegates to the bound to resolve the super type
reveal_type(super()) # revealed: <super: <class 'Foo'>, <class 'Foo[int]'>>
return self
# TypeVar bounded by `type[Foo]`, used in `type[T]` position
# TODO: Should error on signature - `cls` would be `type[type[Foo[int]]]`, a metaclass
# Delegates to `type[Unknown]` since `type[type[Foo[int]]]` can't be constructed
@@ -279,8 +303,10 @@ class Foo[T]:
reveal_type(super()) # revealed: <super: <class 'Foo'>, Unknown>
raise NotImplementedError
type Alias = Bar
class Bar:
def method(self: Alias):
# revealed: <super: <class 'Bar'>, Bar>
@@ -294,11 +320,13 @@ class Bar:
# revealed: <super: <class 'Bar'>, Bar>
reveal_type(super())
class P(Protocol):
def method(self: P):
# revealed: <super: <class 'P'>, P>
reveal_type(super())
class E(enum.Enum):
X = 1
@@ -318,8 +346,10 @@ a plain `super` instance and does not support name lookup via the MRO.
class A:
a: int = 42
class B(A): ...
reveal_type(super(B)) # revealed: super
# error: [unresolved-attribute] "Object of type `super` has no attribute `a`"
@@ -335,8 +365,10 @@ successfully.
class A:
a: int = 3
class B(A): ...
reveal_type(super(B, B()).a) # revealed: int
# error: [invalid-assignment] "Cannot assign to attribute `a` on type `<super: <class 'B'>, B>`"
super(B, B()).a = 3
@@ -353,6 +385,7 @@ member, it should effectively behave like a dynamic type.
class A:
a: int = 1
def f(x):
reveal_type(x) # revealed: Unknown
@@ -370,6 +403,7 @@ def f(x):
```py
from __future__ import annotations
class A:
def test(self):
reveal_type(super()) # revealed: <super: <class 'A'>, Self@test>
@@ -384,6 +418,7 @@ class A:
def inner(t: C):
reveal_type(super()) # revealed: <super: <class 'B'>, C>
lambda x: reveal_type(super()) # revealed: <super: <class 'B'>, Unknown>
```
@@ -399,9 +434,11 @@ reveal_type(super(int, 3)) # revealed: <super: <class 'int'>, int>
reveal_type(super(str, "")) # revealed: <super: <class 'str'>, str>
reveal_type(super(bytes, b"")) # revealed: <super: <class 'bytes'>, bytes>
class E(Enum):
X = 42
reveal_type(super(E, E.X)) # revealed: <super: <class 'E'>, E>
```
@@ -424,8 +461,10 @@ class A:
@classmethod
def a2(cls): ...
class B(A): ...
# A.__dict__["a1"].__get__(B(), B)
reveal_type(super(B, B()).a1) # revealed: bound method B.a1() -> Unknown
# A.__dict__["a2"].__get__(B(), B)
@@ -445,14 +484,20 @@ super objects are combined into a union.
```py
from ty_extensions import reveal_mro
class A: ...
class B:
b: int = 42
class C(A, B): ...
class D(B, A): ...
def f(x: C | D):
reveal_mro(C) # revealed: (<class 'C'>, <class 'A'>, <class 'B'>, <class 'object'>)
reveal_mro(D) # revealed: (<class 'D'>, <class 'B'>, <class 'A'>, <class 'object'>)
@@ -463,11 +508,13 @@ def f(x: C | D):
# error: [possibly-missing-attribute] "Attribute `b` may be missing on object of type `<super: <class 'A'>, C> | <super: <class 'A'>, D>`"
s.b
def f(flag: bool):
x = str() if flag else str("hello")
reveal_type(x) # revealed: Literal["", "hello"]
reveal_type(super(str, x)) # revealed: <super: <class 'str'>, str>
def f(x: int | str):
# error: [invalid-super-argument] "`str` is not an instance or subclass of `<class 'int'>` in `super(<class 'int'>, str)` call"
super(int, x)
@@ -479,6 +526,7 @@ in all cases.
```py
def f(flag: bool):
if flag:
class A:
x = 1
y: int = 1
@@ -486,13 +534,16 @@ def f(flag: bool):
a: str = "hello"
class B(A): ...
s = super(B, B())
else:
class C:
x = 2
y: int | str = "test"
class D(C): ...
s = super(D, D())
reveal_type(s) # revealed: <super: <class 'B'>, B> | <super: <class 'D'>, D>
@@ -514,10 +565,12 @@ python-version = "3.12"
```py
from ty_extensions import TypeOf, static_assert, is_subtype_of
class A[T]:
def f(self, a: T) -> T:
return a
class B[T](A[T]):
def f(self, a: T) -> T:
return super().f(a)
@@ -535,10 +588,12 @@ from __future__ import annotations
# error: [unavailable-implicit-super-arguments] "Cannot determine implicit arguments for 'super()' in this context"
reveal_type(super()) # revealed: Unknown
def f():
# error: [unavailable-implicit-super-arguments] "Cannot determine implicit arguments for 'super()' in this context"
super()
# No first argument in its scope
class A:
# error: [unavailable-implicit-super-arguments] "Cannot determine implicit arguments for 'super()' in this context"
@@ -548,6 +603,7 @@ class A:
def g():
# error: [unavailable-implicit-super-arguments] "Cannot determine implicit arguments for 'super()' in this context"
super()
# error: [unavailable-implicit-super-arguments] "Cannot determine implicit arguments for 'super()' in this context"
lambda: super()
@@ -575,6 +631,7 @@ runtime.
import typing
import collections
def f(x: int):
# error: [invalid-super-argument] "`int` is not a valid class"
super(x, x)
@@ -583,6 +640,7 @@ def f(x: int):
# error: [invalid-super-argument] "`TypeAliasType` is not a valid class"
super(IntAlias, 0)
# error: [invalid-super-argument] "`str` is not an instance or subclass of `<class 'int'>` in `super(<class 'int'>, str)` call"
# revealed: Unknown
reveal_type(super(int, str()))
@@ -591,9 +649,13 @@ reveal_type(super(int, str()))
# revealed: Unknown
reveal_type(super(int, str))
class A: ...
class B(A): ...
# error: [invalid-super-argument] "`A` is not an instance or subclass of `<class 'B'>` in `super(<class 'B'>, A)` call"
# revealed: Unknown
reveal_type(super(B, A()))
@@ -624,6 +686,7 @@ reveal_type(super(typing.ChainMap, collections.ChainMap())) # revealed: Unknown
# revealed: <super: <special-form 'typing.Generic'>, <class 'SupportsInt'>>
reveal_type(super(typing.Generic, typing.SupportsInt))
def _(x: type[typing.Any], y: typing.Any):
reveal_type(super(x, y)) # revealed: <super: Any, Any>
```
@@ -636,11 +699,16 @@ def _(x: type[typing.Any], y: typing.Any):
def coinflip() -> bool:
return False
def f():
if coinflip():
class A: ...
else:
class A: ...
super(A, A()) # error: [invalid-super-argument]
```
@@ -651,16 +719,19 @@ Accessing instance members through `super()` is not allowed.
```py
from __future__ import annotations
class A:
def __init__(self, a: int):
self.a = a
class B(A):
def __init__(self, a: int):
super().__init__(a)
# error: [unresolved-attribute] "Object of type `<super: <class 'B'>, Self@__init__>` has no attribute `a`"
super().a
# error: [unresolved-attribute] "Object of type `<super: <class 'B'>, B>` has no attribute `a`"
super(B, B(42)).a
```
@@ -676,8 +747,10 @@ class A:
def __getitem__(self, key: int) -> int:
return 42
class B(A): ...
reveal_type(A()[0]) # revealed: int
reveal_type(super(B, B()).__getitem__) # revealed: bound method B.__getitem__(key: int) -> int
# error: [not-subscriptable] "Cannot subscript object of type `<super: <class 'B'>, B>` with no `__getitem__` method"
@@ -700,21 +773,26 @@ from __future__ import annotations
from collections.abc import Mapping
from typing import Self
class Parent:
def __init__(self, children: Mapping[str, Self] | None = None) -> None:
self.children = children
class Child(Parent):
def __init__(self, children: Mapping[str, Child] | None = None) -> None:
# error: [invalid-argument-type] "Argument to bound method `__init__` is incorrect: Expected `Mapping[str, Self@__init__] | None`, found `Mapping[str, Child] | None`"
super().__init__(children)
# The fix is to use `Self` consistently in the subclass:
class Parent2:
def __init__(self, children: Mapping[str, Self] | None = None) -> None:
self.children = children
class Child2(Parent2):
def __init__(self, children: Mapping[str, Self] | None = None) -> None:
super().__init__(children) # OK
@@ -730,6 +808,7 @@ from typing import Protocol, Generic, TypeVar
_T_co = TypeVar("_T_co", covariant=True)
class MyProtocol(Protocol, Generic[_T_co]):
def __class_getitem__(cls, item):
# Accessing parent's __class_getitem__ through super()

View File

@@ -17,11 +17,42 @@ from ty_extensions import reveal_mro
A = int
class G[T]: ...
class C(A, G["B"]): ...
A = str
B = bytes
reveal_mro(C) # revealed: (<class 'C'>, <class 'int'>, <class 'G[bytes]'>, typing.Generic, <class 'object'>)
```
## Starred bases
These are currently not supported, but ideally we would support them in some limited situations.
```py
from ty_extensions import reveal_mro
class A: ...
class B: ...
class C: ...
bases = (A, B, C)
class Foo(*bases): ...
# revealed: (<class 'Foo'>, @Todo(StarredExpression), <class 'object'>)
reveal_mro(Foo)
```

View File

@@ -3,10 +3,12 @@
```py
from enum import Enum
class Answer(Enum):
NO = 0
YES = 1
reveal_type(Answer.NO == Answer.NO) # revealed: Literal[True]
reveal_type(Answer.NO == Answer.YES) # revealed: Literal[False]

View File

@@ -3,6 +3,7 @@
```py
class A: ...
def _(a1: A, a2: A, o: object):
n1 = None
n2 = None

View File

@@ -19,6 +19,7 @@ class A:
def __contains__(self, item: str) -> bool:
return True
reveal_type("hello" in A()) # revealed: bool
reveal_type("hello" not in A()) # revealed: bool
# error: [unsupported-operator] "Operator `in` is not supported between objects of type `Literal[42]` and `A`"
@@ -37,10 +38,12 @@ class StringIterator:
def __next__(self) -> str:
return "foo"
class A:
def __iter__(self) -> StringIterator:
return StringIterator()
reveal_type("hello" in A()) # revealed: bool
reveal_type("hello" not in A()) # revealed: bool
reveal_type(42 in A()) # revealed: bool
@@ -59,6 +62,7 @@ class A:
def __getitem__(self, key: int) -> str:
return "foo"
reveal_type("hello" in A()) # revealed: bool
reveal_type("hello" not in A()) # revealed: bool
reveal_type(42 in A()) # revealed: bool
@@ -75,6 +79,7 @@ class A:
def __contains__(self, item: str) -> str:
return "foo"
reveal_type("hello" in A()) # revealed: bool
reveal_type("hello" not in A()) # revealed: bool
```
@@ -86,14 +91,17 @@ reveal_type("hello" not in A()) # revealed: bool
```py
from typing import Literal
class AlwaysTrue:
def __contains__(self, item: int) -> Literal[1]:
return 1
class AlwaysFalse:
def __contains__(self, item: int) -> Literal[""]:
return ""
reveal_type(42 in AlwaysTrue()) # revealed: Literal[True]
reveal_type(42 not in AlwaysTrue()) # revealed: Literal[False]
@@ -108,13 +116,19 @@ doesn't result in a fallback to `__iter__` or `__getitem__`:
```py
class CheckContains: ...
class CheckIter: ...
class CheckGetItem: ...
class CheckIterIterator:
def __next__(self) -> CheckIter:
return CheckIter()
class A:
def __contains__(self, item: CheckContains) -> bool:
return True
@@ -125,6 +139,7 @@ class A:
def __getitem__(self, key: int) -> CheckGetItem:
return CheckGetItem()
reveal_type(CheckContains() in A()) # revealed: bool
# error: [unsupported-operator] "Operator `in` is not supported between objects of type `CheckIter` and `A`"
@@ -132,6 +147,7 @@ reveal_type(CheckIter() in A()) # revealed: bool
# error: [unsupported-operator] "Operator `in` is not supported between objects of type `CheckGetItem` and `A`"
reveal_type(CheckGetItem() in A()) # revealed: bool
class B:
def __iter__(self) -> CheckIterIterator:
return CheckIterIterator()
@@ -139,6 +155,7 @@ class B:
def __getitem__(self, key: int) -> CheckGetItem:
return CheckGetItem()
reveal_type(CheckIter() in B()) # revealed: bool
# Always use `__iter__`, regardless of iterated type; there's no NotImplemented
# in this case, so there's no fallback to `__getitem__`
@@ -155,6 +172,7 @@ class A:
def __getitem__(self, key: str) -> str:
return "foo"
# error: [unsupported-operator] "Operator `in` is not supported between objects of type `Literal[42]` and `A`"
reveal_type(42 in A()) # revealed: bool
# error: [unsupported-operator] "Operator `in` is not supported between objects of type `Literal["hello"]` and `A`"
@@ -193,10 +211,12 @@ It may also be more appropriate to use `unsupported-operator` as the error code.
class NotBoolable:
__bool__: int = 3
class WithContains:
def __contains__(self, item) -> NotBoolable:
return NotBoolable()
# error: [unsupported-bool-conversion]
10 in WithContains()
# error: [unsupported-bool-conversion]

View File

@@ -16,13 +16,25 @@ most common case involves implementing these methods for the same type:
```py
from __future__ import annotations
class EqReturnType: ...
class NeReturnType: ...
class LtReturnType: ...
class LeReturnType: ...
class GtReturnType: ...
class GeReturnType: ...
class A:
def __eq__(self, other: A) -> EqReturnType: # error: [invalid-method-override]
return EqReturnType()
@@ -42,6 +54,7 @@ class A:
def __ge__(self, other: A) -> GeReturnType:
return GeReturnType()
reveal_type(A() == A()) # revealed: EqReturnType
reveal_type(A() != A()) # revealed: NeReturnType
reveal_type(A() < A()) # revealed: LtReturnType
@@ -58,13 +71,25 @@ type:
```py
from __future__ import annotations
class EqReturnType: ...
class NeReturnType: ...
class LtReturnType: ...
class LeReturnType: ...
class GtReturnType: ...
class GeReturnType: ...
class A:
def __eq__(self, other: B) -> EqReturnType: # error: [invalid-method-override]
return EqReturnType()
@@ -84,8 +109,10 @@ class A:
def __ge__(self, other: B) -> GeReturnType:
return GeReturnType()
class B: ...
reveal_type(A() == B()) # revealed: EqReturnType
reveal_type(A() != B()) # revealed: NeReturnType
reveal_type(A() < B()) # revealed: LtReturnType
@@ -103,13 +130,25 @@ these methods will be ignored here because they require a mismatched operand typ
```py
from __future__ import annotations
class EqReturnType: ...
class NeReturnType: ...
class LtReturnType: ...
class LeReturnType: ...
class GtReturnType: ...
class GeReturnType: ...
class A:
def __eq__(self, other: B) -> EqReturnType: # error: [invalid-method-override]
return EqReturnType()
@@ -129,8 +168,10 @@ class A:
def __ge__(self, other: B) -> GeReturnType:
return GeReturnType()
class Unrelated: ...
class B:
def __eq__(self, other: Unrelated) -> B: # error: [invalid-method-override]
return B()
@@ -138,6 +179,7 @@ class B:
def __ne__(self, other: Unrelated) -> B: # error: [invalid-method-override]
return B()
# Because `object.__eq__` and `object.__ne__` accept `object` in typeshed,
# this can only happen with an invalid override of these methods,
# but we still support it.
@@ -150,6 +192,7 @@ reveal_type(B() <= A()) # revealed: GeReturnType
reveal_type(B() > A()) # revealed: LtReturnType
reveal_type(B() >= A()) # revealed: LeReturnType
class C:
def __gt__(self, other: C) -> EqReturnType:
return EqReturnType()
@@ -157,6 +200,7 @@ class C:
def __ge__(self, other: C) -> NeReturnType:
return NeReturnType()
reveal_type(C() < C()) # revealed: EqReturnType
reveal_type(C() <= C()) # revealed: NeReturnType
```
@@ -170,13 +214,25 @@ than `A`.
```py
from __future__ import annotations
class EqReturnType: ...
class NeReturnType: ...
class LtReturnType: ...
class LeReturnType: ...
class GtReturnType: ...
class GeReturnType: ...
class A:
def __eq__(self, other: A) -> A: # error: [invalid-method-override]
return A()
@@ -196,6 +252,7 @@ class A:
def __ge__(self, other: A) -> A:
return A()
class B(A):
def __eq__(self, other: A) -> EqReturnType: # error: [invalid-method-override]
return EqReturnType()
@@ -215,6 +272,7 @@ class B(A):
def __ge__(self, other: A) -> GeReturnType: # error: [invalid-method-override]
return GeReturnType()
reveal_type(A() == B()) # revealed: EqReturnType
reveal_type(A() != B()) # revealed: NeReturnType
@@ -233,6 +291,7 @@ method has an mismatched type to operand, the comparison will fall back to the l
```py
from __future__ import annotations
class A:
def __lt__(self, other: A) -> A:
return A()
@@ -240,6 +299,7 @@ class A:
def __gt__(self, other: A) -> A:
return A()
class B(A):
def __lt__(self, other: int) -> B: # error: [invalid-method-override]
return B()
@@ -247,6 +307,7 @@ class B(A):
def __gt__(self, other: int) -> B: # error: [invalid-method-override]
return B()
reveal_type(A() < B()) # revealed: A
reveal_type(A() > B()) # revealed: A
```
@@ -268,12 +329,15 @@ from does_not_exist import Foo # error: [unresolved-import]
reveal_type(Foo) # revealed: Unknown
class X:
def __lt__(self, other: object) -> int:
return 42
class Y(Foo): ...
# TODO: Should be `int | Unknown`; see above discussion.
reveal_type(X() < Y()) # revealed: int
```
@@ -288,6 +352,7 @@ Please refer to the [docs](https://docs.python.org/3/reference/datamodel.html#ob
```py
from __future__ import annotations
class A:
def __eq__(self, other: int) -> A: # error: [invalid-method-override]
return A()
@@ -295,6 +360,7 @@ class A:
def __ne__(self, other: int) -> A: # error: [invalid-method-override]
return A()
reveal_type(A() == A()) # revealed: bool
reveal_type(A() != A()) # revealed: bool
```
@@ -304,6 +370,7 @@ reveal_type(A() != A()) # revealed: bool
```py
class A: ...
reveal_type(A() == object()) # revealed: bool
reveal_type(A() != object()) # revealed: bool
reveal_type(object() == A()) # revealed: bool
@@ -336,6 +403,7 @@ reveal_type(1 > 2j) # revealed: Unknown
# error: [unsupported-operator] "Operator `>=` is not supported between objects of type `Literal[1]` and `complex`"
reveal_type(1 >= 2j) # revealed: Unknown
def f(x: bool, y: int):
reveal_type(x < y) # revealed: bool
reveal_type(y < x) # revealed: bool
@@ -354,6 +422,7 @@ element) of a chained comparison.
class NotBoolable:
__bool__: int = 3
class Comparable:
def __lt__(self, item) -> NotBoolable:
return NotBoolable()
@@ -361,6 +430,7 @@ class Comparable:
def __gt__(self, item) -> NotBoolable:
return NotBoolable()
# error: [unsupported-bool-conversion]
10 < Comparable() < 20
# error: [unsupported-bool-conversion]
@@ -374,14 +444,17 @@ Comparable() < Comparable() # fine
```py
from typing import Literal
class AlwaysTrue:
def __call__(self, other: object) -> Literal[True]:
return True
class A:
__eq__: AlwaysTrue = AlwaysTrue()
__lt__: AlwaysTrue = AlwaysTrue()
reveal_type(A() == A()) # revealed: Literal[True]
reveal_type(A() < A()) # revealed: Literal[True]
reveal_type(A() > A()) # revealed: Literal[True]

View File

@@ -8,16 +8,20 @@ types, we can infer that the result for the intersection type is also true/false
```py
from typing import Literal
class Base:
def __gt__(self, other) -> bool:
return False
class Child1(Base):
def __eq__(self, other) -> Literal[True]:
return True
class Child2(Base): ...
def _(x: Base):
c1 = Child1()
@@ -95,6 +99,7 @@ def _(x: int):
```py
class A: ...
def _(o: object):
a = A()
n = None
@@ -116,8 +121,11 @@ intersection type:
```py
class NonContainer1: ...
class NonContainer2: ...
def _(x: object):
if isinstance(x, NonContainer1):
if isinstance(x, NonContainer2):
@@ -135,6 +143,7 @@ class Container:
def __contains__(self, x) -> bool:
return False
def _(x: object):
if isinstance(x, NonContainer1):
if isinstance(x, Container):
@@ -167,8 +176,10 @@ class Container:
def __contains__(self, x) -> bool:
return False
class NonContainer: ...
def _(x: object):
if isinstance(x, Container):
if not isinstance(x, NonContainer):

View File

@@ -21,6 +21,7 @@ Walking through examples:
```py
from __future__ import annotations
class A:
def __lt__(self, other) -> A:
return self
@@ -28,14 +29,17 @@ class A:
def __gt__(self, other) -> bool:
return False
class B:
def __lt__(self, other) -> B:
return self
class C:
def __lt__(self, other) -> C:
return self
x = A() < B() < C()
reveal_type(x) # revealed: (A & ~AlwaysTruthy) | B

View File

@@ -152,13 +152,25 @@ of the dunder methods.)
```py
from __future__ import annotations
class EqReturnType: ...
class NeReturnType: ...
class LtReturnType: ...
class LeReturnType: ...
class GtReturnType: ...
class GeReturnType: ...
class A:
def __eq__(self, o: object) -> EqReturnType: # error: [invalid-method-override]
return EqReturnType()
@@ -178,6 +190,7 @@ class A:
def __ge__(self, o: A) -> GeReturnType:
return GeReturnType()
a = (A(), A())
reveal_type(a == a) # revealed: bool
@@ -198,12 +211,15 @@ reveal_type(b <= c) # revealed: Literal[True]
reveal_type(b > c) # revealed: Literal[False]
reveal_type(b >= c) # revealed: Literal[False]
class LtReturnTypeOnB: ...
class B:
def __lt__(self, o: B) -> LtReturnTypeOnB:
return LtReturnTypeOnB()
reveal_type((A(), B()) < (A(), B())) # revealed: LtReturnType | LtReturnTypeOnB | Literal[False]
```
@@ -262,6 +278,7 @@ comparison can clearly conclude before encountering an error, the error should n
```py
def _(n: int, s: str):
class A: ...
# error: [unsupported-operator] "Operator `<` is not supported between two objects of type `A`"
A() < A()
# error: [unsupported-operator] "Operator `<=` is not supported between two objects of type `A`"
@@ -466,6 +483,7 @@ def compute_chained_comparison():
class NotBoolable:
__bool__: int = 5
class Comparable:
def __lt__(self, other) -> NotBoolable:
return NotBoolable()
@@ -473,6 +491,7 @@ class Comparable:
def __gt__(self, other) -> NotBoolable:
return NotBoolable()
a = (1, Comparable())
b = (1, Comparable())
@@ -494,11 +513,13 @@ pair of elements at equivalent positions cannot be converted to a `bool`:
class NotBoolable:
__bool__: None = None
class A:
# error: [invalid-method-override]
def __eq__(self, other) -> NotBoolable:
return NotBoolable()
# error: [unsupported-bool-conversion]
(A(),) == (A(),)
```
@@ -509,9 +530,11 @@ class A:
from __future__ import annotations
from typing import NamedTuple
class Node(NamedTuple):
parent: Node | None
def _(n: Node):
reveal_type(n.parent is n) # revealed: bool
```

View File

@@ -75,6 +75,7 @@ back to `bool` for the result type instead of trying to infer something more pre
```py
from typing import Literal
def _(
x: list[int] | Literal[1],
y: list[int] | Literal[1],

View File

@@ -5,6 +5,7 @@
```py
def _(flag: bool, flag1: bool, flag2: bool):
class A: ...
a = 1 in 7 # error: "Operator `in` is not supported between objects of type `Literal[1]` and `Literal[7]`"
reveal_type(a) # revealed: bool

View File

@@ -6,14 +6,17 @@
# revealed: int
[reveal_type(x) for x in range(3)]
class Row:
def __next__(self) -> range:
return range(3)
class Table:
def __iter__(self) -> Row:
return Row()
# revealed: tuple[int, range]
[reveal_type((cell, row)) for row in Table() for cell in row]
@@ -38,10 +41,12 @@ class Row:
def __next__(self) -> range:
return range(3)
class Table:
def __iter__(self) -> Row:
return Row()
# revealed: tuple[int, range]
[[reveal_type((cell, row)) for cell in row] for row in Table()]
```
@@ -85,6 +90,7 @@ Starred expressions must be iterable
```py
class NotIterable: ...
# This is fine:
x = [*range(3)]
@@ -101,10 +107,12 @@ class AsyncIterator:
async def __anext__(self) -> int:
return 42
class AsyncIterable:
def __aiter__(self) -> AsyncIterator:
return AsyncIterator()
async def _():
# revealed: int
[reveal_type(x) async for x in AsyncIterable()]
@@ -180,6 +188,7 @@ The type context is propagated down into the comprehension:
class Person(TypedDict):
name: str
# TODO: This should not error.
# error: [invalid-assignment]
persons: list[Person] = [{"name": n} for n in ["Alice", "Bob"]]

View File

@@ -42,6 +42,7 @@ def _(flag: bool):
class NotBoolable:
__bool__: int = 3
# error: [unsupported-bool-conversion] "Boolean conversion is not supported for type `NotBoolable`"
3 if NotBoolable() else 4
```

View File

@@ -122,6 +122,7 @@ def _(flag: bool, flag2: bool):
def check(x: int) -> bool:
return bool(x)
if check(x := 1):
x = 2
elif check(x := 3):
@@ -136,6 +137,7 @@ reveal_type(x) # revealed: Literal[2, 3, 4]
def check(x) -> bool:
return bool(x)
def _(flag: bool):
x = 1 if flag else None
y = 0
@@ -154,6 +156,7 @@ def _(flag: bool):
class NotBoolable:
__bool__: int = 3
# error: [unsupported-bool-conversion] "Boolean conversion is not supported for type `NotBoolable`"
if NotBoolable():
...

View File

@@ -63,10 +63,12 @@ This leads us to infer `Literal[1, 3]` as the type of `y` after the `match` stat
```py
from typing import final
@final
class C:
pass
def _(subject: C):
y = 1
match subject:
@@ -85,19 +87,24 @@ all subpatterns in the class pattern match.
```py
from typing import final
class Foo:
pass
class FooSub(Foo):
pass
class Bar:
pass
@final
class Baz:
pass
def _(target: FooSub):
y = 1
@@ -111,6 +118,7 @@ def _(target: FooSub):
reveal_type(y) # revealed: Literal[3]
def _(target: FooSub):
y = 1
@@ -124,6 +132,7 @@ def _(target: FooSub):
reveal_type(y) # revealed: Literal[3, 4]
def _(target: FooSub | str):
y = 1
@@ -144,13 +153,16 @@ def _(target: FooSub | str):
from typing_extensions import assert_never
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
class Other: ...
def _(target: Point):
y = 1
@@ -164,6 +176,7 @@ def _(target: Point):
reveal_type(y) # revealed: Literal[1, 2, 3, 4]
def _(target: Point):
match target:
case Point(x, y): # irrefutable sub-patterns
@@ -171,6 +184,7 @@ def _(target: Point):
case _:
assert_never(target)
def _(target: Point | Other):
match target:
case Point(0, 0):
@@ -190,6 +204,7 @@ Singleton patterns are matched based on identity, not equality comparisons or `i
```py
from typing import Literal
def _(target: Literal[True, False]):
y = 1
@@ -203,6 +218,7 @@ def _(target: Literal[True, False]):
reveal_type(y) # revealed: Literal[2, 3]
def _(target: bool):
y = 1
@@ -216,6 +232,7 @@ def _(target: bool):
reveal_type(y) # revealed: Literal[2, 3]
def _(target: None):
y = 1
@@ -229,6 +246,7 @@ def _(target: None):
reveal_type(y) # revealed: Literal[4]
def _(target: None | Literal[True]):
y = 1
@@ -242,6 +260,7 @@ def _(target: None | Literal[True]):
reveal_type(y) # revealed: Literal[2, 4]
# bool is an int subclass
def _(target: int):
y = 1
@@ -256,6 +275,7 @@ def _(target: int):
reveal_type(y) # revealed: Literal[1, 2, 3]
def _(target: str):
y = 1
@@ -275,10 +295,12 @@ def _(target: str):
```py
from enum import Enum
class Answer(Enum):
NO = 0
YES = 1
def _(answer: Answer):
y = 0
match answer:
@@ -299,6 +321,7 @@ A `|` pattern matches if any of the subpatterns match.
```py
from typing import Literal, final
def _(target: Literal["foo", "baz"]):
y = 1
@@ -310,6 +333,7 @@ def _(target: Literal["foo", "baz"]):
reveal_type(y) # revealed: Literal[2, 3]
def _(target: None):
y = 1
@@ -321,10 +345,12 @@ def _(target: None):
reveal_type(y) # revealed: Literal[2]
@final
class Baz:
pass
def _(target: int | None | float):
y = 1
@@ -336,8 +362,10 @@ def _(target: int | None | float):
reveal_type(y) # revealed: Literal[1, 2]
class Foo: ...
def _(target: None | Foo):
y = 1
@@ -375,6 +403,7 @@ def _(target: int | str):
class NotBoolable:
__bool__: int = 3
def _(target: int, flag: NotBoolable):
y = 1
match target:
@@ -395,10 +424,12 @@ is not covered by any case, even when all enum members are covered.
```py
from enum import Enum
class Answer(Enum):
YES = 1
NO = 2
def _(answer: Answer | None):
y = 0
match answer:
@@ -411,6 +442,7 @@ def _(answer: Answer | None):
# so y could still be 0
reveal_type(y) # revealed: Literal[0, 1, 2]
def _(answer: Answer | None):
match answer:
case Answer.YES:
@@ -422,8 +454,10 @@ def _(answer: Answer | None):
reveal_type(answer) # revealed: None
return 3
class Foo: ...
def _(answer: Answer | None):
match answer:
case Answer.YES:

View File

@@ -7,10 +7,12 @@ Deferred annotations can result in cycles in resolving a function signature:
```py
from __future__ import annotations
# error: [invalid-type-form]
def f(x: f):
pass
reveal_type(f) # revealed: def f(x: Unknown) -> Unknown
```
@@ -27,6 +29,7 @@ class Point:
def replace_with(self, other: "Point") -> None:
self.x, self.y = other.x, other.y
p = Point()
reveal_type(p.x) # revealed: Unknown | int
reveal_type(p.y) # revealed: Unknown | int
@@ -44,15 +47,18 @@ from typing import Union, TypeAliasType, Sequence, Mapping
A = list["A" | None]
def f(x: A):
# TODO: should be `list[A | None]`?
reveal_type(x) # revealed: list[Divergent]
# TODO: should be `A | None`?
reveal_type(x[0]) # revealed: Divergent
JSONPrimitive = Union[str, int, float, bool, None]
JSONValue = TypeAliasType("JSONValue", 'Union[JSONPrimitive, Sequence["JSONValue"], Mapping[str, "JSONValue"]]')
def _(x: JSONValue):
# TODO: should be `JSONValue`
reveal_type(x) # revealed: Divergent
@@ -65,6 +71,7 @@ from typing import Generic, TypeVar
B = TypeVar("B", bound="Base")
class Base(Generic[B]):
pass
```
@@ -86,24 +93,28 @@ class C:
def f(self: "C"):
def inner_a(positional=self.a):
return
self.a = inner_a
# revealed: def inner_a(positional=...) -> Unknown
reveal_type(inner_a)
def inner_b(*, kw_only=self.b):
return
self.b = inner_b
# revealed: def inner_b(*, kw_only=...) -> Unknown
reveal_type(inner_b)
def inner_c(positional_only=self.c, /):
return
self.c = inner_c
# revealed: def inner_c(positional_only=..., /) -> Unknown
reveal_type(inner_c)
def inner_d(*, kw_only=self.d):
return
self.d = inner_d
# revealed: def inner_d(*, kw_only=...) -> Unknown
reveal_type(inner_d)
@@ -116,6 +127,7 @@ class D:
def f(self: "D"):
# error: [invalid-parameter-default] "Default value of type `Unknown | (def inner_a(a: int = ...) -> Unknown)` is not assignable to annotated parameter type `int`"
def inner_a(a: int = self.a): ...
self.a = inner_a
```
@@ -153,6 +165,7 @@ class Cyclic:
if isinstance(self.data, str):
self.data = {"url": self.data}
# revealed: Unknown | str | dict[Unknown, Unknown] | dict[Unknown | str, Unknown | str]
reveal_type(Cyclic("").data)
```

View File

@@ -13,16 +13,19 @@ class, or metaclass is a `dataclass`-like construct.
```py
from typing_extensions import dataclass_transform
@dataclass_transform()
def my_dataclass[T](cls: type[T]) -> type[T]:
# modify cls
return cls
@my_dataclass
class Person:
name: str
age: int | None = None
Person("Alice", 20)
Person("Bob", None)
Person("Bob")
@@ -38,18 +41,22 @@ If we want our `dataclass`-like decorator to also take parameters, that is also
```py
from typing_extensions import dataclass_transform, Callable
@dataclass_transform()
def versioned_class[T](*, version: int = 1):
def decorator(cls):
# modify cls
return cls
return decorator
@versioned_class(version=2)
class Person:
name: str
age: int | None = None
Person("Alice", 20)
# error: [missing-argument]
@@ -61,6 +68,7 @@ We properly type-check the arguments to the decorator:
```py
from typing_extensions import dataclass_transform, Callable
# error: [invalid-argument-type]
@versioned_class(version="a string")
class C:
@@ -77,16 +85,19 @@ The examples from this section are straight from the Python documentation on
```py
from typing_extensions import dataclass_transform
@dataclass_transform()
def create_model[T](cls: type[T]) -> type[T]:
...
return cls
@create_model
class CustomerModel:
id: int
name: str
CustomerModel(id=1, name="Test")
```
@@ -95,15 +106,19 @@ CustomerModel(id=1, name="Test")
```py
from typing_extensions import dataclass_transform
@dataclass_transform()
class ModelMeta(type): ...
class ModelBase(metaclass=ModelMeta): ...
class CustomerModel(ModelBase):
id: int
name: str
CustomerModel(id=1, name="Test")
# error: [missing-argument]
@@ -115,13 +130,16 @@ CustomerModel()
```py
from typing_extensions import dataclass_transform
@dataclass_transform()
class ModelBase: ...
class CustomerModel(ModelBase):
id: int
name: str
CustomerModel(id=1, name="Test")
```
@@ -140,52 +158,67 @@ This can be overwritten using the `order` argument to the custom decorator:
```py
from typing_extensions import dataclass_transform
@dataclass_transform()
def normal(*, order: bool = False):
raise NotImplementedError
@dataclass_transform(order_default=False)
def order_default_false(*, order: bool = False):
raise NotImplementedError
@dataclass_transform(order_default=True)
def order_default_true(*, order: bool = True):
raise NotImplementedError
@normal
class Normal:
inner: int
Normal(1) < Normal(2) # error: [unsupported-operator]
@normal(order=True)
class NormalOverwritten:
inner: int
reveal_type(NormalOverwritten(1) < NormalOverwritten(2)) # revealed: bool
@order_default_false
class OrderFalse:
inner: int
OrderFalse(1) < OrderFalse(2) # error: [unsupported-operator]
@order_default_false(order=True)
class OrderFalseOverwritten:
inner: int
reveal_type(OrderFalseOverwritten(1) < OrderFalseOverwritten(2)) # revealed: bool
@order_default_true
class OrderTrue:
inner: int
reveal_type(OrderTrue(1) < OrderTrue(2)) # revealed: bool
@order_default_true(order=False)
class OrderTrueOverwritten:
inner: int
# error: [unsupported-operator]
OrderTrueOverwritten(1) < OrderTrueOverwritten(2)
```
@@ -196,11 +229,14 @@ This also works for metaclass-based transformers:
@dataclass_transform(order_default=True)
class OrderedModelMeta(type): ...
class OrderedModel(metaclass=OrderedModelMeta): ...
class TestWithMeta(OrderedModel):
inner: int
reveal_type(TestWithMeta(1) < TestWithMeta(2)) # revealed: bool
```
@@ -210,9 +246,11 @@ And for base-class-based transformers:
@dataclass_transform(order_default=True)
class OrderedModelBase: ...
class TestWithBase(OrderedModelBase):
inner: int
reveal_type(TestWithBase(1) < TestWithBase(2)) # revealed: bool
```
@@ -224,12 +262,14 @@ When provided, sets the default value for the `kw_only` parameter of `field()`.
from typing import dataclass_transform
from dataclasses import field
@dataclass_transform(kw_only_default=True)
def create_model(*, kw_only: bool = True): ...
@create_model()
class A:
name: str
a = A(name="Harry")
# error: [missing-argument]
# error: [too-many-positional-arguments]
@@ -244,6 +284,7 @@ class CustomerModel:
id: int
name: str
c = CustomerModel(1, "Harry")
```
@@ -253,11 +294,14 @@ This also works for metaclass-based transformers:
@dataclass_transform(kw_only_default=True)
class ModelMeta(type): ...
class ModelBase(metaclass=ModelMeta): ...
class TestMeta(ModelBase):
name: str
reveal_type(TestMeta.__init__) # revealed: (self: TestMeta, *, name: str) -> None
```
@@ -267,9 +311,11 @@ And for base-class-based transformers:
@dataclass_transform(kw_only_default=True)
class ModelBase: ...
class TestBase(ModelBase):
name: str
reveal_type(TestBase.__init__) # revealed: (self: TestBase, *, name: str) -> None
```
@@ -280,12 +326,14 @@ When provided, sets the default value for the `frozen` parameter of `field()`.
```py
from typing import dataclass_transform
@dataclass_transform(frozen_default=True)
def create_model(*, frozen: bool = True): ...
@create_model()
class ImmutableModel:
name: str
i = ImmutableModel(name="test")
i.name = "new" # error: [invalid-assignment]
```
@@ -297,6 +345,7 @@ Again, this can be overridden by setting `frozen=False` when applying the decora
class MutableModel:
name: str
m = MutableModel(name="test")
m.name = "new" # No error
```
@@ -307,11 +356,14 @@ This also works for metaclass-based transformers:
@dataclass_transform(frozen_default=True)
class ModelMeta(type): ...
class ModelBase(metaclass=ModelMeta): ...
class TestMeta(ModelBase):
name: str
t = TestMeta(name="test")
t.name = "new" # error: [invalid-assignment]
```
@@ -322,9 +374,11 @@ And for base-class-based transformers:
@dataclass_transform(frozen_default=True)
class ModelBase: ...
class TestMeta(ModelBase):
name: str
t = TestMeta(name="test")
t.name = "new" # error: [invalid-assignment]
@@ -337,6 +391,7 @@ Combining several of these parameters also works as expected:
```py
from typing import dataclass_transform
@dataclass_transform(eq_default=True, order_default=False, kw_only_default=True, frozen_default=True)
def create_model(*, eq: bool = True, order: bool = False, kw_only: bool = True, frozen: bool = True): ...
@create_model(eq=False, order=True, kw_only=False, frozen=False)
@@ -344,6 +399,7 @@ class OverridesAllParametersModel:
name: str
age: int
# Positional arguments are allowed:
model = OverridesAllParametersModel("test", 25)
@@ -365,21 +421,25 @@ from `order=False` (default) to `order=True`:
```py
from typing import dataclass_transform
@dataclass_transform(frozen_default=True)
def default_frozen_model(*, frozen: bool = True, order: bool = False): ...
@default_frozen_model()
class Frozen:
name: str
f = Frozen(name="test")
f.name = "new" # error: [invalid-assignment]
Frozen(name="A") < Frozen(name="B") # error: [unsupported-operator]
@default_frozen_model(frozen=False, order=True)
class Mutable:
name: str
m = Mutable(name="test")
m.name = "new" # No error
@@ -391,6 +451,7 @@ reveal_type(Mutable(name="A") < Mutable(name="B")) # revealed: bool
```py
from typing import dataclass_transform
@dataclass_transform(frozen_default=True)
class DefaultFrozenMeta(type):
def __new__(
@@ -403,19 +464,24 @@ class DefaultFrozenMeta(type):
order: bool = False,
): ...
class DefaultFrozenModel(metaclass=DefaultFrozenMeta): ...
class Frozen(DefaultFrozenModel):
name: str
f = Frozen(name="test")
f.name = "new" # error: [invalid-assignment]
Frozen(name="A") < Frozen(name="B") # error: [unsupported-operator]
class Mutable(DefaultFrozenModel, frozen=False, order=True):
name: str
m = Mutable(name="test")
# TODO: This should not be an error. In order to support this, we need to implement the precise `frozen` semantics of
# `dataclass_transform` described here: https://typing.python.org/en/latest/spec/dataclasses.html#dataclass-semantics
@@ -429,6 +495,7 @@ reveal_type(Mutable(name="A") < Mutable(name="B")) # revealed: bool
```py
from typing import dataclass_transform
@dataclass_transform(frozen_default=True)
class DefaultFrozenModel:
def __init_subclass__(
@@ -438,17 +505,21 @@ class DefaultFrozenModel:
order: bool = False,
): ...
class Frozen(DefaultFrozenModel):
name: str
f = Frozen(name="test")
f.name = "new" # error: [invalid-assignment]
Frozen(name="A") < Frozen(name="B") # error: [unsupported-operator]
class Mutable(DefaultFrozenModel, frozen=False, order=True):
name: str
m = Mutable(name="test")
m.name = "new" # No error
@@ -467,20 +538,25 @@ from typing_extensions import dataclass_transform, TypeVar, Callable
T = TypeVar("T", bound=type)
@dataclass_transform()
def fancy_model(*, slots: bool = False) -> Callable[[T], T]:
raise NotImplementedError
@fancy_model()
class NoSlots:
name: str
NoSlots.__slots__ # error: [unresolved-attribute]
@fancy_model(slots=True)
class WithSlots:
name: str
reveal_type(WithSlots.__slots__) # revealed: tuple[Literal["name"]]
```
@@ -489,23 +565,29 @@ reveal_type(WithSlots.__slots__) # revealed: tuple[Literal["name"]]
```py
from typing_extensions import dataclass_transform
@dataclass_transform()
class FancyMeta(type):
def __new__(cls, name, bases, namespace, *, slots: bool = False):
...
return super().__new__(cls, name, bases, namespace)
class FancyBase(metaclass=FancyMeta): ...
class NoSlots(FancyBase):
name: str
# error: [unresolved-attribute]
NoSlots.__slots__
class WithSlots(FancyBase, slots=True):
name: str
reveal_type(WithSlots.__slots__) # revealed: tuple[Literal["name"]]
```
@@ -514,20 +596,25 @@ reveal_type(WithSlots.__slots__) # revealed: tuple[Literal["name"]]
```py
from typing_extensions import dataclass_transform
@dataclass_transform()
class FancyBase:
def __init_subclass__(cls, *, slots: bool = False):
...
super().__init_subclass__()
class NoSlots(FancyBase):
name: str
NoSlots.__slots__ # error: [unresolved-attribute]
class WithSlots(FancyBase, slots=True):
name: str
reveal_type(WithSlots.__slots__) # revealed: tuple[Literal["name"]]
```
@@ -545,18 +632,21 @@ checkers do not seem to support this either.
```py
from typing_extensions import dataclass_transform, Any
def fancy_field(*, init: bool = True, kw_only: bool = False, alias: str | None = None) -> Any: ...
@dataclass_transform(field_specifiers=(fancy_field,))
def fancy_model[T](cls: type[T]) -> type[T]:
...
return cls
@fancy_model
class Person:
id: int = fancy_field(init=False)
internal_name: str = fancy_field(alias="name")
age: int | None = fancy_field(kw_only=True)
reveal_type(Person.__init__) # revealed: (self: Person, name: str, *, age: int | None) -> None
alice = Person("Alice", age=30)
@@ -571,6 +661,7 @@ reveal_type(alice.age) # revealed: int | None
```py
from typing_extensions import dataclass_transform, Any
def fancy_field(*, init: bool = True, kw_only: bool = False, alias: str | None = None) -> Any: ...
@dataclass_transform(field_specifiers=(fancy_field,))
class FancyMeta(type):
@@ -578,13 +669,16 @@ class FancyMeta(type):
...
return super().__new__(cls, name, bases, namespace)
class FancyBase(metaclass=FancyMeta): ...
class Person(FancyBase):
id: int = fancy_field(init=False)
internal_name: str = fancy_field(alias="name")
age: int | None = fancy_field(kw_only=True)
reveal_type(Person.__init__) # revealed: (self: Person, name: str, *, age: int | None) -> None
alice = Person("Alice", age=30)
@@ -599,6 +693,7 @@ reveal_type(alice.age) # revealed: int | None
```py
from typing_extensions import dataclass_transform, Any
def fancy_field(*, init: bool = True, kw_only: bool = False, alias: str | None = None) -> Any: ...
@dataclass_transform(field_specifiers=(fancy_field,))
class FancyBase:
@@ -606,11 +701,13 @@ class FancyBase:
...
super().__init_subclass__()
class Person(FancyBase):
id: int = fancy_field(init=False)
internal_name: str = fancy_field(alias="name")
age: int | None = fancy_field(kw_only=True)
reveal_type(Person.__init__) # revealed: (self: Person, name: str, *, age: int | None) -> None
alice = Person("Alice", age=30)
@@ -627,17 +724,20 @@ Field specifiers can have default arguments that should be respected:
```py
from typing_extensions import dataclass_transform, Any
def fancy_field(*, init: bool = False) -> Any: ...
@dataclass_transform(field_specifiers=(fancy_field,))
def fancy_model[T](cls: type[T]) -> type[T]:
...
return cls
@fancy_model
class Person:
id: int = fancy_field()
name: str = fancy_field(init=True)
reveal_type(Person.__init__) # revealed: (self: Person, name: str) -> None
Person(name="Alice")
@@ -655,11 +755,13 @@ correctly when passed via `**kwargs` for all three kinds of transformers.
from typing import Any
from typing_extensions import dataclass_transform
def field(**kwargs: Any) -> Any: ...
@dataclass_transform(field_specifiers=(field,))
def create_model[T](cls: type[T]) -> type[T]:
return cls
@create_model
class Person:
id: int = field(init=False)
@@ -669,6 +771,7 @@ class Person:
email: str = field(kw_only=True)
internal_notes: str = field(alias="notes")
# revealed: (self: Person, name: str, age: int = ..., tags: list[str] = ..., notes: str, *, email: str) -> None
reveal_type(Person.__init__)
@@ -682,12 +785,15 @@ Person("Bob", email="bob@example.com", notes="other notes")
from typing import Any
from typing_extensions import dataclass_transform
def field(**kwargs: Any) -> Any: ...
@dataclass_transform(field_specifiers=(field,))
class ModelMeta(type): ...
class ModelBase(metaclass=ModelMeta): ...
class Person(ModelBase):
id: int = field(init=False)
name: str
@@ -696,6 +802,7 @@ class Person(ModelBase):
email: str = field(kw_only=True)
internal_notes: str = field(alias="notes")
# revealed: (self: Person, name: str, age: int = ..., tags: list[str] = ..., notes: str, *, email: str) -> None
reveal_type(Person.__init__)
@@ -709,10 +816,12 @@ Person("Bob", email="bob@example.com", notes="other notes")
from typing import Any
from typing_extensions import dataclass_transform
def field(**kwargs: Any) -> Any: ...
@dataclass_transform(field_specifiers=(field,))
class ModelBase: ...
class Person(ModelBase):
id: int = field(init=False)
name: str
@@ -721,6 +830,7 @@ class Person(ModelBase):
email: str = field(kw_only=True)
internal_notes: str = field(alias="notes")
# revealed: (self: Person, name: str, age: int = ..., tags: list[str] = ..., notes: str, *, email: str) -> None
reveal_type(Person.__init__)
@@ -736,11 +846,13 @@ the synthesized `__init__` method.
```py
from typing_extensions import dataclass_transform, Any
def field_with_alias(*, alias: str | None = None, kw_only: bool = False) -> Any: ...
@dataclass_transform(field_specifiers=(field_with_alias,))
def model[T](cls: type[T]) -> type[T]:
return cls
@model
class Person:
internal_name: str = field_with_alias(alias="name")
@@ -785,6 +897,7 @@ p = Person(name="Alice", internal_age=30)
```py
from typing_extensions import dataclass_transform, overload, Any
@overload
def fancy_field(*, init: bool = True) -> Any: ...
@overload
@@ -795,12 +908,14 @@ def fancy_model[T](cls: type[T]) -> type[T]:
...
return cls
@fancy_model
class Person:
id: int = fancy_field(init=False)
name: str = fancy_field()
age: int | None = fancy_field(kw_only=True)
reveal_type(Person.__init__) # revealed: (self: Person, name: str, *, age: int | None) -> None
```
@@ -812,18 +927,21 @@ Make sure that models are only affected by the field specifiers of their own tra
from typing_extensions import dataclass_transform, Any
from dataclasses import field
def outer_field(*, init: bool = True, kw_only: bool = False) -> Any: ...
@dataclass_transform(field_specifiers=(outer_field,))
def outer_model[T](cls: type[T]) -> type[T]:
# ...
return cls
def inner_field(*, init: bool = True, kw_only: bool = False) -> Any: ...
@dataclass_transform(field_specifiers=(inner_field,))
def inner_model[T](cls: type[T]) -> type[T]:
# ...
return cls
@outer_model
class Outer:
@inner_model
@@ -834,6 +952,7 @@ class Outer:
outer_a: int = outer_field(init=False)
outer_b: str = inner_field(init=False)
reveal_type(Outer.__init__) # revealed: (self: Outer, outer_b: str = ...) -> None
reveal_type(Outer.Inner.__init__) # revealed: (self: Inner, inner_b: str = ...) -> None
```
@@ -850,6 +969,7 @@ from typing_extensions import dataclass_transform, TypeVar, Callable, overload
T = TypeVar("T", bound=type)
@overload
def versioned_class(
cls: T,
@@ -869,14 +989,17 @@ def versioned_class(
) -> T | Callable[[T], T]:
raise NotImplementedError
@versioned_class
class D1:
x: str
@versioned_class(version=2)
class D2:
x: str
D1("a")
D2("a")
@@ -891,6 +1014,7 @@ from typing_extensions import dataclass_transform, TypeVar, Callable, overload
T = TypeVar("T", bound=type)
@overload
@dataclass_transform()
def versioned_class(
@@ -910,14 +1034,17 @@ def versioned_class(
) -> T | Callable[[T], T]:
raise NotImplementedError
@versioned_class
class D1:
x: str
@versioned_class(version=2)
class D2:
x: str
D1("a")
D2("a")
@@ -937,17 +1064,21 @@ sure that we recognize all fields in a hierarchy like this:
from dataclasses import dataclass
from typing import dataclass_transform
@dataclass_transform()
class ModelMeta(type):
pass
class Sensor(metaclass=ModelMeta):
key: int
@dataclass(frozen=True, kw_only=True)
class TemperatureSensor(Sensor):
name: str
t = TemperatureSensor(key=1, name="Temperature Sensor")
reveal_type(t.key) # revealed: int
reveal_type(t.name) # revealed: str
@@ -965,15 +1096,18 @@ enables use of `dataclasses.fields`, `dataclasses.asdict`, `dataclasses.replace`
from dataclasses import fields, asdict, replace, Field
from typing import dataclass_transform, Any
@dataclass_transform()
def create_model[T](cls: type[T]) -> type[T]:
return cls
@create_model
class Person:
name: str
age: int
p = Person("Alice", 30)
reveal_type(Person.__dataclass_fields__) # revealed: dict[str, Field[Any]]
@@ -990,15 +1124,19 @@ reveal_type(replace(p, name="Bob")) # revealed: Person
from dataclasses import fields, asdict, replace, Field
from typing import dataclass_transform, Any
@dataclass_transform()
class ModelMeta(type): ...
class ModelBase(metaclass=ModelMeta): ...
class Person(ModelBase):
name: str
age: int
p = Person("Alice", 30)
reveal_type(Person.__dataclass_fields__) # revealed: dict[str, Field[Any]]
@@ -1015,13 +1153,16 @@ reveal_type(replace(p, name="Bob")) # revealed: Person
from dataclasses import fields, asdict, replace, Field
from typing import dataclass_transform, Any
@dataclass_transform()
class ModelBase: ...
class Person(ModelBase):
name: str
age: int
p = Person("Alice", 30)
reveal_type(Person.__dataclass_fields__) # revealed: dict[str, Field[Any]]
@@ -1042,13 +1183,16 @@ When a function decorated with `@dataclass_transform()` is called directly with
```py
from typing_extensions import dataclass_transform
@dataclass_transform()
def my_dataclass[T](cls: type[T]) -> type[T]:
return cls
class A:
x: int
B = my_dataclass(A)
reveal_type(B) # revealed: <class 'A'>
@@ -1061,13 +1205,16 @@ B(1)
```py
from typing_extensions import dataclass_transform
@dataclass_transform()
def my_dataclass[T](cls: type[T], *, order: bool = False) -> type[T]:
return cls
class A:
x: int
B = my_dataclass(A, order=True)
reveal_type(B) # revealed: <class 'A'>
@@ -1083,6 +1230,7 @@ decorator), calling it with a class should return the class type.
```py
from typing_extensions import dataclass_transform, Callable, overload
@overload
@dataclass_transform()
def my_dataclass[T](cls: type[T]) -> type[T]: ...
@@ -1091,9 +1239,11 @@ def my_dataclass[T]() -> Callable[[type[T]], type[T]]: ...
def my_dataclass[T](cls: type[T] | None = None) -> type[T] | Callable[[type[T]], type[T]]:
raise NotImplementedError
class A:
x: int
B = my_dataclass(A)
reveal_type(B) # revealed: <class 'A'>
@@ -1109,13 +1259,16 @@ specialization should be preserved.
```py
from typing_extensions import dataclass_transform
@dataclass_transform()
def my_dataclass[T](cls: type[T]) -> type[T]:
return cls
class A[T]:
x: T
B = my_dataclass(A[int])
reveal_type(B) # revealed: <class 'A[int]'>
@@ -1132,22 +1285,28 @@ class, not to the parameter class.
```py
from typing_extensions import dataclass_transform
@dataclass_transform()
def hydrated_dataclass[T](target: type[T], *, frozen: bool = False):
def decorator[U](cls: type[U]) -> type[U]:
return cls
return decorator
class Target:
pass
decorator = hydrated_dataclass(Target)
reveal_type(decorator) # revealed: <decorator produced by dataclass-like function>
@hydrated_dataclass(Target)
class Model:
x: int
# Model should be a dataclass-like class with x as a field
Model(x=1)
reveal_type(Model.__init__) # revealed: (self: Model, x: int) -> None

View File

@@ -5,12 +5,14 @@
```py
from dataclasses import dataclass, field
@dataclass
class Member:
name: str
role: str = field(default="user")
tag: str | None = field(default=None, init=False)
# revealed: (self: Member, name: str, role: str = "user") -> None
reveal_type(Member.__init__)
@@ -32,11 +34,13 @@ field:
from dataclasses import dataclass, field
from datetime import datetime
@dataclass
class Data:
content: list[int] = field(default_factory=list)
timestamp: datetime = field(default_factory=datetime.now, init=False)
# revealed: (self: Data, content: list[int] = ...) -> None
reveal_type(Data.__init__)
@@ -57,12 +61,14 @@ If `kw_only` is set to `True`, the field can only be set using keyword arguments
```py
from dataclasses import dataclass, field
@dataclass
class Person:
name: str
age: int | None = field(default=None, kw_only=True)
role: str = field(default="user", kw_only=True)
# revealed: (self: Person, name: str, *, age: int | None = None, role: str = "user") -> None
reveal_type(Person.__init__)
@@ -77,9 +83,11 @@ bob = Person("Bob", 30)
```py
from dataclasses import field
def get_default() -> str:
return "default"
reveal_type(field(default=1)) # revealed: dataclasses.Field[Literal[1]]
reveal_type(field(default=None)) # revealed: dataclasses.Field[None]
reveal_type(field(default_factory=get_default)) # revealed: dataclasses.Field[str]
@@ -97,23 +105,29 @@ from typing import TypeVar
T = TypeVar("T")
@dataclass_transform()
def create_model(*, init: bool = True):
def deco(cls: type[T]) -> type[T]:
return cls
return deco
@create_model()
class A:
name: str = field(init=False)
# field(init=False) should be ignored for dataclass_transform without explicit field_specifiers
reveal_type(A.__init__) # revealed: (self: A, name: str) -> None
@dataclass
class B:
name: str = field(init=False)
# Regular @dataclass should respect field(init=False)
reveal_type(B.__init__) # revealed: (self: B) -> None
```

View File

@@ -13,9 +13,11 @@ of the decorator (which does not necessarily need to be a callable type):
def custom_decorator(f) -> int:
return 1
@custom_decorator
def f(x): ...
reveal_type(f) # revealed: int
```
@@ -26,13 +28,16 @@ More commonly, a decorator returns a modified callable type:
```py
from typing import Callable
def ensure_positive(wrapped: Callable[[int], bool]) -> Callable[[int], bool]:
return lambda x: wrapped(x) and x > 0
@ensure_positive
def even(x: int) -> bool:
return x % 2 == 0
reveal_type(even) # revealed: (int, /) -> bool
reveal_type(even(4)) # revealed: bool
```
@@ -45,15 +50,19 @@ arguments:
```py
from typing import Callable
def ensure_larger_than(lower_bound: int) -> Callable[[Callable[[int], bool]], Callable[[int], bool]]:
def decorator(wrapped: Callable[[int], bool]) -> Callable[[int], bool]:
return lambda x: wrapped(x) and x >= lower_bound
return decorator
@ensure_larger_than(10)
def even(x: int) -> bool:
return x % 2 == 0
reveal_type(even) # revealed: (int, /) -> bool
reveal_type(even(14)) # revealed: bool
```
@@ -67,17 +76,21 @@ meaning that the decorator closest to the function definition is applied first:
def maps_to_str(f) -> str:
return "a"
def maps_to_int(f) -> int:
return 1
def maps_to_bytes(f) -> bytes:
return b"a"
@maps_to_str
@maps_to_int
@maps_to_bytes
def f(x): ...
reveal_type(f) # revealed: str
```
@@ -97,10 +110,12 @@ class accept_strings:
def __call__(self, x: str | int) -> bool:
return self.f(int(x))
@accept_strings
def even(x: int) -> bool:
return x > 0
reveal_type(even) # revealed: accept_strings
reveal_type(even.custom_attribute) # revealed: str
reveal_type(even("1")) # revealed: bool
@@ -121,17 +136,21 @@ implemented using `functools.wraps`.
from typing import Callable
from functools import wraps
def custom_decorator(f) -> Callable[[int], str]:
@wraps(f)
def wrapper(*args, **kwargs):
print("Calling decorated function")
return f(*args, **kwargs)
return wrapper
@custom_decorator
def f(x: int) -> str:
return str(x)
reveal_type(f) # revealed: (int, /) -> str
```
@@ -140,10 +159,12 @@ reveal_type(f) # revealed: (int, /) -> str
```py
from functools import cache
@cache
def f(x: int) -> int:
return x**2
# revealed: _lru_cache_wrapper[int]
reveal_type(f)
# revealed: int
@@ -157,6 +178,7 @@ reveal_type(f(1))
def g(x: int) -> str:
return "a"
# TODO: This should be `Literal[g]` or `(int, /) -> str`
reveal_type(g) # revealed: Unknown
```
@@ -170,6 +192,7 @@ reveal_type(g) # revealed: Unknown
@unknown_decorator
def f(x): ...
reveal_type(f) # revealed: Unknown
```
@@ -180,6 +203,7 @@ reveal_type(f) # revealed: Unknown
@(1 + "a")
def f(x): ...
reveal_type(f) # revealed: Unknown
```
@@ -188,10 +212,12 @@ reveal_type(f) # revealed: Unknown
```py
non_callable = 1
# error: [call-non-callable] "Object of type `Literal[1]` is not callable"
@non_callable
def f(x): ...
reveal_type(f) # revealed: Unknown
```
@@ -206,10 +232,12 @@ first argument:
def wrong_signature(f: int) -> str:
return "a"
# error: [invalid-argument-type] "Argument to function `wrong_signature` is incorrect: Expected `int`, found `def f(x) -> Unknown`"
@wrong_signature
def f(x): ...
reveal_type(f) # revealed: str
```
@@ -221,15 +249,19 @@ Decorators need to be callable with a single argument. If they are not, we emit
def takes_two_arguments(f, g) -> str:
return "a"
# error: [missing-argument] "No argument provided for required parameter `g` of function `takes_two_arguments`"
@takes_two_arguments
def f(x): ...
reveal_type(f) # revealed: str
def takes_no_argument() -> str:
return "a"
# error: [too-many-positional-arguments] "Too many positional arguments to function `takes_no_argument`: expected 0, got 1"
@takes_no_argument
def g(x): ...
@@ -243,6 +275,7 @@ Class decorator calls are validated, emitting diagnostics for invalid arguments:
def takes_int(x: int) -> int:
return x
# error: [invalid-argument-type]
@takes_int
class Foo: ...
@@ -262,10 +295,12 @@ A decorator can enforce type constraints on the class being decorated:
def decorator(cls: type[int]) -> type[int]:
return cls
# error: [invalid-argument-type]
@decorator
class Baz: ...
# TODO: the revealed type should ideally be `type[int]` (the decorator's return type)
reveal_type(Baz) # revealed: <class 'Baz'>
```

View File

@@ -12,6 +12,7 @@ When a class defines `__eq__` and `__lt__`, the decorator synthesizes `__le__`,
```py
from functools import total_ordering
@total_ordering
class Student:
def __init__(self, grade: int):
@@ -25,6 +26,7 @@ class Student:
def __lt__(self, other: "Student") -> bool:
return self.grade < other.grade
s1 = Student(85)
s2 = Student(90)
@@ -47,6 +49,7 @@ other than the class itself:
```py
from functools import total_ordering
@total_ordering
class Comparable:
def __init__(self, value: int):
@@ -66,6 +69,7 @@ class Comparable:
return self.value < other
return NotImplemented
a = Comparable(10)
b = Comparable(20)
@@ -88,6 +92,7 @@ overridden.
```py
from functools import total_ordering
@total_ordering
class MultiSig:
def __init__(self, value: int):
@@ -95,13 +100,16 @@ class MultiSig:
def __eq__(self, other: object) -> bool:
return True
# __lt__ accepts `object` (highest priority, used as root)
def __lt__(self, other: object) -> bool:
return True
# __gt__ only accepts `MultiSig` (not overridden by decorator)
def __gt__(self, other: "MultiSig") -> bool:
return True
a = MultiSig(10)
b = MultiSig(20)
@@ -125,6 +133,7 @@ all overloads:
from functools import total_ordering
from typing import overload
@total_ordering
class Flexible:
def __init__(self, value: int):
@@ -142,6 +151,7 @@ class Flexible:
return self.value < other.value
return self.value < other
a = Flexible(10)
b = Flexible(20)
@@ -165,6 +175,7 @@ When a class defines `__eq__` and `__gt__`, the decorator synthesizes `__lt__`,
```py
from functools import total_ordering
@total_ordering
class Priority:
def __init__(self, level: int):
@@ -178,6 +189,7 @@ class Priority:
def __gt__(self, other: "Priority") -> bool:
return self.level > other.level
p1 = Priority(1)
p2 = Priority(2)
@@ -199,6 +211,7 @@ A class only needs to define a single comparison method. The `__eq__` method can
```py
from functools import total_ordering
@total_ordering
class Score:
def __init__(self, value: int):
@@ -207,6 +220,7 @@ class Score:
def __lt__(self, other: "Score") -> bool:
return self.value < other.value
s1 = Score(85)
s2 = Score(90)
@@ -226,10 +240,12 @@ The decorator also works when the ordering method is inherited from a superclass
```py
from functools import total_ordering
class Base:
def __lt__(self, other: "Base") -> bool:
return True
@total_ordering
class Child(Base):
def __eq__(self, other: object) -> bool:
@@ -237,6 +253,7 @@ class Child(Base):
return NotImplemented
return True
c1 = Child()
c2 = Child()
@@ -256,16 +273,19 @@ over the locally-defined `__gt__`:
from functools import total_ordering
from typing import Literal
class Base:
def __lt__(self, other: "Base") -> Literal[True]:
return True
@total_ordering
class Child(Base):
# __gt__ is defined locally, but __lt__ (inherited) takes precedence
def __gt__(self, other: "Child") -> Literal[False]:
return False
c1 = Child()
c2 = Child()
@@ -290,6 +310,7 @@ We use a narrower return type (`Literal[True]`) to verify that the explicit meth
from functools import total_ordering
from typing import Literal
@total_ordering
class Temperature:
def __init__(self, celsius: float):
@@ -301,6 +322,7 @@ class Temperature:
def __gt__(self, other: "Temperature") -> Literal[True]:
return True
t1 = Temperature(20.0)
t2 = Temperature(25.0)
@@ -321,6 +343,7 @@ The decorator works with `@dataclass`:
from dataclasses import dataclass
from functools import total_ordering
@total_ordering
@dataclass
class Point:
@@ -330,6 +353,7 @@ class Point:
def __lt__(self, other: "Point") -> bool:
return (self.x, self.y) < (other.x, other.y)
p1 = Point(1, 2)
p2 = Point(3, 4)
@@ -353,11 +377,13 @@ a diagnostic is emitted at the decorator site:
```py
from functools import total_ordering
@total_ordering # error: [invalid-total-ordering]
class NoOrdering:
def __eq__(self, other: object) -> bool:
return True
n1 = NoOrdering()
n2 = NoOrdering()
@@ -384,6 +410,7 @@ class NoDecorator:
def __lt__(self, other: "NoDecorator") -> bool:
return self.value < other.value
n1 = NoDecorator(1)
n2 = NoDecorator(2)
@@ -416,6 +443,7 @@ simplifies to `int`:
```py
from functools import total_ordering
@total_ordering
class IntReturn:
def __init__(self, value: int):
@@ -429,6 +457,7 @@ class IntReturn:
def __lt__(self, other: "IntReturn") -> int:
return self.value - other.value
a = IntReturn(10)
b = IntReturn(20)
@@ -447,6 +476,7 @@ When the root method returns a type that is not a supertype of `bool`, the union
```py
from functools import total_ordering
@total_ordering
class StrReturn:
def __init__(self, value: str):
@@ -460,6 +490,7 @@ class StrReturn:
def __lt__(self, other: "StrReturn") -> str:
return self.value
a = StrReturn("a")
b = StrReturn("b")
@@ -480,10 +511,12 @@ performed:
```py
from functools import total_ordering
class NoOrderingMethod:
def __eq__(self, other: object) -> bool:
return True
# error: [invalid-total-ordering]
InvalidOrderedClass = total_ordering(NoOrderingMethod)
```
@@ -493,6 +526,7 @@ When the class does define an ordering method, no error is emitted:
```py
from functools import total_ordering
class HasOrderingMethod:
def __eq__(self, other: object) -> bool:
return True
@@ -500,7 +534,108 @@ class HasOrderingMethod:
def __lt__(self, other: "HasOrderingMethod") -> bool:
return True
# No error (class defines `__lt__`).
ValidOrderedClass = total_ordering(HasOrderingMethod)
reveal_type(ValidOrderedClass) # revealed: type[HasOrderingMethod]
```
## Function call form with `type()`
When `total_ordering` is called on a class created with `type()`, the same validation is performed:
```py
from functools import total_ordering
def lt_impl(self, other) -> bool:
return True
# No error: the functional class defines `__lt__` in its namespace
ValidFunctional = total_ordering(type("ValidFunctional", (), {"__lt__": lt_impl}))
InvalidFunctionalBase = type("InvalidFunctionalBase", (), {})
# error: [invalid-total-ordering]
InvalidFunctional = total_ordering(InvalidFunctionalBase)
```
## Inherited from functional class
When a class inherits from a functional class that defines an ordering method, `@total_ordering`
correctly detects it:
```py
from functools import total_ordering
def lt_impl(self, other) -> bool:
return True
def eq_impl(self, other) -> bool:
return True
# Functional class with __lt__ method
OrderedBase = type("OrderedBase", (), {"__lt__": lt_impl})
# A class inheriting from OrderedBase gets the ordering method
@total_ordering
class Ordered(OrderedBase):
def __eq__(self, other: object) -> bool:
return True
o1 = Ordered()
o2 = Ordered()
# Inherited __lt__ is available
reveal_type(o1 < o2) # revealed: bool
# @total_ordering synthesizes the other methods
reveal_type(o1 <= o2) # revealed: bool
reveal_type(o1 > o2) # revealed: bool
reveal_type(o1 >= o2) # revealed: bool
```
When the dynamic base class does not define any ordering method, `@total_ordering` emits an error:
```py
from functools import total_ordering
# Dynamic class without ordering methods (invalid for @total_ordering)
NoOrderBase = type("NoOrderBase", (), {})
@total_ordering # error: [invalid-total-ordering]
class NoOrder(NoOrderBase):
def __eq__(self, other: object) -> bool:
return True
```
## Dynamic namespace
When a `type()`-constructed class has a dynamic namespace, we assume it might provide an ordering
method (since we can't know what's in the namespace). No error is emitted when such a class is
passed to `@total_ordering`:
```py
from functools import total_ordering
from typing import Any
def f(ns: dict[str, Any]):
# Dynamic class with dynamic namespace - might have ordering methods
DynamicBase = type("DynamicBase", (), ns)
# No error: the dynamic namespace might contain __lt__ or another ordering method
@total_ordering
class Ordered(DynamicBase):
def __eq__(self, other: object) -> bool:
return True
# Also works when calling total_ordering as a function
OrderedDirect = total_ordering(type("OrderedDirect", (), ns))
```

View File

@@ -21,9 +21,11 @@ reveal_type(x) # revealed: Unknown
# error: [unresolved-reference]
reveal_type(y) # revealed: Unknown
def cond() -> bool:
return True
b = 1
if cond():
del b
@@ -42,17 +44,21 @@ reveal_type(c) # revealed: Literal[2]
d = [1, 2, 3]
def delete():
del d # error: [unresolved-reference] "Name `d` used when not defined"
delete()
reveal_type(d) # revealed: list[Unknown | int]
def delete_element():
# When the `del` target isn't a name, it doesn't force local resolution.
del d[0]
print(d)
def delete_global():
global d
del d
@@ -60,10 +66,12 @@ def delete_global():
# be careful about false positives if `d` got reinitialized somehow in between the two `del`s.
del d
delete_global()
# Again, the variable should have been removed, but we don't check it.
reveal_type(d) # revealed: list[Unknown | int]
def delete_nonlocal():
e = 2
@@ -86,6 +94,7 @@ local error:
```py
x = 1
def foo():
print(x) # error: [unresolved-reference] "Name `x` used when not defined"
if False:
@@ -104,11 +113,14 @@ However, with `global x` in `foo`, `print(x)` in `bar` resolves in the global sc
```py
x = 1
def foo():
global x
def bar():
# allowed, refers to `x` in the global scope
reveal_type(x) # revealed: Literal[1]
bar()
del x # allowed, deletes `x` in the global scope (though we don't track that)
```
@@ -119,11 +131,14 @@ refer to:
```py
def enclosing():
x = 2
def foo():
nonlocal x
def bar():
# allowed, refers to `x` in `enclosing`
reveal_type(x) # revealed: Literal[2]
bar()
del x # allowed, deletes `x` in `enclosing` (though we don't track that)
```
@@ -141,6 +156,7 @@ assignment, and the attribute type will be the originally declared type.
class C:
x: int = 1
c = C()
del c.x
reveal_type(c.x) # revealed: int
@@ -160,6 +176,7 @@ reveal_type(c.x) # revealed: int
class C:
x: int = 1
c = C()
reveal_type(c.x) # revealed: int
@@ -202,17 +219,21 @@ from typing import Protocol, TypeVar
KT = TypeVar("KT")
class CanDelItem(Protocol[KT]):
def __delitem__(self, k: KT, /) -> None: ...
def f(x: CanDelItem[int], k: int):
# This should be valid - the object has __delitem__
del x[k]
class OnlyDelItem:
def __delitem__(self, key: int) -> None:
pass
d = OnlyDelItem()
del d[0] # OK
@@ -229,6 +250,7 @@ class OnlyGetItem:
def __getitem__(self, key: int) -> str:
return "value"
g = OnlyGetItem()
reveal_type(g[0]) # revealed: str
@@ -247,18 +269,22 @@ a valid instance of that TypedDict type. However, deleting `NotRequired` keys (o
```py
from typing_extensions import TypedDict, NotRequired
class Movie(TypedDict):
name: str
year: int
class PartialMovie(TypedDict, total=False):
name: str
year: int
class MixedMovie(TypedDict):
name: str
year: NotRequired[int]
m: Movie = {"name": "Blade Runner", "year": 1982}
p: PartialMovie = {"name": "Test"}
mixed: MixedMovie = {"name": "Test"}

View File

@@ -10,30 +10,36 @@ classes. Uses of these items should subsequently produce a warning.
```py
from typing_extensions import deprecated
@deprecated("use OtherClass")
def myfunc(x: int): ...
myfunc(1) # error: [deprecated] "use OtherClass"
```
```py
from typing_extensions import deprecated
@deprecated("use BetterClass")
class MyClass: ...
MyClass() # error: [deprecated] "use BetterClass"
```
```py
from typing_extensions import deprecated
class MyClass:
@deprecated("use something else")
def afunc(): ...
@deprecated("don't use this!")
def amethod(self): ...
MyClass.afunc() # error: [deprecated] "use something else"
MyClass().amethod() # error: [deprecated] "don't use this!"
```
@@ -59,18 +65,22 @@ runtime behavior.
```py
from typing_extensions import deprecated
@deprecated # error: [invalid-argument-type] "LiteralString"
def invalid_deco(): ...
invalid_deco() # error: [missing-argument]
```
```py
from typing_extensions import deprecated
@deprecated() # error: [missing-argument] "message"
def invalid_deco(): ...
invalid_deco()
```
@@ -82,9 +92,11 @@ from typing_extensions import deprecated
x = "message"
@deprecated(x)
def invalid_deco(): ...
invalid_deco() # error: [deprecated] "message"
```
@@ -93,12 +105,15 @@ However sufficiently opaque LiteralStrings we can't resolve, and so we lose the
```py
from typing_extensions import deprecated, LiteralString
def opaque() -> LiteralString:
return "message"
@deprecated(opaque())
def valid_deco(): ...
valid_deco() # error: [deprecated]
```
@@ -108,12 +123,15 @@ LiteralString, so we can/should emit a diagnostic for this:
```py
from typing_extensions import deprecated
def opaque() -> str:
return "message"
@deprecated(opaque()) # error: [invalid-argument-type] "LiteralString"
def dubious_deco(): ...
dubious_deco()
```
@@ -122,9 +140,11 @@ Although we have no use for the other arguments, we should still error if they'r
```py
from typing_extensions import deprecated
@deprecated("some message", dsfsdf="whatever") # error: [unknown-argument] "dsfsdf"
def invalid_deco(): ...
invalid_deco()
```
@@ -133,9 +153,11 @@ And we should always handle correct ones fine.
```py
from typing_extensions import deprecated
@deprecated("some message", category=DeprecationWarning, stacklevel=1)
def valid_deco(): ...
valid_deco() # error: [deprecated] "some message"
```
@@ -155,11 +177,13 @@ python-version = "3.13"
import warnings
import typing_extensions
@warnings.deprecated("nope")
def func1(): ...
@typing_extensions.deprecated("nada")
def func2(): ...
func1() # error: [deprecated] "nope"
func2() # error: [deprecated] "nada"
```
@@ -176,9 +200,11 @@ shouldn't produce a warning.
```py
from typing_extensions import deprecated
@deprecated("Use OtherType instead")
class DeprType: ...
@deprecated("Use other_func instead")
def depr_func(): ...
```
@@ -194,8 +220,10 @@ from module import DeprType, depr_func
DeprType() # error: [deprecated] "Use OtherType instead"
depr_func() # error: [deprecated] "Use other_func instead"
def higher_order(x): ...
# TODO: these diagnostics ideally shouldn't fire since we warn on the import
higher_order(DeprType) # error: [deprecated] "Use OtherType instead"
higher_order(depr_func) # error: [deprecated] "Use other_func instead"
@@ -215,9 +243,11 @@ a warning.
```py
from typing_extensions import deprecated
@deprecated("Use OtherType instead")
class DeprType: ...
@deprecated("Use other_func instead")
def depr_func(): ...
```
@@ -230,8 +260,10 @@ import module
module.DeprType() # error: [deprecated] "Use OtherType instead"
module.depr_func() # error: [deprecated] "Use other_func instead"
def higher_order(x): ...
higher_order(module.DeprType) # error: [deprecated] "Use OtherType instead"
higher_order(module.depr_func) # error: [deprecated] "Use other_func instead"
@@ -248,9 +280,11 @@ If the items are instead star-imported, then the actual uses should warn.
```py
from typing_extensions import deprecated
@deprecated("Use OtherType instead")
class DeprType: ...
@deprecated("Use other_func instead")
def depr_func(): ...
```
@@ -263,8 +297,10 @@ from module import *
DeprType() # error: [deprecated] "Use OtherType instead"
depr_func() # error: [deprecated] "Use other_func instead"
def higher_order(x): ...
higher_order(DeprType) # error: [deprecated] "Use OtherType instead"
higher_order(depr_func) # error: [deprecated] "Use other_func instead"
@@ -281,12 +317,15 @@ redundant and annoying.
```py
from typing_extensions import deprecated
@deprecated("Use OtherType instead")
class DeprType: ...
@deprecated("Use other_func instead")
def depr_func(): ...
alias_func = depr_func # error: [deprecated] "Use other_func instead"
AliasClass = DeprType # error: [deprecated] "Use OtherType instead"
@@ -303,6 +342,7 @@ diagnostic.
```py
from typing_extensions import deprecated
class MyInt:
def __init__(self, val):
self.val = val
@@ -311,6 +351,7 @@ class MyInt:
def __add__(self, other):
return MyInt(self.val + other.val)
x = MyInt(1)
y = MyInt(2)
z = x + y # TODO error: [deprecated] "MyInt `+` support is broken"
@@ -324,6 +365,7 @@ Overloads can be deprecated, but only trigger warnings when invoked.
from typing_extensions import deprecated
from typing_extensions import overload
@overload
@deprecated("strings are no longer supported")
def f(x: str): ...
@@ -332,6 +374,7 @@ def f(x: int): ...
def f(x):
print(x)
f(1)
f("hello") # TODO: error: [deprecated] "strings are no longer supported"
```
@@ -342,6 +385,7 @@ If the actual impl is deprecated, the deprecation always fires.
from typing_extensions import deprecated
from typing_extensions import overload
@overload
def f(x: str): ...
@overload
@@ -350,6 +394,7 @@ def f(x: int): ...
def f(x):
print(x)
f(1) # error: [deprecated] "unusable"
f("hello") # error: [deprecated] "unusable"
```

View File

@@ -16,6 +16,7 @@ descriptor that returns a constant value:
```py
from typing import Literal
class Ten:
def __get__(self, instance: object, owner: type | None = None) -> Literal[10]:
return 10
@@ -23,9 +24,11 @@ class Ten:
def __set__(self, instance: object, value: Literal[10]) -> None:
pass
class C:
ten: Ten = Ten()
c = C()
reveal_type(c.ten) # revealed: Literal[10]
@@ -66,9 +69,11 @@ class FlexibleInt:
def __set__(self, instance: object, value: int | str) -> None:
self._value = int(value)
class C:
flexible_int: FlexibleInt = FlexibleInt()
c = C()
reveal_type(c.flexible_int) # revealed: int | None
@@ -97,6 +102,7 @@ non-data descriptors.
```py
from typing import Literal
class DataDescriptor:
def __get__(self, instance: object, owner: type | None = None) -> Literal["data"]:
return "data"
@@ -104,10 +110,12 @@ class DataDescriptor:
def __set__(self, instance: object, value: int) -> None:
pass
class NonDataDescriptor:
def __get__(self, instance: object, owner: type | None = None) -> Literal["non-data"]:
return "non-data"
class C:
data_descriptor = DataDescriptor()
non_data_descriptor = NonDataDescriptor()
@@ -123,6 +131,7 @@ class C:
# So it is possible to override them.
self.non_data_descriptor = 1
c = C()
reveal_type(c.data_descriptor) # revealed: Unknown | Literal["data"]
@@ -148,6 +157,7 @@ all possible results accordingly. We start by defining a data and a non-data des
```py
from typing import Literal
class DataDescriptor:
def __get__(self, instance: object, owner: type | None = None) -> Literal["data"]:
return "data"
@@ -155,6 +165,7 @@ class DataDescriptor:
def __set__(self, instance: object, value: int) -> None:
pass
class NonDataDescriptor:
def __get__(self, instance: object, owner: type | None = None) -> Literal["non-data"]:
return "non-data"
@@ -186,8 +197,10 @@ descriptor here:
class C2:
def f(self):
self.attr = "normal"
attr = NonDataDescriptor()
reveal_type(C2().attr) # revealed: Unknown | Literal["non-data", "normal"]
# Assignments always go to the instance attribute in this case
@@ -201,14 +214,17 @@ Descriptors only work when used as class variables. When put in instances, they
```py
from typing import Literal
class Ten:
def __get__(self, instance: object, owner: type | None = None) -> Literal[10]:
return 10
class C:
def __init__(self):
self.ten: Ten = Ten()
reveal_type(C().ten) # revealed: Ten
C().ten = Ten()
@@ -233,6 +249,7 @@ To verify this, we define a data and a non-data descriptor:
```py
from typing import Literal, Any
class DataDescriptor:
def __get__(self, instance: object, owner: type | None = None) -> Literal["data"]:
return "data"
@@ -240,6 +257,7 @@ class DataDescriptor:
def __set__(self, instance: object, value: int) -> None:
pass
class NonDataDescriptor:
def __get__(self, instance: object, owner: type | None = None) -> Literal["non-data"]:
return "non-data"
@@ -253,10 +271,12 @@ class Meta1(type):
meta_data_descriptor: DataDescriptor = DataDescriptor()
meta_non_data_descriptor: NonDataDescriptor = NonDataDescriptor()
class C1(metaclass=Meta1):
class_data_descriptor: DataDescriptor = DataDescriptor()
class_non_data_descriptor: NonDataDescriptor = NonDataDescriptor()
reveal_type(C1.meta_data_descriptor) # revealed: Literal["data"]
reveal_type(C1.meta_non_data_descriptor) # revealed: Literal["non-data"]
@@ -293,6 +313,7 @@ class Meta2(type):
meta_data_descriptor1: DataDescriptor = DataDescriptor()
meta_data_descriptor2: DataDescriptor = DataDescriptor()
class ClassLevelDataDescriptor:
def __get__(self, instance: object, owner: type | None = None) -> Literal["class level data descriptor"]:
return "class level data descriptor"
@@ -300,10 +321,12 @@ class ClassLevelDataDescriptor:
def __set__(self, instance: object, value: str) -> None:
pass
class C2(metaclass=Meta2):
meta_data_descriptor1: Literal["value on class"] = "value on class"
meta_data_descriptor2: ClassLevelDataDescriptor = ClassLevelDataDescriptor()
reveal_type(C2.meta_data_descriptor1) # revealed: Literal["data"]
reveal_type(C2.meta_data_descriptor2) # revealed: Literal["data"]
@@ -326,12 +349,14 @@ class Meta3(type):
meta_non_data_descriptor1: NonDataDescriptor = NonDataDescriptor()
meta_non_data_descriptor2: NonDataDescriptor = NonDataDescriptor()
class C3(metaclass=Meta3):
meta_attribute1: Literal["value on class"] = "value on class"
meta_attribute2: ClassLevelDataDescriptor = ClassLevelDataDescriptor()
meta_non_data_descriptor1: Literal["value on class"] = "value on class"
meta_non_data_descriptor2: ClassLevelDataDescriptor = ClassLevelDataDescriptor()
reveal_type(C3.meta_attribute1) # revealed: Literal["value on class"]
reveal_type(C3.meta_attribute2) # revealed: Literal["class level data descriptor"]
reveal_type(C3.meta_non_data_descriptor1) # revealed: Literal["value on class"]
@@ -346,8 +371,10 @@ class Meta4(type):
meta_attribute: Literal["value on metaclass"] = "value on metaclass"
meta_non_data_descriptor: NonDataDescriptor = NonDataDescriptor()
class C4(metaclass=Meta4): ...
reveal_type(C4.meta_attribute) # revealed: Literal["value on metaclass"]
reveal_type(C4.meta_non_data_descriptor) # revealed: Literal["non-data"]
```
@@ -386,6 +413,7 @@ metaclass attribute (unless it's a data descriptor, which always takes precedenc
```py
from typing import Any
def _(flag: bool):
class Meta6(type):
attribute1: DataDescriptor = DataDescriptor()
@@ -448,6 +476,7 @@ when it is accessed on an instance. A real-world example of this is the `__get__
```py
from typing_extensions import Literal, LiteralString, overload
class Descriptor:
@overload
def __get__(self, instance: None, owner: type, /) -> Literal["called on class object"]: ...
@@ -459,9 +488,11 @@ class Descriptor:
else:
return "called on class object"
class C:
d: Descriptor = Descriptor()
reveal_type(C.d) # revealed: Literal["called on class object"]
reveal_type(C().d) # revealed: Literal["called on instance"]
@@ -479,13 +510,16 @@ class SomeCallable:
def __call__(self, x: int) -> str:
return "a"
class Descriptor:
def __get__(self, instance: object, owner: type | None = None) -> SomeCallable:
return SomeCallable()
class B:
__call__: Descriptor = Descriptor()
b_instance = B()
reveal_type(b_instance(1)) # revealed: str
@@ -512,6 +546,7 @@ class C:
def name(self, value: str | None) -> None:
self._value = value
c = C()
reveal_type(c._name) # revealed: str | None
@@ -550,6 +585,7 @@ class Base:
def other(self, v: float) -> None:
self.value = v
class Derived(Base):
@property
def other(self) -> float:
@@ -573,6 +609,7 @@ class DontAssignToMe:
@property
def immutable(self): ...
# error: [invalid-assignment]
DontAssignToMe().immutable = "the properties, they are a-changing"
```
@@ -595,6 +632,7 @@ class C:
def get_name(cls) -> str:
return cls.__name__
c1 = C.factory("test") # okay
reveal_type(c1) # revealed: C
@@ -612,6 +650,7 @@ class C:
def helper(value: str) -> str:
return value
reveal_type(C.helper("42")) # revealed: str
c = C()
reveal_type(c.helper("string")) # revealed: str
@@ -628,9 +667,11 @@ import types
from inspect import getattr_static
from ty_extensions import static_assert, is_subtype_of, TypeOf
def f(x: object) -> str:
return "a"
reveal_type(f) # revealed: def f(x: object) -> str
reveal_type(f.__get__) # revealed: <method-wrapper '__get__' of function 'f'>
static_assert(is_subtype_of(TypeOf[f.__get__], types.MethodWrapperType))
@@ -655,6 +696,7 @@ We can also bind the free function `f` to an instance of a class `C`:
```py
class C: ...
bound_method = wrapper_descriptor(f, C(), C)
reveal_type(bound_method) # revealed: bound method C.f() -> str
@@ -708,25 +750,31 @@ This test makes sure that we call `__get__` with the right argument types for va
```py
from __future__ import annotations
class TailoredForClassObjectAccess:
def __get__(self, instance: None, owner: type[C]) -> int:
return 1
class TailoredForInstanceAccess:
def __get__(self, instance: C, owner: type[C] | None = None) -> str:
return "a"
class TailoredForMetaclassAccess:
def __get__(self, instance: type[C], owner: type[Meta]) -> bytes:
return b"a"
class Meta(type):
metaclass_access: TailoredForMetaclassAccess = TailoredForMetaclassAccess()
class C(metaclass=Meta):
class_object_access: TailoredForClassObjectAccess = TailoredForClassObjectAccess()
instance_access: TailoredForInstanceAccess = TailoredForInstanceAccess()
reveal_type(C.class_object_access) # revealed: int
reveal_type(C().instance_access) # revealed: str
reveal_type(C.metaclass_access) # revealed: bytes
@@ -752,9 +800,11 @@ class Descriptor:
def __get__(self) -> int:
return 1
class C:
descriptor: Descriptor = Descriptor()
# TODO: This should be an error
reveal_type(C.descriptor) # revealed: int
@@ -772,9 +822,11 @@ call `__get__`" on the descriptor object (leading us to infer `Unknown`):
class BrokenDescriptor:
__get__: None = None
class Foo:
desc: BrokenDescriptor = BrokenDescriptor()
# TODO: this raises `TypeError` at runtime due to the implicit call to `__get__`;
# we should emit a diagnostic
reveal_type(Foo().desc) # revealed: Unknown
@@ -794,9 +846,11 @@ class Descriptor:
def __set__(self, instance: object, value: int) -> None:
pass
class C:
descriptor = Descriptor()
C.descriptor = "something else"
reveal_type(C.descriptor) # revealed: Literal["something else"]
```
@@ -811,10 +865,12 @@ class DataDescriptor:
def __set__(self, instance: int, value) -> None:
pass
class NonDataDescriptor:
def __get__(self, instance: object, owner: type | None = None) -> int:
return 1
def _(flag: bool):
class PossiblyUnbound:
if flag:
@@ -840,6 +896,7 @@ def _(flag: bool):
def _(flag: bool):
class MaybeDescriptor:
if flag:
def __get__(self, instance: object, owner: type | None = None) -> int:
return 1
@@ -859,36 +916,46 @@ descriptor protocol on the callable's `__call__` method:
```py
from __future__ import annotations
class ReturnedCallable2:
def __call__(self, descriptor: Descriptor1, instance: None, owner: type[C]) -> int:
return 1
class ReturnedCallable1:
def __call__(self, descriptor: Descriptor2, instance: Callable1, owner: type[Callable1]) -> ReturnedCallable2:
return ReturnedCallable2()
class Callable3:
def __call__(self, descriptor: Descriptor3, instance: Callable2, owner: type[Callable2]) -> ReturnedCallable1:
return ReturnedCallable1()
class Descriptor3:
__get__: Callable3 = Callable3()
class Callable2:
__call__: Descriptor3 = Descriptor3()
class Descriptor2:
__get__: Callable2 = Callable2()
class Callable1:
__call__: Descriptor2 = Descriptor2()
class Descriptor1:
__get__: Callable1 = Callable1()
class C:
d: Descriptor1 = Descriptor1()
reveal_type(C.d) # revealed: int
```

View File

@@ -13,6 +13,7 @@ These can be set on instances and on class objects.
class C:
attr: int = 0
instance = C()
instance.attr = 1 # fine
instance.attr = "wrong" # error: [invalid-assignment]
@@ -31,6 +32,7 @@ class C:
def __init__(self):
self.attr: int = 0
instance = C()
instance.attr = 1 # fine
instance.attr = "wrong" # error: [invalid-assignment]
@@ -46,9 +48,11 @@ diagnostic that mentions that the attribute is only available on class objects.
```py
from typing import ClassVar
class C:
attr: ClassVar[int] = 0
C.attr = 1 # fine
C.attr = "wrong" # error: [invalid-assignment]
@@ -63,6 +67,7 @@ When trying to set an attribute that is not defined, we also emit errors:
```py
class C: ...
C.non_existent = 1 # error: [unresolved-attribute]
instance = C()
@@ -97,9 +102,11 @@ class Descriptor:
def __set__(self, instance: object, value: int) -> None:
pass
class C:
attr: Descriptor = Descriptor()
instance = C()
instance.attr = 1 # fine
@@ -114,9 +121,11 @@ class WrongDescriptor:
def __set__(self, instance: object, value: int, extra: int) -> None:
pass
class C:
attr: WrongDescriptor = WrongDescriptor()
instance = C()
# TODO: ideally, we would mention why this is an invalid assignment (wrong number of arguments for `__set__`)
@@ -128,10 +137,12 @@ instance.attr = 1 # error: [invalid-assignment]
```py
def _(flag: bool) -> None:
if flag:
class C1:
attr: int = 0
else:
class C1:
attr: str = ""

View File

@@ -11,6 +11,7 @@ to the invalid argument.
def foo(x: int) -> int:
return x * x
foo("hello") # error: [invalid-argument-type]
```
@@ -22,6 +23,7 @@ This is like the basic test, except we put the call site above the function defi
def bar():
foo("hello") # error: [invalid-argument-type]
def foo(x: int) -> int:
return x * x
```
@@ -52,6 +54,7 @@ This checks that a diagnostic renders reasonably when there are multiple paramet
def foo(x: int, y: int, z: int) -> int:
return x * y * z
foo(1, "hello", 3) # error: [invalid-argument-type]
```
@@ -68,6 +71,7 @@ def foo(
) -> int:
return x * y * z
foo(1, "hello", 3) # error: [invalid-argument-type]
```
@@ -80,6 +84,7 @@ invalid argument types.
def foo(x: int, y: int, z: int) -> int:
return x * y * z
# error: [invalid-argument-type]
# error: [invalid-argument-type]
# error: [invalid-argument-type]
@@ -114,6 +119,7 @@ Tests a function definition with only positional parameters.
def foo(x: int, y: int, z: int, /) -> int:
return x * y * z
foo(1, "hello", 3) # error: [invalid-argument-type]
```
@@ -125,6 +131,7 @@ Tests a function definition with variadic arguments.
def foo(*numbers: int) -> int:
return len(numbers)
foo(1, 2, 3, "hello", 5) # error: [invalid-argument-type]
```
@@ -136,6 +143,7 @@ Tests a function definition with keyword-only arguments.
def foo(x: int, y: int, *, z: int = 0) -> int:
return x * y * z
foo(1, 2, z="hello") # error: [invalid-argument-type]
```
@@ -147,6 +155,7 @@ Tests a function definition with keyword-only arguments.
def foo(x: int, y: int, z: int = 0) -> int:
return x * y * z
foo(1, 2, "hello") # error: [invalid-argument-type]
```
@@ -156,6 +165,7 @@ foo(1, 2, "hello") # error: [invalid-argument-type]
def foo(**numbers: int) -> int:
return len(numbers)
foo(a=1, b=2, c=3, d="hello", e=5) # error: [invalid-argument-type]
```
@@ -167,6 +177,7 @@ Tests a function definition with multiple different kinds of arguments.
def foo(x: int, /, y: int, *, z: int = 0) -> int:
return x * y * z
foo(1, 2, z="hello") # error: [invalid-argument-type]
```
@@ -179,6 +190,7 @@ class C:
def __call__(self, x: int) -> int:
return 1
c = C()
c("wrong") # error: [invalid-argument-type]
```
@@ -192,6 +204,7 @@ class C:
def square(self, x: int) -> int:
return x * x
c = C()
c.square("hello") # error: [invalid-argument-type]
```
@@ -203,6 +216,7 @@ c.square("hello") # error: [invalid-argument-type]
```py
class Foo: ...
def needs_a_foo(x: Foo): ...
```
@@ -211,8 +225,10 @@ def needs_a_foo(x: Foo): ...
```py
from module import needs_a_foo
class Foo: ...
needs_a_foo(Foo()) # error: [invalid-argument-type]
```
@@ -230,6 +246,7 @@ python-version = "3.12"
```py
class Foo: ...
def needs_a_foo(x: Foo): ...
```
@@ -238,8 +255,10 @@ def needs_a_foo(x: Foo): ...
```py
from module import needs_a_foo
class Foo: ...
def f[T: Foo](x: T) -> T:
needs_a_foo(x) # error: [invalid-argument-type]
return x

View File

@@ -19,6 +19,7 @@ This diagnostic also points to the class definition if available.
class MissingAwait:
pass
async def main() -> None:
await MissingAwait() # error: [invalid-await]
```
@@ -30,11 +31,14 @@ This diagnostic also points to the method definition if available.
```py
from datetime import datetime
class PossiblyUnbound:
if datetime.today().weekday() == 0:
def __await__(self):
yield
async def main() -> None:
await PossiblyUnbound() # error: [invalid-await]
```
@@ -50,6 +54,7 @@ class InvalidAwaitArgs:
def __await__(self, value: int):
yield value
async def main() -> None:
await InvalidAwaitArgs() # error: [invalid-await]
```
@@ -63,6 +68,7 @@ awaitable.
class NonCallableAwait:
__await__ = 42
async def main() -> None:
await NonCallableAwait() # error: [invalid-await]
```
@@ -77,6 +83,7 @@ class InvalidAwaitReturn:
def __await__(self) -> int:
return 5
async def main() -> None:
await InvalidAwaitReturn() # error: [invalid-await]
```
@@ -90,16 +97,19 @@ instance to be awaitable. In this specific case, no specific function definition
import typing
from datetime import datetime
class UnawaitableUnion:
if datetime.today().weekday() == 6:
def __await__(self) -> typing.Generator[typing.Any, None, None]:
yield
else:
def __await__(self) -> int:
return 5
async def main() -> None:
await UnawaitableUnion() # error: [invalid-await]
```

View File

@@ -17,25 +17,32 @@ T3 = TypeVar("T3")
DefaultStrT = TypeVar("DefaultStrT", default=str)
class SubclassMe(Generic[T1, DefaultStrT]):
x: DefaultStrT
class Baz(SubclassMe[int, DefaultStrT]):
pass
# error: [invalid-generic-class] "Type parameter `T2` without a default cannot follow earlier parameter `T1` with a default"
class Foo(Generic[T1, T2]):
pass
class Bar(Generic[T2, T1, T3]): # error: [invalid-generic-class]
pass
class Spam(Generic[T1, T2, DefaultStrT, T3]): # error: [invalid-generic-class]
pass
class Ham(Protocol[T1, T2, DefaultStrT, T3]): # error: [invalid-generic-class]
pass
class VeryBad(
Protocol[T1, T2, DefaultStrT, T3], # error: [invalid-generic-class]
Generic[T1, T2, DefaultStrT, T3],

View File

@@ -102,9 +102,11 @@ T = TypeVar("T", covariant=True, contravariant=True)
```py
from typing_extensions import TypeVar
def cond() -> bool:
return True
# error: [invalid-legacy-type-variable]
T = TypeVar("T", covariant=cond())

View File

@@ -13,6 +13,7 @@ too verbose for it to be worth it.
def f(a, b=42): ...
def g(a, b): ...
class Foo:
def method(self, a): ...
```
@@ -24,9 +25,11 @@ from module import f, g, Foo
f() # error: [missing-argument]
def coinflip() -> bool:
return True
h = f if coinflip() else g
# error: [missing-argument]

View File

@@ -7,6 +7,7 @@
```py
from typing import overload
@overload
def f(x: int) -> int: ...
@overload
@@ -14,6 +15,7 @@ def f(x: str) -> str: ...
def f(x: int | str) -> int | str:
return x
f(b"foo") # error: [no-matching-overload]
```
@@ -27,8 +29,10 @@ Which in turn makes snapshotting a bit annoying, since the output can depend on
```py
from typing import overload
class Foo: ...
@overload
def foo(a: int, b: int, c: int): ...
@overload
@@ -73,6 +77,7 @@ def foo(a: str, b: float, c: float): ...
def foo(a: float, b: float, c: float): ...
def foo(a, b, c): ...
foo(Foo(), Foo()) # error: [no-matching-overload]
```
@@ -84,8 +89,10 @@ cut off the list in the diagnostic and emit a message stating the number of omit
```py
from typing import overload
class Foo: ...
@overload
def foo(a: int, b: int, c: int): ...
@overload
@@ -210,6 +217,7 @@ def foo(a: float, b: float, c: bool): ...
def foo(a: bool, b: float, c: float): ...
def foo(a, b, c): ...
foo(Foo(), Foo()) # error: [no-matching-overload]
```
@@ -218,6 +226,7 @@ foo(Foo(), Foo()) # error: [no-matching-overload]
```py
from typing import overload
@overload
def f(
lion: int,
@@ -276,6 +285,7 @@ def f(
) -> int | str:
return 0
f(b"foo") # error: [no-matching-overload]
```
@@ -284,6 +294,7 @@ f(b"foo") # error: [no-matching-overload]
```py
from typing import overload
class Foo:
@overload
def bar(self, x: int) -> int: ...
@@ -292,6 +303,7 @@ class Foo:
def bar(self, x: int | str) -> int | str:
return x
foo = Foo()
foo.bar(b"wat") # error: [no-matching-overload]
```

View File

@@ -11,10 +11,12 @@ class A:
class B:
pass
class C:
class B:
pass
a: A.B = C.B() # error: [invalid-assignment] "Object of type `test.C.B` is not assignable to `test.A.B`"
```
@@ -26,6 +28,7 @@ a: A.B = C.B() # error: [invalid-assignment] "Object of type `test.C.B` is not
class B:
pass
def f(b: B):
class B:
pass
@@ -42,6 +45,7 @@ import b
df: a.DataFrame = b.DataFrame() # error: [invalid-assignment] "Object of type `b.DataFrame` is not assignable to `a.DataFrame`"
def _(dfs: list[b.DataFrame]):
# error: [invalid-assignment] "Object of type `list[b.DataFrame]` is not assignable to `list[a.DataFrame]`"
dataframes: list[a.DataFrame] = dfs
@@ -68,6 +72,7 @@ class DataFrame:
```py
from .foo import MyClass
def make_MyClass() -> MyClass:
return MyClass()
```
@@ -83,10 +88,11 @@ class MyClass: ...
```py
class MyClass: ...
def get_MyClass() -> MyClass:
from . import make_MyClass
# error: [invalid-return-type] "Return type does not match returned value: expected `package.foo.MyClass @ src/package/foo.py:1`, found `package.foo.MyClass @ src/package/foo.pyi:1`"
# error: [invalid-return-type] "Return type does not match returned value: expected `package.foo.MyClass @ src/package/foo.py:1:7`, found `package.foo.MyClass @ src/package/foo.pyi:1:7`"
return make_MyClass()
```
@@ -105,6 +111,7 @@ s: status_a.Status = status_b.Status.ACTIVE
```py
from enum import Enum
class Status(Enum):
ACTIVE = 1
INACTIVE = 2
@@ -115,6 +122,7 @@ class Status(Enum):
```py
from enum import Enum
class Status(Enum):
ACTIVE = "active"
INACTIVE = "inactive"
@@ -127,16 +135,19 @@ class Status(Enum):
```py
from enum import Enum
class A:
class B(Enum):
ACTIVE = "active"
INACTIVE = "inactive"
class C:
class B(Enum):
ACTIVE = "active"
INACTIVE = "inactive"
# error: [invalid-assignment] "Object of type `Literal[test.C.B.ACTIVE]` is not assignable to `test.A.B`"
a: A.B = C.B.ACTIVE
```
@@ -182,6 +193,7 @@ from typing import Generic, TypeVar
T = TypeVar("T")
class Container(Generic[T]):
pass
```
@@ -193,6 +205,7 @@ from typing import Generic, TypeVar
T = TypeVar("T")
class Container(Generic[T]):
pass
```
@@ -208,9 +221,11 @@ from typing import Protocol, TypeVar
T_co = TypeVar("T_co", covariant=True)
class Iterator(Protocol[T_co]):
def __nexxt__(self) -> T_co: ...
def bad() -> Iterator[str]:
raise NotImplementedError
```
@@ -220,6 +235,7 @@ def bad() -> Iterator[str]:
```py
from typing import Iterator
def f() -> Iterator[str]:
import bad
@@ -234,6 +250,7 @@ from typing import Protocol
import proto_a
import proto_b
def _(drawable_b: proto_b.Drawable):
# error: [invalid-assignment] "Object of type `proto_b.Drawable` is not assignable to `proto_a.Drawable`"
drawable: proto_a.Drawable = drawable_b
@@ -244,6 +261,7 @@ def _(drawable_b: proto_b.Drawable):
```py
from typing import Protocol
class Drawable(Protocol):
def draw(self) -> None: ...
```
@@ -253,6 +271,7 @@ class Drawable(Protocol):
```py
from typing import Protocol
class Drawable(Protocol):
def draw(self) -> int: ...
```
@@ -264,6 +283,7 @@ from typing import TypedDict
import dict_a
import dict_b
def _(b_person: dict_b.Person):
# error: [invalid-assignment] "Object of type `dict_b.Person` is not assignable to `dict_a.Person`"
person_var: dict_a.Person = b_person
@@ -274,6 +294,7 @@ def _(b_person: dict_b.Person):
```py
from typing import TypedDict
class Person(TypedDict):
name: str
```
@@ -283,6 +304,7 @@ class Person(TypedDict):
```py
from typing import TypedDict
class Person(TypedDict):
name: bytes
```
@@ -298,6 +320,7 @@ class Model: ...
```py
class Model: ...
def get_models_tuple() -> tuple[Model]:
from module import Model

View File

@@ -18,6 +18,7 @@ python-version = "3.10"
async def elements(n):
yield n
async def f():
# error: 19 [invalid-syntax] "cannot use an asynchronous comprehension inside of a synchronous comprehension on Python 3.10 (syntax was added in 3.11)"
return {n: [x async for x in elements(n)] for n in range(3)}
@@ -38,9 +39,11 @@ correctly in the `SemanticSyntaxContext` trait:
async def f():
[x for x in [1]] and [x async for x in elements(1)]
async def f():
def g():
pass
[x async for x in elements(1)]
```
@@ -57,6 +60,7 @@ python-version = "3.11"
async def elements(n):
yield n
async def f():
return {n: [x async for x in elements(n)] for n in range(3)}
```
@@ -82,6 +86,7 @@ python-version = "3.12"
```py
from __future__ import annotations
# error: [invalid-type-form] "Named expressions are not allowed in type expressions"
# error: [invalid-syntax] "named expression cannot be used within a type annotation"
def f() -> (y := 3): ...
@@ -114,6 +119,7 @@ python-version = "3.10"
class Point:
pass
obj = Point()
match obj:
# error: [invalid-syntax] "attribute name `x` repeated in class pattern"
@@ -127,6 +133,7 @@ match obj:
class C:
def __await__(self): ...
# error: [invalid-syntax] "`return` statement outside of a function"
return
@@ -140,10 +147,12 @@ yield from []
# error: [invalid-syntax] "`await` outside of an asynchronous function"
await C()
def f():
# error: [invalid-syntax] "`await` outside of an asynchronous function"
await C()
(await cor async for cor in f()) # ok
(await cor for cor in f()) # ok
([await c for c in cor] async for cor in f()) # ok
@@ -155,6 +164,7 @@ Generators are evaluated lazily, so `await` is allowed, even outside of a functi
async def g():
yield 1
(x async for x in g())
```
@@ -209,6 +219,7 @@ python-version = "3.12"
class C[T, T]:
pass
# error: [invalid-syntax] "duplicate type parameter"
def f[X, Y, X]():
pass
@@ -223,10 +234,12 @@ def func():
# error: [invalid-syntax] "Starred expression cannot be used here"
return *[1, 2, 3]
def gen():
# error: [invalid-syntax] "Starred expression cannot be used here"
yield * [1, 2, 3]
# error: [invalid-syntax] "Starred expression cannot be used here"
for *x in range(10):
pass
@@ -287,10 +300,12 @@ python-version = "3.12"
# error: [invalid-syntax] "cannot assign to `__debug__`"
__debug__ = False
# error: [invalid-syntax] "cannot assign to `__debug__`"
def process(__debug__):
pass
# error: [invalid-syntax] "cannot assign to `__debug__`"
class Generic[__debug__]:
pass
@@ -311,16 +326,19 @@ def _():
# error: [invalid-syntax] "yield expression cannot be used within a TypeVar bound"
type X[T: (yield 1)] = int
def _():
# error: [invalid-type-form] "`yield` expressions are not allowed in type expressions"
# error: [invalid-syntax] "yield expression cannot be used within a type alias"
type Y = (yield 1)
# error: [invalid-type-form] "Named expressions are not allowed in type expressions"
# error: [invalid-syntax] "named expression cannot be used within a generic definition"
def f[T](x: int) -> (y := 3):
return x
def _():
# error: [invalid-syntax] "yield expression cannot be used within a generic definition"
class C[T]((yield from [object])):
@@ -335,6 +353,7 @@ This error includes `await`, `async for`, `async with`, and `async` comprehensio
async def elements(n):
yield n
def _():
# error: [invalid-syntax] "`await` outside of an asynchronous function"
await elements(1)
@@ -354,6 +373,7 @@ def _():
```py
x: int
def f():
x = 1
global x # error: [invalid-syntax] "name `x` is used prior to global declaration"
@@ -388,32 +408,39 @@ for x in range(42):
```py
a = None
def f(a):
global a # error: [invalid-syntax]
def g(a):
if True:
global a # error: [invalid-syntax]
def h(a):
def inner():
global a
def i(a):
try:
global a # error: [invalid-syntax]
except Exception:
pass
def f(a):
a = 1
global a # error: [invalid-syntax]
def f(a):
a = 1
a = 2
global a # error: [invalid-syntax]
def f(a):
class Inner:
global a # ok

View File

@@ -7,6 +7,7 @@
```py
class C: ...
C = 1 # error: [invalid-assignment]
```
@@ -15,5 +16,6 @@ C = 1 # error: [invalid-assignment]
```py
def f(): ...
f = 1 # error: [invalid-assignment]
```

View File

@@ -7,6 +7,7 @@ from typing_extensions import Any, Final, LiteralString, Self
X = Any
class Foo:
X: Final = LiteralString
a: int
@@ -16,6 +17,7 @@ class Foo:
def __init__(self):
self.y: Final = LiteralString
X.foo # error: [unresolved-attribute]
X.aaaaooooooo # error: [unresolved-attribute]
Foo.X.startswith # error: [unresolved-attribute]

View File

@@ -13,6 +13,7 @@ too verbose for it to be worth it.
def f(a, b=42): ...
def g(a, b): ...
class Foo:
def method(self, a): ...
```
@@ -24,9 +25,11 @@ from module import f, g, Foo
f(1, 2, 3) # error: [too-many-positional-arguments]
def coinflip() -> bool:
return True
h = f if coinflip() else g
# error: [too-many-positional-arguments]

View File

@@ -13,9 +13,11 @@ python-version = "3.12"
def f1() -> int:
return 0
def f2(name: str) -> int:
return 0
def _(flag: bool):
if flag:
f = f1
@@ -37,9 +39,11 @@ the end user.)
def f1(a: int) -> int:
return 0
def f2(name: str) -> int:
return 0
def _(flag: bool):
if flag:
f = f1
@@ -61,18 +65,23 @@ just ensuring that we get test coverage for each of the possible diagnostic mess
from inspect import getattr_static
from typing import overload
def f1() -> int:
return 0
def f2(name: str) -> int:
return 0
def f3(a: int, b: int) -> int:
return 0
def f4[T: str](x: T) -> int:
return 0
@overload
def f5() -> None: ...
@overload
@@ -80,6 +89,7 @@ def f5(x: str) -> str: ...
def f5(x: str | None = None) -> str | None:
return x
@overload
def f6() -> None: ...
@overload
@@ -87,9 +97,11 @@ def f6(x: str, y: str) -> str: ...
def f6(x: str | None = None, y: str | None = None) -> str | None:
return x + y if x and y else None
def _(n: int):
class PossiblyNotCallable:
if n == 0:
def __call__(self) -> int:
return 0
@@ -126,9 +138,11 @@ def _(n: int):
def any(*args, **kwargs) -> int:
return 0
def f1(name: str) -> int:
return 0
def _(n: int):
if n == 0:
f = f1
@@ -147,16 +161,29 @@ therefore truncate the long expected union type to avoid overwhelming output.
```py
from typing import Literal, Union
class A: ...
class B: ...
class C: ...
class D: ...
class E: ...
class F: ...
def f1(x: Union[Literal[1, 2, 3, 4, 5, 6, 7, 8], A, B, C, D, E, F]) -> int:
return 0
def _(n: int):
x = n
# error: [invalid-argument-type]

View File

@@ -13,6 +13,7 @@ sub-diagnostic for each element would probably be too verbose for it to be worth
def f(a, b, c=42): ...
def g(a, b): ...
class Foo:
def method(self, a, b): ...
```
@@ -24,9 +25,11 @@ from module import f, g, Foo
f(a=1, b=2, c=3, d=42) # error: [unknown-argument]
def coinflip() -> bool:
return True
h = f if coinflip() else g
# error: [unknown-argument]

View File

@@ -9,6 +9,7 @@ class NotBoolable:
def __bool__(self, foo):
return False
a = NotBoolable()
# error: [unsupported-bool-conversion]
@@ -22,6 +23,7 @@ class NotBoolable:
def __bool__(self) -> str:
return "wat"
a = NotBoolable()
# error: [unsupported-bool-conversion]
@@ -34,6 +36,7 @@ a = NotBoolable()
class NotBoolable:
__bool__: int = 3
a = NotBoolable()
# error: [unsupported-bool-conversion]
@@ -47,15 +50,19 @@ class NotBoolable1:
def __bool__(self) -> str:
return "wat"
class NotBoolable2:
pass
class NotBoolable3:
__bool__: int = 3
def get() -> NotBoolable1 | NotBoolable2 | NotBoolable3:
return NotBoolable2()
# error: [unsupported-bool-conversion]
10 and get() and True
```

View File

@@ -10,6 +10,7 @@
from typing_extensions import assert_never, Never, Any
from ty_extensions import Unknown
def _(never: Never):
assert_never(never) # fine
```
@@ -24,24 +25,31 @@ If it is not, a `type-assertion-failure` diagnostic is emitted.
from typing_extensions import assert_never, Never, Any
from ty_extensions import Unknown
def _():
assert_never(0) # error: [type-assertion-failure]
def _():
assert_never("") # error: [type-assertion-failure]
def _():
assert_never(None) # error: [type-assertion-failure]
def _():
assert_never(()) # error: [type-assertion-failure]
def _(flag: bool, never: Never):
assert_never(1 if flag else never) # error: [type-assertion-failure]
def _(any_: Any):
assert_never(any_) # error: [type-assertion-failure]
def _(unknown: Unknown):
assert_never(unknown) # error: [type-assertion-failure]
```
@@ -59,10 +67,16 @@ are handled in a series of `isinstance` checks or other narrowing patterns that
```py
from typing_extensions import assert_never, Literal
class A: ...
class B: ...
class C: ...
def if_else_isinstance_success(obj: A | B):
if isinstance(obj, A):
pass
@@ -73,6 +87,7 @@ def if_else_isinstance_success(obj: A | B):
else:
assert_never(obj)
def if_else_isinstance_error(obj: A | B):
if isinstance(obj, A):
pass
@@ -83,6 +98,7 @@ def if_else_isinstance_error(obj: A | B):
# error: [type-assertion-failure] "Type `B & ~A & ~C` is not equivalent to `Never`"
assert_never(obj)
def if_else_singletons_success(obj: Literal[1, "a"] | None):
if obj == 1:
pass
@@ -93,6 +109,7 @@ def if_else_singletons_success(obj: Literal[1, "a"] | None):
else:
assert_never(obj)
def if_else_singletons_error(obj: Literal[1, "a"] | None):
if obj == 1:
pass
@@ -104,6 +121,7 @@ def if_else_singletons_error(obj: Literal[1, "a"] | None):
# error: [type-assertion-failure] "Type `Literal["a"]` is not equivalent to `Never`"
assert_never(obj)
def match_singletons_success(obj: Literal[1, "a"] | None):
match obj:
case 1:
@@ -115,6 +133,7 @@ def match_singletons_success(obj: Literal[1, "a"] | None):
case _ as obj:
assert_never(obj)
def match_singletons_error(obj: Literal[1, "a"] | None):
match obj:
case 1:

Some files were not shown because too many files have changed in this diff Show More