Compare commits

...

37 Commits

Author SHA1 Message Date
David Peter
b6954063c1 [ty] Do not function-like property for Callable type relations 2025-10-13 13:15:36 +02:00
Alex Waygood
d83d7a0dcd [ty] Fix false-positive diagnostics on super() calls (#20814) 2025-10-13 10:57:46 +00:00
David Peter
565dbf3c9d [ty] Move class_member to member module (#20837)
## Summary

Move the `class_member` function to the `member` module. This allows us
to move the `member` module into the `types` module and to reduce the
visibility of its contents to `pub(super)`. The drawback is that we need
to make `place::place_by_id` public.

## Test Plan

Pure refactoring.
2025-10-13 10:58:37 +02:00
Takayuki Maeda
f715d70be1 [ruff] Use DiagnosticTag for more flake8 and numpy rules (#20758) 2025-10-13 10:29:15 +02:00
David Peter
9b9c9ae092 [ty] Prefer declared base class attribute over inferred attribute on subclass (#20764)
## Summary

When accessing an (instance) attribute on a given class, we were
previously traversing its MRO, and building a union of types (if the
attribute was available on multiple classes in the MRO) until we found a
*definitely bound* symbol. The idea was that possibly unbound symbols in
a subclass might only partially shadow the underlying base class
attribute.

This behavior was problematic for two reasons:
* if the attribute was definitely bound on a class (e.g. `self.x =
None`), we would have stopped iterating, even if there might be a `x:
str | None` declaration in a base class (the bug reported in
https://github.com/astral-sh/ty/issues/1067).
* if the attribute originated from an implicit instance attribute
assignment (e.g. `self.x = 1` in method `Sub.foo`), we might stop
looking and miss another implicit instance attribute assignment in a
base class method (e.g. `self.x = 2` in method `Base.bar`).

With this fix, we still iterate the MRO of the class, but we only stop
iterating if we find a *definitely declared* symbol. In this case, we
only return the declared attribute type. Otherwise, we keep building a
union of inferred attribute types.

The implementation here seemed to be the easiest fix for
https://github.com/astral-sh/ty/issues/1067 that also kept the ecosystem
impact low (the changes that I see all look correct). However, as the
Markdown tests show, there are other things to fix in this area. For
example, we should do a similar thing for *class attributes*. This is
more involved, though (affects many different areas and probably
involves a change to our descriptor protocol implementation), so I'd
like to postpone this to a follow-up.

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

## Test Plan

Updated Markdown tests, including a regression test for
https://github.com/astral-sh/ty/issues/1067.
2025-10-13 09:28:57 +02:00
Micha Reiser
c80ee1a50b [ty] Log files that are slow to type check (#20836) 2025-10-13 09:15:54 +02:00
renovate[bot]
350042b801 Update cargo-bins/cargo-binstall action to v1.15.7 (#20827)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-13 08:28:55 +02:00
renovate[bot]
e02cdd350e Update CodSpeedHQ/action action to v4.1.1 (#20828)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-13 08:28:37 +02:00
renovate[bot]
e3b910c41a Update Rust crate pyproject-toml to v0.13.7 (#20835)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-13 08:28:06 +02:00
renovate[bot]
0e8c02aea6 Update Rust crate anstream to v0.6.21 (#20829) 2025-10-12 21:43:39 -04:00
renovate[bot]
74b2c4c2e4 Update Rust crate libc to v0.2.177 (#20832) 2025-10-12 21:43:10 -04:00
renovate[bot]
89b67a2448 Update Rust crate memchr to v2.7.6 (#20834) 2025-10-12 21:42:52 -04:00
renovate[bot]
6be344af65 Update Rust crate libcst to v1.8.5 (#20833) 2025-10-12 21:42:38 -04:00
renovate[bot]
89f9dd6b43 Update Rust crate camino to v1.2.1 (#20831) 2025-10-12 21:42:19 -04:00
renovate[bot]
1935896e6b Update Rust crate anstyle to v1.0.13 (#20830) 2025-10-12 21:42:06 -04:00
Alex Waygood
7064c38e53 [ty] Filter out revealed-type and undefined-reveal diagnostics from mdtest snapshots (#20820) 2025-10-12 18:39:32 +00:00
Shunsuke Shibayama
dc64c08633 [ty] bidirectional type inference using function return type annotations (#20528)
## Summary

Implements bidirectional type inference using function return type
annotations.

This PR was originally proposed to solve astral-sh/ty#1167, but this
does not fully resolve it on its own.
Additionally, I believe we need to allow dataclasses to generate their
own `__new__` methods, [use constructor return types ​​for
inference](5844c0103d/crates/ty_python_semantic/src/types.rs (L5326-L5328)),
and a mechanism to discard type narrowing like `& ~AlwaysFalsy` if
necessary (at a more general level than this PR).

## Test Plan

`mdtest/bidirectional.md` is added.

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Co-authored-by: Ibraheem Ahmed <ibraheem@ibraheem.ca>
2025-10-11 00:38:35 +00:00
Shunsuke Shibayama
11a9e7ee44 [ty] use type context more aggressively to infer values ​​when constructing a TypedDict (#20806)
## Summary

Based on @ibraheemdev's comment on #20792:

> I think we can also update our bidirectional inference code, [which
makes the same
assumption](https://github.com/astral-sh/ruff/blob/main/crates/ty_python_semantic/src/types/infer/builder.rs?rgh-link-date=2025-10-09T21%3A30%3A31Z#L5860).

This PR also adds more test cases for how `TypedDict` annotations affect
generic call inference.

## Test Plan

New tests in `typed_dict.md`
2025-10-10 16:51:16 -07:00
ageorgou
bbd3856de8 [flake8-datetimez] Clarify docs for several rules (#20778)
## Summary

Resolves #19384.

- Distinguishes more clearly between `date` and `datetime` objects.
- Uniformly links to the relevant Python docs from rules in this
category.

I've tried to be clearer, but there's still a contradiction in the rules
as written: we say "use timezone-aware objects", but `date`s are
inherently timezone-naive.

Also, the full docs don't always match the error message: for instance,
in [DTZ012](https://docs.astral.sh/ruff/rules/call-date-fromtimestamp/),
the example says to use:
```python
datetime.datetime.fromtimestamp(946684800, tz=datetime.UTC)
```
while `fix_title` returns "Use `datetime.datetime.fromtimestamp(ts,
tz=...)**.date()**` instead".
I have left this as it was for now.

## Test Plan
Ran `mkdocs` locally and inspected result.
2025-10-10 13:02:24 +00:00
David Peter
ae83a1fd2d [ty] Additional tests for dataclass_transform (class-level overwrites, field_specifiers) (#20788)
## Summary

Adds a set of basic new tests corresponding to open points in
https://github.com/astral-sh/ty/issues/1327, to document the state of
support for `dataclass_transform`.
2025-10-10 11:22:06 +00:00
Alex Waygood
44807c4a05 [ty] Better implementation of assignability for intersections with negated gradual elements (#20773) 2025-10-10 11:10:17 +00:00
David Peter
69f9182033 [ty] Annotations are deferred by default for 3.14+ (#20799)
## Summary

Type annotations are deferred by default starting with Python 3.14. No
`from __future__ import annotations` import is necessary.

## Test Plan

New Markdown test
2025-10-10 12:05:03 +02:00
David Peter
949a4f1c42 [ty] Simplify and fix CallableTypeOf[..] implementation (#20797)
## Summary

Simplify and fix the implementation of
`ty_extensions.CallableTypeOf[..]`.

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

## Test Plan

Added regression test.
2025-10-10 12:04:37 +02:00
David Peter
a82833a998 [ty] Update mypy_primer and project lists (#20798)
## Summary

Pulls in two updates to `mypy_primer` projects:

* https://github.com/hauntsaninja/mypy_primer/pull/201 (add
`django-test-migrations`)
* https://github.com/hauntsaninja/mypy_primer/pull/122 (remove
`SinbadCogs`)

## Test Plan

CI on this PR
2025-10-10 11:08:39 +02:00
Micha Reiser
4bd454f9b5 Shard ty walltime benchmarks (#20791) 2025-10-10 07:55:50 +02:00
pieterh-oai
66885e4bce [flake8-logging-format] Avoid dropping implicitly concatenated pieces in the G004 fix (#20793)
## Summary

The original autofix for G004 was quietly dropping everything but the
f-string components of any implicit concatenation sequence; this
addresses that.

Side note: It looks like `f_strings` is a bit risky to use (since it
implicitly skips non-f-string parts); use iter and include implicitly
concatenated pieces. We should consider if it's worth having
(convenience vs. bit risky).

## Test Plan

```
cargo test -p ruff_linter
```

Backtest (run new testcases against previous implementation):
```
git checkout HEAD^ crates/ruff_linter/src/rules/flake8_logging_format/rules/logging_call.rs
cargot test -p ruff_linter

```
2025-10-09 18:14:38 -04:00
Carl Meyer
8248193ed9 [ty] defer inference of legacy TypeVar bound/constraints/defaults (#20598)
## Summary

This allows us to handle self-referential bounds/constraints/defaults
without panicking.

Handles more cases from https://github.com/astral-sh/ty/issues/256

This also changes the way we infer the types of legacy TypeVars. Rather
than understanding a constructor call to `typing[_extension].TypeVar`
inside of any (arbitrarily nested) expression, and having to use a
special `assigned_to` field of the semantic index to try to best-effort
figure out what name the typevar was assigned to, we instead understand
the creation of a legacy `TypeVar` only in the supported syntactic
position (RHS of a simple un-annotated assignment with one target). In
any other position, we just infer it as creating an opaque instance of
`typing.TypeVar`. (This behavior matches all other type checkers.)

So we now special-case TypeVar creation in `TypeInferenceBuilder`, as a
special case of an assignment definition, rather than deeper inside call
binding. This does mean we re-implement slightly more of
argument-parsing, but in practice this is minimal and easy to handle
correctly.

This is easier to implement if we also make the RHS of a simple (no
unpacking) one-target assignment statement no longer a standalone
expression. Which is fine to do, because simple one-target assignments
don't need to infer the RHS more than once. This is a bonus performance
(0-3% across various projects) and significant memory-usage win, since
most assignment statements are simple one-target assignment statements,
meaning we now create many fewer standalone-expression salsa
ingredients.

This change does mean that inference of manually-constructed
`TypeAliasType` instances can no longer find its Definition in
`assigned_to`, which regresses go-to-definition for these aliases. In a
future PR, `TypeAliasType` will receive the same treatment that
`TypeVar` did in this PR (moving its special-case inference into
`TypeInferenceBuilder` and supporting it only in the correct syntactic
position, and lazily inferring its value type to support recursion),
which will also fix the go-to-definition regression. (I decided a
temporary edge-case regression is better in this case than doubling the
size of this PR.)

This PR also tightens up and fixes various aspects of the validation of
`TypeVar` creation, as seen in the tests.

We still (for now) treat all typevars as instances of `typing.TypeVar`,
even if they were created using `typing_extensions.TypeVar`. This means
we'll wrongly error on e.g. `T.__default__` on Python 3.11, even if `T`
is a `typing_extensions.TypeVar` instance at runtime. We share this
wrong behavior with both mypy and pyrefly. It will be easier to fix
after we pull in https://github.com/python/typeshed/pull/14840.

There are some issues that showed up here with typevar identity and
`MarkTypeVarsInferable`; the fix here (using the new `original` field
and `is_identical_to` methods on `BoundTypeVarInstance` and
`TypeVarInstance`) is a bit kludgy, but it can go away when we eliminate
`MarkTypeVarsInferable`.

## Test Plan

Added and updated mdtests.

### Conformance suite impact

The impact here is all positive:

* We now correctly error on a legacy TypeVar with exactly one constraint
type given.
* We now correctly error on a legacy TypeVar with both an upper bound
and constraints specified.

### Ecosystem impact

Basically none; in the setuptools case we just issue slightly different
errors on an invalid TypeVar definition, due to the modified validation
code.

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-10-09 21:08:37 +00:00
Ibraheem Ahmed
b086ffe921 [ty] Type-context aware literal promotion (#20776)
## Summary

Avoid literal promotion when a literal type annotation is provided, e.g.,
```py
x: list[Literal[1]] = [1]
```

Resolves https://github.com/astral-sh/ty/issues/1198. This does not fix
issue https://github.com/astral-sh/ty/issues/1284, but it does make it
more relevant because after this change, it is possible to directly
instantiate a generic type with a literal specialization.
2025-10-09 16:53:53 -04:00
Dan Parizher
537ec5f012 [fastapi] Fix false positives for path parameters that FastAPI doesn't recognize (FAST003) (#20687)
## Summary

Fixes #20680

---------

Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2025-10-09 16:10:21 -04:00
Shunsuke Shibayama
db91ac7dce [ty] allow any string Literal type expression as a key when constructing a TypedDict (#20792) 2025-10-09 18:24:11 +00:00
David Peter
75f3c0e8e6 [ty] Respect dataclass_transform parameters for metaclass-based models (#20780)
## Summary

Respect parameters such as `frozen_default` for metaclass-based
`@dataclass_transformer` models.

Related to: https://github.com/astral-sh/ty/issues/1260

## Typing conformance changes

Those are all correct (new true positives)

## Test Plan

New Markdown tests
2025-10-09 13:24:20 +00:00
wangxiaolei
f0d0b57900 [ty] dataclass_transform: Support frozen_default and kw_only_default (#20761)
## Summary

- Add support for eq, kw_only, and frozen parameter overrides in
@dataclass_transform
- Previously only order parameter override was supported
- Update test documentation to reflect fixed behavior
- Resolves issue where kw_only_default and frozen_default could not be
overridden

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

## Test Plan

New Markdown tests

---------

Co-authored-by: David Peter <mail@david-peter.de>
2025-10-09 09:34:49 +02:00
Alex Waygood
b0c6217e0b [ty] Fix broken property tests for disjointness of intersections (#20775)
## Summary

Two stable property tests are currently failing on `main`, following
f054b8a55e
(of course, I only thought to run the property tests again around 30
minutes _after_ landing that PR...). The issue is quite subtle, and took
me an annoying amount of time to pin down: we're matching over `(self,
other)` in `Type::is_disjoint_from_impl`, but `other` here is shadowed
by the binding in the `match` branch, which means that the wrong key is
inserted into the cache of the `IsDisjointFrom` cycle detector:


f054b8a55e/crates/ty_python_semantic/src/types.rs (L2408-L2435)

This PR fixes that issue, and also adds a few `Debug` implementations to
our cycle detectors, so that issues like this are easier to debug in the
future.

I'm adding the `internal` label, as this fixes a bug that hasn't yet
appeared in any released version of ty, so it doesn't deserve its own
changelog entry.

## Test Plan

`QUICKCHECK_TESTS=1000000 cargo test --release -p ty_python_semantic --
--ignored types::property_tests::stable` now once again passes on `main`

I considered adding new mdtests as well, but the examples that the
property tests were throwing at me all seemed _quite_ obscure and
somewhat unlikely to occur in the real world. I don't think it's worth
it.
2025-10-08 22:28:56 +01:00
Alex Waygood
f054b8a55e [ty] Improve assignability/subtyping between two protocol types (#20368) 2025-10-08 18:37:30 +00:00
Alex Waygood
b9c84add07 [ty] Disambiguate classes that live in different modules but have the same fully qualified names (#20756)
## Summary

Even disambiguating classes using their fully qualified names is not
enough for some diagnostics. We've seen real-world examples in the
ecosystem (and https://github.com/astral-sh/ruff/pull/20368 introduces
some more!) where two types can be different, but can still have the
same fully qualified name. In these cases, our disambiguation machinery
needs to print the file path and line number of the class in order to
disambiguate classes with similar names in our diagnostics.

Helps with https://github.com/astral-sh/ty/issues/1306

## Test Plan

Mdtests
2025-10-08 18:27:40 +01:00
David Peter
150ea92d03 [ty] Add tests for instance attributes in class hierarchies (#20767)
## Summary

This adds a couple of new test cases related to
https://github.com/astral-sh/ty/issues/1067 and beyond that. For now,
they are just documenting the current (problematic) behavior. Since the
topic has some subtleties, I'd like to merge this prior to the actual
bugfix(es) in order to evaluate the changes in an easier way.
2025-10-08 17:46:47 +02:00
David Peter
697998f836 [ty] Do not re-export ide_support attributes from types (#20769)
## Summary

The `types` module currently re-exports a lot of functions and data
types from `types::ide_support`. One of these is called `Member`, a name
that is overloaded several times already. And I'd like to add one more
`Member` struct soon. Making the whole `ide_support` module public seems
cleaner to me, anyway.

## Test Plan

Pure refactoring.
2025-10-08 17:45:28 +02:00
132 changed files with 6670 additions and 3558 deletions

View File

@@ -452,7 +452,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: "Install cargo-binstall"
uses: cargo-bins/cargo-binstall@38e8f5e4c386b611d51e8aa997b9a06a3c8eb67a # v1.15.6
uses: cargo-bins/cargo-binstall@a66119fbb1c952daba62640c2609111fe0803621 # v1.15.7
- name: "Install cargo-fuzz"
# Download the latest version from quick install and not the github releases because github releases only has MUSL targets.
run: cargo binstall cargo-fuzz --force --disable-strategies crate-meta-data --no-confirm
@@ -703,7 +703,7 @@ jobs:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: cargo-bins/cargo-binstall@38e8f5e4c386b611d51e8aa997b9a06a3c8eb67a # v1.15.6
- uses: cargo-bins/cargo-binstall@a66119fbb1c952daba62640c2609111fe0803621 # v1.15.7
- run: cargo binstall --no-confirm cargo-shear
- run: cargo shear
@@ -953,7 +953,7 @@ jobs:
run: cargo codspeed build --features "codspeed,instrumented" --no-default-features -p ruff_benchmark --bench formatter --bench lexer --bench linter --bench parser
- name: "Run benchmarks"
uses: CodSpeedHQ/action@3959e9e296ef25296e93e32afcc97196f966e57f # v4.1.0
uses: CodSpeedHQ/action@6b43a0cd438f6ca5ad26f9ed03ed159ed2df7da9 # v4.1.1
with:
mode: instrumentation
run: cargo codspeed run
@@ -988,19 +988,23 @@ jobs:
run: cargo codspeed build --features "codspeed,instrumented" --no-default-features -p ruff_benchmark --bench ty
- name: "Run benchmarks"
uses: CodSpeedHQ/action@3959e9e296ef25296e93e32afcc97196f966e57f # v4.1.0
uses: CodSpeedHQ/action@6b43a0cd438f6ca5ad26f9ed03ed159ed2df7da9 # v4.1.1
with:
mode: instrumentation
run: cargo codspeed run
token: ${{ secrets.CODSPEED_TOKEN }}
benchmarks-walltime:
name: "benchmarks walltime (${{ matrix.benchmarks }})"
runs-on: codspeed-macro
needs: determine_changes
if: ${{ github.repository == 'astral-sh/ruff' && !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.ty == 'true' || github.ref == 'refs/heads/main') }}
timeout-minutes: 20
env:
TY_LOG: ruff_benchmark=debug
strategy:
matrix:
benchmarks:
- "medium|multithreaded"
- "small|large"
steps:
- name: "Checkout Branch"
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
@@ -1022,7 +1026,7 @@ jobs:
run: cargo codspeed build --features "codspeed,walltime" --no-default-features -p ruff_benchmark
- name: "Run benchmarks"
uses: CodSpeedHQ/action@3959e9e296ef25296e93e32afcc97196f966e57f # v4.1.0
uses: CodSpeedHQ/action@6b43a0cd438f6ca5ad26f9ed03ed159ed2df7da9 # v4.1.1
env:
# enabling walltime flamegraphs adds ~6 minutes to the CI time, and they don't
# appear to provide much useful insight for our walltime benchmarks right now
@@ -1030,5 +1034,5 @@ jobs:
CODSPEED_PERF_ENABLED: false
with:
mode: walltime
run: cargo codspeed run
run: cargo codspeed run --bench ty_walltime "${{ matrix.benchmarks }}"
token: ${{ secrets.CODSPEED_TOKEN }}

147
Cargo.lock generated
View File

@@ -50,9 +50,9 @@ dependencies = [
[[package]]
name = "anstream"
version = "0.6.20"
version = "0.6.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192"
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
dependencies = [
"anstyle",
"anstyle-parse",
@@ -65,9 +65,9 @@ dependencies = [
[[package]]
name = "anstyle"
version = "1.0.11"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
[[package]]
name = "anstyle-lossy"
@@ -214,15 +214,6 @@ version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
dependencies = [
"serde",
]
[[package]]
name = "bincode"
version = "2.0.1"
@@ -243,6 +234,26 @@ dependencies = [
"virtue",
]
[[package]]
name = "bindgen"
version = "0.72.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
dependencies = [
"bitflags 2.9.4",
"cexpr",
"clang-sys",
"itertools 0.10.5",
"log",
"prettyplease",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"syn",
]
[[package]]
name = "bitflags"
version = "1.3.2"
@@ -316,9 +327,9 @@ dependencies = [
[[package]]
name = "camino"
version = "1.2.0"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1de8bc0aa9e9385ceb3bf0c152e3a9b9544f6c4a912c8ae504e80c1f0368603"
checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609"
dependencies = [
"serde_core",
]
@@ -350,6 +361,15 @@ dependencies = [
"shlex",
]
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "1.0.3"
@@ -400,6 +420,17 @@ dependencies = [
"half",
]
[[package]]
name = "clang-sys"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "clap"
version = "4.5.48"
@@ -486,16 +517,17 @@ dependencies = [
[[package]]
name = "codspeed"
version = "3.0.5"
version = "4.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35584c5fcba8059780748866387fb97c5a203bcfc563fc3d0790af406727a117"
checksum = "d0f62ea8934802f8b374bf691eea524c3aa444d7014f604dd4182a3667b69510"
dependencies = [
"anyhow",
"bincode 1.3.3",
"bindgen",
"cc",
"colored 2.2.0",
"glob",
"libc",
"nix 0.29.0",
"nix 0.30.1",
"serde",
"serde_json",
"statrs",
@@ -504,20 +536,22 @@ dependencies = [
[[package]]
name = "codspeed-criterion-compat"
version = "3.0.5"
version = "4.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78f6c1c6bed5fd84d319e8b0889da051daa361c79b7709c9394dfe1a882bba67"
checksum = "d87efbc015fc0ff1b2001cd87df01c442824de677e01a77230bf091534687abb"
dependencies = [
"clap",
"codspeed",
"codspeed-criterion-compat-walltime",
"colored 2.2.0",
"regex",
]
[[package]]
name = "codspeed-criterion-compat-walltime"
version = "3.0.5"
version = "4.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c989289ce6b1cbde72ed560496cb8fbf5aa14d5ef5666f168e7f87751038352e"
checksum = "ae5713ace440123bb4f1f78dd068d46872cb8548bfe61f752e7b2ad2c06d7f00"
dependencies = [
"anes",
"cast",
@@ -540,20 +574,22 @@ dependencies = [
[[package]]
name = "codspeed-divan-compat"
version = "3.0.5"
version = "4.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adf64eda57508448d59efd940bad62ede7c50b0d451a150b8d6a0eca642792a6"
checksum = "95b4214b974f8f5206497153e89db90274e623f06b00bf4b9143eeb7735d975d"
dependencies = [
"clap",
"codspeed",
"codspeed-divan-compat-macros",
"codspeed-divan-compat-walltime",
"regex",
]
[[package]]
name = "codspeed-divan-compat-macros"
version = "3.0.5"
version = "4.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "058167258e819b16a4ba601fdfe270349ef191154758dbce122c62a698f70ba8"
checksum = "a53f34a16cb70ce4fd9ad57e1db016f0718e434f34179ca652006443b9a39967"
dependencies = [
"divan-macros",
"itertools 0.14.0",
@@ -565,9 +601,9 @@ dependencies = [
[[package]]
name = "codspeed-divan-compat-walltime"
version = "3.0.5"
version = "4.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48f9866ee3a4ef9d2868823ea5811886763af244f2df584ca247f49281c43f1f"
checksum = "e8a5099050c8948dce488b8eaa2e68dc5cf571cb8f9fce99aaaecbdddb940bcd"
dependencies = [
"cfg-if",
"clap",
@@ -1527,7 +1563,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5"
dependencies = [
"equivalent",
"hashbrown 0.16.0",
"hashbrown 0.15.5",
"serde",
"serde_core",
]
@@ -1801,15 +1837,15 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.175"
version = "0.2.177"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
[[package]]
name = "libcst"
version = "1.8.4"
version = "1.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "052ef5d9fc958a51aeebdf3713573b36c6fd6eed0bf0e60e204d2c0f8cf19b9f"
checksum = "9d56bcd52d9b5e5f43e7fba20eb1f423ccb18c84cdf1cb506b8c1b95776b0b49"
dependencies = [
"annotate-snippets",
"libcst_derive",
@@ -1822,14 +1858,24 @@ dependencies = [
[[package]]
name = "libcst_derive"
version = "1.8.4"
version = "1.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a91a751afee92cbdd59d4bc6754c7672712eec2d30a308f23de4e3287b2929cb"
checksum = "3fcf5a725c4db703660124fe0edb98285f1605d0b87b7ee8684b699764a4f01a"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "libloading"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
dependencies = [
"cfg-if",
"windows-link 0.2.0",
]
[[package]]
name = "libmimalloc-sys"
version = "0.1.44"
@@ -1971,9 +2017,9 @@ checksum = "2f926ade0c4e170215ae43342bf13b9310a437609c81f29f86c5df6657582ef9"
[[package]]
name = "memchr"
version = "2.7.5"
version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "memoffset"
@@ -2482,6 +2528,16 @@ dependencies = [
"yansi",
]
[[package]]
name = "prettyplease"
version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "proc-macro-crate"
version = "3.4.0"
@@ -2513,9 +2569,9 @@ dependencies = [
[[package]]
name = "pyproject-toml"
version = "0.13.6"
version = "0.13.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec768e063102b426e8962989758115e8659485124de9207bc365fab524125d65"
checksum = "f6d755483ad14b49e76713b52285235461a5b4f73f17612353e11a5de36a5fd2"
dependencies = [
"indexmap",
"pep440_rs",
@@ -2713,9 +2769,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.11.2"
version = "1.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c"
dependencies = [
"aho-corasick",
"memchr",
@@ -2725,9 +2781,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.10"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad"
dependencies = [
"aho-corasick",
"memchr",
@@ -2764,7 +2820,7 @@ dependencies = [
"anyhow",
"argfile",
"assert_fs",
"bincode 2.0.1",
"bincode",
"bitflags 2.9.4",
"cachedir",
"clap",
@@ -4443,6 +4499,7 @@ dependencies = [
"colored 3.0.0",
"insta",
"memchr",
"path-slash",
"regex",
"ruff_db",
"ruff_index",

View File

@@ -71,8 +71,8 @@ clap = { version = "4.5.3", features = ["derive"] }
clap_complete_command = { version = "0.6.0" }
clearscreen = { version = "4.0.0" }
csv = { version = "1.3.1" }
divan = { package = "codspeed-divan-compat", version = "3.0.2" }
codspeed-criterion-compat = { version = "3.0.2", default-features = false }
divan = { package = "codspeed-divan-compat", version = "4.0.4" }
codspeed-criterion-compat = { version = "4.0.4", default-features = false }
colored = { version = "3.0.0" }
console_error_panic_hook = { version = "0.1.7" }
console_log = { version = "1.0.0" }

View File

@@ -599,7 +599,7 @@ impl<'a> ProjectBenchmark<'a> {
self.project
.check_paths()
.iter()
.map(|path| path.to_path_buf())
.map(|path| SystemPathBuf::from(*path))
.collect(),
);
@@ -645,8 +645,8 @@ fn hydra(criterion: &mut Criterion) {
name: "hydra-zen",
repository: "https://github.com/mit-ll-responsible-ai/hydra-zen",
commit: "dd2b50a9614c6f8c46c5866f283c8f7e7a960aa8",
paths: vec![SystemPath::new("src")],
dependencies: vec!["pydantic", "beartype", "hydra-core"],
paths: &["src"],
dependencies: &["pydantic", "beartype", "hydra-core"],
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY313,
},
@@ -662,8 +662,8 @@ fn attrs(criterion: &mut Criterion) {
name: "attrs",
repository: "https://github.com/python-attrs/attrs",
commit: "a6ae894aad9bc09edc7cdad8c416898784ceec9b",
paths: vec![SystemPath::new("src")],
dependencies: vec![],
paths: &["src"],
dependencies: &[],
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY313,
},
@@ -679,8 +679,8 @@ fn anyio(criterion: &mut Criterion) {
name: "anyio",
repository: "https://github.com/agronholm/anyio",
commit: "561d81270a12f7c6bbafb5bc5fad99a2a13f96be",
paths: vec![SystemPath::new("src")],
dependencies: vec![],
paths: &["src"],
dependencies: &[],
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY313,
},
@@ -696,8 +696,8 @@ fn datetype(criterion: &mut Criterion) {
name: "DateType",
repository: "https://github.com/glyph/DateType",
commit: "57c9c93cf2468069f72945fc04bf27b64100dad8",
paths: vec![SystemPath::new("src")],
dependencies: vec![],
paths: &["src"],
dependencies: &[],
max_dep_date: "2025-07-04",
python_version: PythonVersion::PY313,
},

View File

@@ -1,6 +1,5 @@
use std::fmt::{Display, Formatter};
use divan::{Bencher, bench};
use std::fmt::{Display, Formatter};
use rayon::ThreadPoolBuilder;
use ruff_benchmark::real_world_projects::{InstalledProject, RealWorldProject};
@@ -13,29 +12,39 @@ use ty_project::metadata::value::{RangedValue, RelativePathBuf};
use ty_project::{Db, ProjectDatabase, ProjectMetadata};
struct Benchmark<'a> {
project: InstalledProject<'a>,
project: RealWorldProject<'a>,
installed_project: std::sync::OnceLock<InstalledProject<'a>>,
max_diagnostics: usize,
}
impl<'a> Benchmark<'a> {
fn new(project: RealWorldProject<'a>, max_diagnostics: usize) -> Self {
let setup_project = project.setup().expect("Failed to setup project");
const fn new(project: RealWorldProject<'a>, max_diagnostics: usize) -> Self {
Self {
project: setup_project,
project,
installed_project: std::sync::OnceLock::new(),
max_diagnostics,
}
}
fn installed_project(&self) -> &InstalledProject<'a> {
self.installed_project.get_or_init(|| {
self.project
.clone()
.setup()
.expect("Failed to setup project")
})
}
fn setup_iteration(&self) -> ProjectDatabase {
let root = SystemPathBuf::from_path_buf(self.project.path.clone()).unwrap();
let installed_project = self.installed_project();
let root = SystemPathBuf::from_path_buf(installed_project.path.clone()).unwrap();
let system = OsSystem::new(&root);
let mut metadata = ProjectMetadata::discover(&root, &system).unwrap();
metadata.apply_options(Options {
environment: Some(EnvironmentOptions {
python_version: Some(RangedValue::cli(self.project.config.python_version)),
python_version: Some(RangedValue::cli(installed_project.config.python_version)),
python: Some(RelativePathBuf::cli(SystemPath::new(".venv"))),
..EnvironmentOptions::default()
}),
@@ -46,7 +55,7 @@ impl<'a> Benchmark<'a> {
db.project().set_included_paths(
&mut db,
self.project
installed_project
.check_paths()
.iter()
.map(|path| SystemPath::absolute(path, &root))
@@ -58,7 +67,7 @@ impl<'a> Benchmark<'a> {
impl Display for Benchmark<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(self.project.config.name)
self.project.name.fmt(f)
}
}
@@ -75,166 +84,150 @@ fn check_project(db: &ProjectDatabase, max_diagnostics: usize) {
);
}
static ALTAIR: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::new(|| {
Benchmark::new(
RealWorldProject {
name: "altair",
repository: "https://github.com/vega/altair",
commit: "d1f4a1ef89006e5f6752ef1f6df4b7a509336fba",
paths: vec![SystemPath::new("altair")],
dependencies: vec![
"jinja2",
"narwhals",
"numpy",
"packaging",
"pandas-stubs",
"pyarrow-stubs",
"pytest",
"scipy-stubs",
"types-jsonschema",
],
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
1000,
)
});
static ALTAIR: Benchmark = Benchmark::new(
RealWorldProject {
name: "altair",
repository: "https://github.com/vega/altair",
commit: "d1f4a1ef89006e5f6752ef1f6df4b7a509336fba",
paths: &["altair"],
dependencies: &[
"jinja2",
"narwhals",
"numpy",
"packaging",
"pandas-stubs",
"pyarrow-stubs",
"pytest",
"scipy-stubs",
"types-jsonschema",
],
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
1000,
);
static COLOUR_SCIENCE: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::new(|| {
Benchmark::new(
RealWorldProject {
name: "colour-science",
repository: "https://github.com/colour-science/colour",
commit: "a17e2335c29e7b6f08080aa4c93cfa9b61f84757",
paths: vec![SystemPath::new("colour")],
dependencies: vec![
"matplotlib",
"numpy",
"pandas-stubs",
"pytest",
"scipy-stubs",
],
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY310,
},
600,
)
});
static COLOUR_SCIENCE: Benchmark = Benchmark::new(
RealWorldProject {
name: "colour-science",
repository: "https://github.com/colour-science/colour",
commit: "a17e2335c29e7b6f08080aa4c93cfa9b61f84757",
paths: &["colour"],
dependencies: &[
"matplotlib",
"numpy",
"pandas-stubs",
"pytest",
"scipy-stubs",
],
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY310,
},
600,
);
static FREQTRADE: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::new(|| {
Benchmark::new(
RealWorldProject {
name: "freqtrade",
repository: "https://github.com/freqtrade/freqtrade",
commit: "2d842ea129e56575852ee0c45383c8c3f706be19",
paths: vec![SystemPath::new("freqtrade")],
dependencies: vec![
"numpy",
"pandas-stubs",
"pydantic",
"sqlalchemy",
"types-cachetools",
"types-filelock",
"types-python-dateutil",
"types-requests",
"types-tabulate",
],
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
400,
)
});
static FREQTRADE: Benchmark = Benchmark::new(
RealWorldProject {
name: "freqtrade",
repository: "https://github.com/freqtrade/freqtrade",
commit: "2d842ea129e56575852ee0c45383c8c3f706be19",
paths: &["freqtrade"],
dependencies: &[
"numpy",
"pandas-stubs",
"pydantic",
"sqlalchemy",
"types-cachetools",
"types-filelock",
"types-python-dateutil",
"types-requests",
"types-tabulate",
],
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
400,
);
static PANDAS: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::new(|| {
Benchmark::new(
RealWorldProject {
name: "pandas",
repository: "https://github.com/pandas-dev/pandas",
commit: "5909621e2267eb67943a95ef5e895e8484c53432",
paths: vec![SystemPath::new("pandas")],
dependencies: vec![
"numpy",
"types-python-dateutil",
"types-pytz",
"types-PyMySQL",
"types-setuptools",
"pytest",
],
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
3000,
)
});
static PANDAS: Benchmark = Benchmark::new(
RealWorldProject {
name: "pandas",
repository: "https://github.com/pandas-dev/pandas",
commit: "5909621e2267eb67943a95ef5e895e8484c53432",
paths: &["pandas"],
dependencies: &[
"numpy",
"types-python-dateutil",
"types-pytz",
"types-PyMySQL",
"types-setuptools",
"pytest",
],
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
3000,
);
static PYDANTIC: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::new(|| {
Benchmark::new(
RealWorldProject {
name: "pydantic",
repository: "https://github.com/pydantic/pydantic",
commit: "0c4a22b64b23dfad27387750cf07487efc45eb05",
paths: vec![SystemPath::new("pydantic")],
dependencies: vec![
"annotated-types",
"pydantic-core",
"typing-extensions",
"typing-inspection",
],
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY39,
},
1000,
)
});
static PYDANTIC: Benchmark = Benchmark::new(
RealWorldProject {
name: "pydantic",
repository: "https://github.com/pydantic/pydantic",
commit: "0c4a22b64b23dfad27387750cf07487efc45eb05",
paths: &["pydantic"],
dependencies: &[
"annotated-types",
"pydantic-core",
"typing-extensions",
"typing-inspection",
],
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY39,
},
1000,
);
static SYMPY: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::new(|| {
Benchmark::new(
RealWorldProject {
name: "sympy",
repository: "https://github.com/sympy/sympy",
commit: "22fc107a94eaabc4f6eb31470b39db65abb7a394",
paths: vec![SystemPath::new("sympy")],
dependencies: vec!["mpmath"],
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
13000,
)
});
static SYMPY: Benchmark = Benchmark::new(
RealWorldProject {
name: "sympy",
repository: "https://github.com/sympy/sympy",
commit: "22fc107a94eaabc4f6eb31470b39db65abb7a394",
paths: &["sympy"],
dependencies: &["mpmath"],
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
13000,
);
static TANJUN: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::new(|| {
Benchmark::new(
RealWorldProject {
name: "tanjun",
repository: "https://github.com/FasterSpeeding/Tanjun",
commit: "69f40db188196bc59516b6c69849c2d85fbc2f4a",
paths: vec![SystemPath::new("tanjun")],
dependencies: vec!["hikari", "alluka"],
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
100,
)
});
static TANJUN: Benchmark = Benchmark::new(
RealWorldProject {
name: "tanjun",
repository: "https://github.com/FasterSpeeding/Tanjun",
commit: "69f40db188196bc59516b6c69849c2d85fbc2f4a",
paths: &["tanjun"],
dependencies: &["hikari", "alluka"],
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
100,
);
static STATIC_FRAME: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::new(|| {
Benchmark::new(
RealWorldProject {
name: "static-frame",
repository: "https://github.com/static-frame/static-frame",
commit: "34962b41baca5e7f98f5a758d530bff02748a421",
paths: vec![SystemPath::new("static_frame")],
// N.B. `arraykit` is installed as a dependency during mypy_primer runs,
// but it takes much longer to be installed in a Codspeed run than it does in a mypy_primer run
// (seems to be built from source on the Codspeed CI runners for some reason).
dependencies: vec!["numpy"],
max_dep_date: "2025-08-09",
python_version: PythonVersion::PY311,
},
600,
)
});
static STATIC_FRAME: Benchmark = Benchmark::new(
RealWorldProject {
name: "static-frame",
repository: "https://github.com/static-frame/static-frame",
commit: "34962b41baca5e7f98f5a758d530bff02748a421",
paths: &["static_frame"],
// N.B. `arraykit` is installed as a dependency during mypy_primer runs,
// but it takes much longer to be installed in a Codspeed run than it does in a mypy_primer run
// (seems to be built from source on the Codspeed CI runners for some reason).
dependencies: &["numpy"],
max_dep_date: "2025-08-09",
python_version: PythonVersion::PY311,
},
630,
);
#[track_caller]
fn run_single_threaded(bencher: Bencher, benchmark: &Benchmark) {
@@ -245,22 +238,22 @@ fn run_single_threaded(bencher: Bencher, benchmark: &Benchmark) {
});
}
#[bench(args=[&*ALTAIR, &*FREQTRADE, &*PYDANTIC, &*TANJUN], sample_size=2, sample_count=3)]
#[bench(args=[&ALTAIR, &FREQTRADE, &PYDANTIC, &TANJUN], sample_size=2, sample_count=3)]
fn small(bencher: Bencher, benchmark: &Benchmark) {
run_single_threaded(bencher, benchmark);
}
#[bench(args=[&*COLOUR_SCIENCE, &*PANDAS, &*STATIC_FRAME], sample_size=1, sample_count=3)]
#[bench(args=[&COLOUR_SCIENCE, &PANDAS, &STATIC_FRAME], sample_size=1, sample_count=3)]
fn medium(bencher: Bencher, benchmark: &Benchmark) {
run_single_threaded(bencher, benchmark);
}
#[bench(args=[&*SYMPY], sample_size=1, sample_count=2)]
#[bench(args=[&SYMPY], sample_size=1, sample_count=2)]
fn large(bencher: Bencher, benchmark: &Benchmark) {
run_single_threaded(bencher, benchmark);
}
#[bench(args=[&*PYDANTIC], sample_size=3, sample_count=8)]
#[bench(args=[&PYDANTIC], sample_size=3, sample_count=8)]
fn multithreaded(bencher: Bencher, benchmark: &Benchmark) {
let thread_pool = ThreadPoolBuilder::new().build().unwrap();

View File

@@ -30,9 +30,9 @@ pub struct RealWorldProject<'a> {
/// Specific commit hash to checkout
pub commit: &'a str,
/// List of paths within the project to check (`ty check <paths>`)
pub paths: Vec<&'a SystemPath>,
pub paths: &'a [&'a str],
/// Dependencies to install via uv
pub dependencies: Vec<&'a str>,
pub dependencies: &'a [&'a str],
/// Limit candidate packages to those that were uploaded prior to a given point in time (ISO 8601 format).
/// Maps to uv's `exclude-newer`.
pub max_dep_date: &'a str,
@@ -125,9 +125,9 @@ impl<'a> InstalledProject<'a> {
&self.config
}
/// Get the benchmark paths as `SystemPathBuf`
pub fn check_paths(&self) -> &[&SystemPath] {
&self.config.paths
/// Get the benchmark paths
pub fn check_paths(&self) -> &[&str] {
self.config.paths
}
/// Get the virtual environment path
@@ -297,7 +297,7 @@ fn install_dependencies(checkout: &Checkout) -> Result<()> {
"--exclude-newer",
checkout.project().max_dep_date,
])
.args(&checkout.project().dependencies);
.args(checkout.project().dependencies);
let output = cmd
.output()

View File

@@ -227,3 +227,32 @@ async def read_thing(query: str):
@app.get("/things/{ thing_id : str }")
async def read_thing(query: str):
return {"query": query}
# https://github.com/astral-sh/ruff/issues/20680
# These should NOT trigger FAST003 because FastAPI doesn't recognize them as path parameters
# Non-ASCII characters in parameter name
@app.get("/f1/{用户身份}")
async def f1():
return locals()
# Space in parameter name
@app.get("/f2/{x: str}")
async def f2():
return locals()
# Non-ASCII converter
@app.get("/f3/{complex_number:}")
async def f3():
return locals()
# Mixed non-ASCII characters
@app.get("/f4/{用户_id}")
async def f4():
return locals()
# Space in parameter name with converter
@app.get("/f5/{param: int}")
async def f5():
return locals()

View File

@@ -0,0 +1,8 @@
import logging
variablename = "value"
log = logging.getLogger(__name__)
log.info(f"a" f"b {variablename}")
log.info("a " f"b {variablename}")
log.info("prefix " f"middle {variablename}" f" suffix")

View File

@@ -1,12 +1,12 @@
use std::iter::Peekable;
use std::ops::Range;
use std::str::CharIndices;
use std::sync::LazyLock;
use regex::{CaptureMatches, Regex};
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast as ast;
use ruff_python_ast::{Arguments, Expr, ExprCall, ExprSubscript, Parameter, ParameterWithDefault};
use ruff_python_semantic::{BindingKind, Modules, ScopeKind, SemanticModel};
use ruff_python_stdlib::identifiers::is_identifier;
use ruff_text_size::{Ranged, TextSize};
use crate::Fix;
@@ -165,11 +165,6 @@ pub(crate) fn fastapi_unused_path_parameter(
// Check if any of the path parameters are not in the function signature.
for (path_param, range) in path_params {
// Ignore invalid identifiers (e.g., `user-id`, as opposed to `user_id`)
if !is_identifier(path_param) {
continue;
}
// If the path parameter is already in the function or the dependency signature,
// we don't need to do anything.
if named_args.contains(&path_param) {
@@ -461,15 +456,19 @@ fn parameter_alias<'a>(parameter: &'a Parameter, semantic: &SemanticModel) -> Op
/// the parameter name. For example, `/{x}` is a valid parameter, but `/{ x }` is treated literally.
#[derive(Debug)]
struct PathParamIterator<'a> {
input: &'a str,
chars: Peekable<CharIndices<'a>>,
inner: CaptureMatches<'a, 'a>,
}
impl<'a> PathParamIterator<'a> {
fn new(input: &'a str) -> Self {
PathParamIterator {
input,
chars: input.char_indices().peekable(),
/// Matches the Starlette pattern for path parameters with optional converters from
/// <https://github.com/Kludex/starlette/blob/e18637c68e36d112b1983bc0c8b663681e6a4c50/starlette/routing.py#L121>
static FASTAPI_PATH_PARAM_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"\{([a-zA-Z_][a-zA-Z0-9_]*)(?::[a-zA-Z_][a-zA-Z0-9_]*)?\}").unwrap()
});
Self {
inner: FASTAPI_PATH_PARAM_REGEX.captures_iter(input),
}
}
}
@@ -478,19 +477,10 @@ impl<'a> Iterator for PathParamIterator<'a> {
type Item = (&'a str, Range<usize>);
fn next(&mut self) -> Option<Self::Item> {
while let Some((start, c)) = self.chars.next() {
if c == '{' {
if let Some((end, _)) = self.chars.by_ref().find(|&(_, ch)| ch == '}') {
let param_content = &self.input[start + 1..end];
// We ignore text after a colon, since those are path converters
// See also: https://fastapi.tiangolo.com/tutorial/path-params/?h=path#path-convertor
let param_name_end = param_content.find(':').unwrap_or(param_content.len());
let param_name = &param_content[..param_name_end];
return Some((param_name, start..end + 1));
}
}
}
None
self.inner
.next()
// Extract the first capture group (the path parameter), but return the range of the
// whole match (everything in braces and including the braces themselves).
.and_then(|capture| Some((capture.get(1)?.as_str(), capture.get(0)?.range())))
}
}

View File

@@ -1091,9 +1091,12 @@ fn suspicious_function(
] => checker.report_diagnostic_if_enabled(SuspiciousInsecureCipherModeUsage, range),
// Mktemp
["tempfile", "mktemp"] => {
checker.report_diagnostic_if_enabled(SuspiciousMktempUsage, range)
}
["tempfile", "mktemp"] => checker
.report_diagnostic_if_enabled(SuspiciousMktempUsage, range)
.map(|mut diagnostic| {
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Deprecated);
diagnostic
}),
// Eval
["" | "builtins", "eval"] => {

View File

@@ -11,15 +11,15 @@ use crate::checkers::ast::Checker;
/// Checks for usage of `datetime.date.fromtimestamp()`.
///
/// ## Why is this bad?
/// Python datetime objects can be naive or timezone-aware. While an aware
/// Python date objects are naive, that is, not timezone-aware. While an aware
/// object represents a specific moment in time, a naive object does not
/// contain enough information to unambiguously locate itself relative to other
/// datetime objects. Since this can lead to errors, it is recommended to
/// always use timezone-aware objects.
///
/// `datetime.date.fromtimestamp(ts)` returns a naive datetime object.
/// Instead, use `datetime.datetime.fromtimestamp(ts, tz=...)` to create a
/// timezone-aware object.
/// `datetime.date.fromtimestamp(ts)` returns a naive date object.
/// Instead, use `datetime.datetime.fromtimestamp(ts, tz=...).date()` to
/// create a timezone-aware datetime object and retrieve its date component.
///
/// ## Example
/// ```python
@@ -32,14 +32,14 @@ use crate::checkers::ast::Checker;
/// ```python
/// import datetime
///
/// datetime.datetime.fromtimestamp(946684800, tz=datetime.timezone.utc)
/// datetime.datetime.fromtimestamp(946684800, tz=datetime.timezone.utc).date()
/// ```
///
/// Or, for Python 3.11 and later:
/// ```python
/// import datetime
///
/// datetime.datetime.fromtimestamp(946684800, tz=datetime.UTC)
/// datetime.datetime.fromtimestamp(946684800, tz=datetime.UTC).date()
/// ```
///
/// ## References

View File

@@ -11,14 +11,15 @@ use crate::checkers::ast::Checker;
/// Checks for usage of `datetime.date.today()`.
///
/// ## Why is this bad?
/// Python datetime objects can be naive or timezone-aware. While an aware
/// Python date objects are naive, that is, not timezone-aware. While an aware
/// object represents a specific moment in time, a naive object does not
/// contain enough information to unambiguously locate itself relative to other
/// datetime objects. Since this can lead to errors, it is recommended to
/// always use timezone-aware objects.
///
/// `datetime.date.today` returns a naive datetime object. Instead, use
/// `datetime.datetime.now(tz=...).date()` to create a timezone-aware object.
/// `datetime.date.today` returns a naive date object without taking timezones
/// into account. Instead, use `datetime.datetime.now(tz=...).date()` to
/// create a timezone-aware object and retrieve its date component.
///
/// ## Example
/// ```python

View File

@@ -42,6 +42,9 @@ use crate::rules::flake8_datetimez::helpers;
///
/// datetime.datetime.now(tz=datetime.UTC)
/// ```
///
/// ## References
/// - [Python documentation: Aware and Naive Objects](https://docs.python.org/3/library/datetime.html#aware-and-naive-objects)
#[derive(ViolationMetadata)]
pub(crate) struct CallDatetimeToday;

View File

@@ -41,6 +41,9 @@ use crate::rules::flake8_datetimez::helpers::{self, DatetimeModuleAntipattern};
///
/// datetime.datetime(2000, 1, 1, 0, 0, 0, tzinfo=datetime.UTC)
/// ```
///
/// ## References
/// - [Python documentation: Aware and Naive Objects](https://docs.python.org/3/library/datetime.html#aware-and-naive-objects)
#[derive(ViolationMetadata)]
pub(crate) struct CallDatetimeWithoutTzinfo(DatetimeModuleAntipattern);

View File

@@ -38,6 +38,9 @@ use crate::checkers::ast::Checker;
///
/// datetime.datetime.max.replace(tzinfo=datetime.UTC)
/// ```
///
/// ## References
/// - [Python documentation: Aware and Naive Objects](https://docs.python.org/3/library/datetime.html#aware-and-naive-objects)
#[derive(ViolationMetadata)]
pub(crate) struct DatetimeMinMax {
min_max: MinMax,

View File

@@ -23,6 +23,7 @@ mod tests {
#[test_case(Path::new("G003.py"))]
#[test_case(Path::new("G004.py"))]
#[test_case(Path::new("G004_arg_order.py"))]
#[test_case(Path::new("G004_implicit_concat.py"))]
#[test_case(Path::new("G010.py"))]
#[test_case(Path::new("G101_1.py"))]
#[test_case(Path::new("G101_2.py"))]
@@ -52,6 +53,7 @@ mod tests {
#[test_case(Rule::LoggingFString, Path::new("G004.py"))]
#[test_case(Rule::LoggingFString, Path::new("G004_arg_order.py"))]
#[test_case(Rule::LoggingFString, Path::new("G004_implicit_concat.py"))]
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(
"preview__{}_{}",

View File

@@ -42,38 +42,52 @@ fn logging_f_string(
// Default to double quotes if we can't determine it.
let quote_str = f_string
.value
.f_strings()
.iter()
.map(|part| match part {
ast::FStringPart::Literal(literal) => literal.flags.quote_str(),
ast::FStringPart::FString(f) => f.flags.quote_str(),
})
.next()
.map(|f| f.flags.quote_str())
.unwrap_or("\"");
for f in f_string.value.f_strings() {
for element in &f.elements {
match element {
InterpolatedStringElement::Literal(lit) => {
// If the literal text contains a '%' placeholder, bail out: mixing
// f-string interpolation with '%' placeholders is ambiguous for our
// automatic conversion, so don't offer a fix for this case.
if lit.value.as_ref().contains('%') {
return;
}
format_string.push_str(lit.value.as_ref());
for part in &f_string.value {
match part {
ast::FStringPart::Literal(literal) => {
let literal_text = literal.as_str();
if literal_text.contains('%') {
return;
}
InterpolatedStringElement::Interpolation(interpolated) => {
if interpolated.format_spec.is_some()
|| !matches!(
interpolated.conversion,
ruff_python_ast::ConversionFlag::None
)
{
return;
}
match interpolated.expression.as_ref() {
Expr::Name(name) => {
format_string.push_str("%s");
args.push(name.id.as_str());
format_string.push_str(literal_text);
}
ast::FStringPart::FString(f) => {
for element in &f.elements {
match element {
InterpolatedStringElement::Literal(lit) => {
// If the literal text contains a '%' placeholder, bail out: mixing
// f-string interpolation with '%' placeholders is ambiguous for our
// automatic conversion, so don't offer a fix for this case.
if lit.value.as_ref().contains('%') {
return;
}
format_string.push_str(lit.value.as_ref());
}
InterpolatedStringElement::Interpolation(interpolated) => {
if interpolated.format_spec.is_some()
|| !matches!(
interpolated.conversion,
ruff_python_ast::ConversionFlag::None
)
{
return;
}
match interpolated.expression.as_ref() {
Expr::Name(name) => {
format_string.push_str("%s");
args.push(name.id.as_str());
}
_ => return,
}
}
_ => return,
}
}
}

View File

@@ -0,0 +1,35 @@
---
source: crates/ruff_linter/src/rules/flake8_logging_format/mod.rs
assertion_line: 50
---
G004 Logging statement uses f-string
--> G004_implicit_concat.py:6:10
|
5 | log = logging.getLogger(__name__)
6 | log.info(f"a" f"b {variablename}")
| ^^^^^^^^^^^^^^^^^^^^^^^^
7 | log.info("a " f"b {variablename}")
8 | log.info("prefix " f"middle {variablename}" f" suffix")
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004_implicit_concat.py:7:10
|
5 | log = logging.getLogger(__name__)
6 | log.info(f"a" f"b {variablename}")
7 | log.info("a " f"b {variablename}")
| ^^^^^^^^^^^^^^^^^^^^^^^^
8 | log.info("prefix " f"middle {variablename}" f" suffix")
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004_implicit_concat.py:8:10
|
6 | log.info(f"a" f"b {variablename}")
7 | log.info("a " f"b {variablename}")
8 | log.info("prefix " f"middle {variablename}" f" suffix")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: Convert to lazy `%` formatting

View File

@@ -0,0 +1,53 @@
---
source: crates/ruff_linter/src/rules/flake8_logging_format/mod.rs
assertion_line: 71
---
G004 [*] Logging statement uses f-string
--> G004_implicit_concat.py:6:10
|
5 | log = logging.getLogger(__name__)
6 | log.info(f"a" f"b {variablename}")
| ^^^^^^^^^^^^^^^^^^^^^^^^
7 | log.info("a " f"b {variablename}")
8 | log.info("prefix " f"middle {variablename}" f" suffix")
|
help: Convert to lazy `%` formatting
3 | variablename = "value"
4 |
5 | log = logging.getLogger(__name__)
- log.info(f"a" f"b {variablename}")
6 + log.info("ab %s", variablename)
7 | log.info("a " f"b {variablename}")
8 | log.info("prefix " f"middle {variablename}" f" suffix")
G004 [*] Logging statement uses f-string
--> G004_implicit_concat.py:7:10
|
5 | log = logging.getLogger(__name__)
6 | log.info(f"a" f"b {variablename}")
7 | log.info("a " f"b {variablename}")
| ^^^^^^^^^^^^^^^^^^^^^^^^
8 | log.info("prefix " f"middle {variablename}" f" suffix")
|
help: Convert to lazy `%` formatting
4 |
5 | log = logging.getLogger(__name__)
6 | log.info(f"a" f"b {variablename}")
- log.info("a " f"b {variablename}")
7 + log.info("a b %s", variablename)
8 | log.info("prefix " f"middle {variablename}" f" suffix")
G004 [*] Logging statement uses f-string
--> G004_implicit_concat.py:8:10
|
6 | log.info(f"a" f"b {variablename}")
7 | log.info("a " f"b {variablename}")
8 | log.info("prefix " f"middle {variablename}" f" suffix")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: Convert to lazy `%` formatting
5 | log = logging.getLogger(__name__)
6 | log.info(f"a" f"b {variablename}")
7 | log.info("a " f"b {variablename}")
- log.info("prefix " f"middle {variablename}" f" suffix")
8 + log.info("prefix middle %s suffix", variablename)

View File

@@ -74,7 +74,8 @@ pub(crate) fn bytestring_attribute(checker: &Checker, attribute: &Expr) {
["collections", "abc", "ByteString"] => ByteStringOrigin::CollectionsAbc,
_ => return,
};
checker.report_diagnostic(ByteStringUsage { origin }, attribute.range());
let mut diagnostic = checker.report_diagnostic(ByteStringUsage { origin }, attribute.range());
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Deprecated);
}
/// PYI057
@@ -94,7 +95,9 @@ pub(crate) fn bytestring_import(checker: &Checker, import_from: &ast::StmtImport
for name in names {
if name.name.as_str() == "ByteString" {
checker.report_diagnostic(ByteStringUsage { origin }, name.range());
let mut diagnostic =
checker.report_diagnostic(ByteStringUsage { origin }, name.range());
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Deprecated);
}
}
}

View File

@@ -898,7 +898,9 @@ fn check_test_function_args(checker: &Checker, parameters: &Parameters, decorato
/// PT020
fn check_fixture_decorator_name(checker: &Checker, decorator: &Decorator) {
if is_pytest_yield_fixture(decorator, checker.semantic()) {
checker.report_diagnostic(PytestDeprecatedYieldFixture, decorator.range());
let mut diagnostic =
checker.report_diagnostic(PytestDeprecatedYieldFixture, decorator.range());
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Deprecated);
}
}

View File

@@ -223,7 +223,7 @@ enum Argumentable {
impl Argumentable {
fn check_for(self, checker: &Checker, name: String, range: TextRange) {
match self {
let mut diagnostic = match self {
Self::Function => checker.report_diagnostic(UnusedFunctionArgument { name }, range),
Self::Method => checker.report_diagnostic(UnusedMethodArgument { name }, range),
Self::ClassMethod => {
@@ -234,6 +234,7 @@ impl Argumentable {
}
Self::Lambda => checker.report_diagnostic(UnusedLambdaArgument { name }, range),
};
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Unnecessary);
}
const fn rule_code(self) -> Rule {

View File

@@ -80,6 +80,7 @@ pub(crate) fn deprecated_function(checker: &Checker, expr: &Expr) {
},
expr.range(),
);
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Deprecated);
diagnostic.try_set_fix(|| {
let (import_edit, binding) = checker.importer().get_or_import_symbol(
&ImportRequest::import_from("numpy", replacement),

View File

@@ -80,6 +80,7 @@ pub(crate) fn deprecated_type_alias(checker: &Checker, expr: &Expr) {
},
expr.range(),
);
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Deprecated);
let type_name = match type_name {
"unicode" => "str",
_ => type_name,

View File

@@ -15,8 +15,10 @@ use ruff_text_size::{Ranged, TextRange, TextSize};
use ty_python_semantic::HasDefinition;
use ty_python_semantic::ImportAliasResolution;
use ty_python_semantic::ResolvedDefinition;
use ty_python_semantic::types::definitions_for_keyword_argument;
use ty_python_semantic::types::{Type, call_signature_details};
use ty_python_semantic::types::Type;
use ty_python_semantic::types::ide_support::{
call_signature_details, definitions_for_keyword_argument,
};
use ty_python_semantic::{
HasType, SemanticModel, definitions_for_imported_symbol, definitions_for_name,
};

View File

@@ -308,26 +308,8 @@ mod tests {
"#,
);
assert_snapshot!(test.goto_type_definition(), @r#"
info[goto-type-definition]: Type definition
--> main.py:4:1
|
2 | from typing_extensions import TypeAliasType
3 |
4 | Alias = TypeAliasType("Alias", tuple[int, int])
| ^^^^^
5 |
6 | Alias
|
info: Source
--> main.py:6:1
|
4 | Alias = TypeAliasType("Alias", tuple[int, int])
5 |
6 | Alias
| ^^^^^
|
"#);
// TODO: This should jump to the definition of `Alias` above.
assert_snapshot!(test.goto_type_definition(), @"No type definitions found");
}
#[test]

View File

@@ -6,7 +6,8 @@ use ruff_db::parsed::parsed_module;
use ruff_python_ast::visitor::source_order::{self, SourceOrderVisitor, TraversalSignal};
use ruff_python_ast::{AnyNodeRef, Expr, Stmt};
use ruff_text_size::{Ranged, TextRange, TextSize};
use ty_python_semantic::types::{Type, inlay_hint_function_argument_details};
use ty_python_semantic::types::Type;
use ty_python_semantic::types::ide_support::inlay_hint_function_argument_details;
use ty_python_semantic::{HasType, SemanticModel};
#[derive(Debug, Clone)]

View File

@@ -13,9 +13,8 @@ use ruff_python_ast::{
use ruff_text_size::{Ranged, TextLen, TextRange};
use std::ops::Deref;
use ty_python_semantic::{
HasType, SemanticModel,
semantic_index::definition::DefinitionKind,
types::{Type, definition_kind_for_name},
HasType, SemanticModel, semantic_index::definition::DefinitionKind, types::Type,
types::ide_support::definition_kind_for_name,
};
// This module walks the AST and collects a set of "semantic tokens" for a file

View File

@@ -17,7 +17,7 @@ use ruff_text_size::{Ranged, TextRange, TextSize};
use ty_python_semantic::ResolvedDefinition;
use ty_python_semantic::SemanticModel;
use ty_python_semantic::semantic_index::definition::Definition;
use ty_python_semantic::types::{
use ty_python_semantic::types::ide_support::{
CallSignatureDetails, call_signature_details, find_active_signature_from_details,
};

View File

@@ -130,13 +130,9 @@ type IntList = list[int]
m: IntList = [1, 2, 3]
reveal_type(m) # revealed: list[int]
# TODO: this should type-check and avoid literal promotion
# error: [invalid-assignment] "Object of type `list[Unknown | int]` is not assignable to `list[Literal[1, 2, 3]]`"
n: list[typing.Literal[1, 2, 3]] = [1, 2, 3]
reveal_type(n) # revealed: list[Literal[1, 2, 3]]
# TODO: this should type-check and avoid literal promotion
# error: [invalid-assignment] "Object of type `list[Unknown | str]` is not assignable to `list[LiteralString]`"
o: list[typing.LiteralString] = ["a", "b", "c"]
reveal_type(o) # revealed: list[LiteralString]
@@ -150,7 +146,75 @@ r: dict[int | str, int | str] = {1: 1, 2: 2, 3: 3}
reveal_type(r) # revealed: dict[int | str, int | str]
```
## Incorrect collection literal assignments are complained aobut
## Optional collection literal annotations are understood
```toml
[environment]
python-version = "3.12"
```
```py
import typing
a: list[int] | None = [1, 2, 3]
reveal_type(a) # revealed: list[int]
b: list[int | str] | None = [1, 2, 3]
reveal_type(b) # revealed: list[int | str]
c: typing.List[int] | None = [1, 2, 3]
reveal_type(c) # revealed: list[int]
d: list[typing.Any] | None = []
reveal_type(d) # revealed: list[Any]
e: set[int] | None = {1, 2, 3}
reveal_type(e) # revealed: set[int]
f: set[int | str] | None = {1, 2, 3}
reveal_type(f) # revealed: set[int | str]
g: typing.Set[int] | None = {1, 2, 3}
reveal_type(g) # revealed: set[int]
h: list[list[int]] | None = [[], [42]]
reveal_type(h) # revealed: list[list[int]]
i: list[typing.Any] | None = [1, 2, "3", ([4],)]
reveal_type(i) # revealed: list[Any | int | str | tuple[list[Unknown | int]]]
j: list[tuple[str | int, ...]] | None = [(1, 2), ("foo", "bar"), ()]
reveal_type(j) # revealed: list[tuple[str | int, ...]]
k: list[tuple[list[int], ...]] | None = [([],), ([1, 2], [3, 4]), ([5], [6], [7])]
reveal_type(k) # revealed: list[tuple[list[int], ...]]
l: tuple[list[int], *tuple[list[typing.Any], ...], list[str]] | None = ([1, 2, 3], [4, 5, 6], [7, 8, 9], ["10", "11", "12"])
# TODO: this should be `tuple[list[int], list[Any | int], list[Any | int], list[str]]`
reveal_type(l) # revealed: tuple[list[Unknown | int], list[Unknown | int], list[Unknown | int], list[Unknown | str]]
type IntList = list[int]
m: IntList | None = [1, 2, 3]
reveal_type(m) # revealed: list[int]
n: list[typing.Literal[1, 2, 3]] | None = [1, 2, 3]
reveal_type(n) # revealed: list[Literal[1, 2, 3]]
o: list[typing.LiteralString] | None = ["a", "b", "c"]
reveal_type(o) # revealed: list[LiteralString]
p: dict[int, int] | None = {}
reveal_type(p) # revealed: dict[int, int]
q: dict[int | str, int] | None = {1: 1, 2: 2, 3: 3}
reveal_type(q) # revealed: dict[int | str, int]
r: dict[int | str, int | str] | None = {1: 1, 2: 2, 3: 3}
reveal_type(r) # revealed: dict[int | str, int | str]
```
## Incorrect collection literal assignments are complained about
```py
# error: [invalid-assignment] "Object of type `list[Unknown | int]` is not assignable to `list[str]`"
@@ -160,6 +224,81 @@ a: list[str] = [1, 2, 3]
b: set[int] = {1, 2, "3"}
```
## Literal annnotations are respected
```toml
[environment]
python-version = "3.12"
```
```py
from enum import Enum
from typing_extensions import Literal, LiteralString
a: list[Literal[1]] = [1]
reveal_type(a) # revealed: list[Literal[1]]
b: list[Literal[True]] = [True]
reveal_type(b) # revealed: list[Literal[True]]
c: list[Literal["a"]] = ["a"]
reveal_type(c) # revealed: list[Literal["a"]]
d: list[LiteralString] = ["a", "b", "c"]
reveal_type(d) # revealed: list[LiteralString]
e: list[list[Literal[1]]] = [[1]]
reveal_type(e) # revealed: list[list[Literal[1]]]
class Color(Enum):
RED = "red"
f: dict[list[Literal[1]], list[Literal[Color.RED]]] = {[1]: [Color.RED, Color.RED]}
reveal_type(f) # revealed: dict[list[Literal[1]], list[Literal[Color.RED]]]
class X[T]:
def __init__(self, value: T): ...
g: X[Literal[1]] = X(1)
reveal_type(g) # revealed: X[Literal[1]]
h: X[int] = X(1)
reveal_type(h) # revealed: X[int]
i: dict[list[X[Literal[1]]], set[Literal[b"a"]]] = {[X(1)]: {b"a"}}
reveal_type(i) # revealed: dict[list[X[Literal[1]]], set[Literal[b"a"]]]
j: list[Literal[1, 2, 3]] = [1, 2, 3]
reveal_type(j) # revealed: list[Literal[1, 2, 3]]
k: list[Literal[1] | Literal[2] | Literal[3]] = [1, 2, 3]
reveal_type(k) # revealed: list[Literal[1, 2, 3]]
type Y[T] = list[T]
l: Y[Y[Literal[1]]] = [[1]]
reveal_type(l) # revealed: list[list[Literal[1]]]
m: list[tuple[Literal[1], Literal[2], Literal[3]]] = [(1, 2, 3)]
reveal_type(m) # revealed: list[tuple[Literal[1], Literal[2], Literal[3]]]
n: list[tuple[int, str, int]] = [(1, "2", 3), (4, "5", 6)]
reveal_type(n) # revealed: list[tuple[int, str, int]]
o: list[tuple[Literal[1], ...]] = [(1, 1, 1)]
reveal_type(o) # revealed: list[tuple[Literal[1], ...]]
p: list[tuple[int, ...]] = [(1, 1, 1)]
reveal_type(p) # revealed: list[tuple[int, ...]]
# literal promotion occurs based on assignability, an exact match is not required
q: list[int | Literal[1]] = [1]
reveal_type(q) # revealed: list[int]
r: list[Literal[1, 2, 3, 4]] = [1, 2]
reveal_type(r) # revealed: list[Literal[1, 2, 3, 4]]
```
## PEP-604 annotations are supported
```py
@@ -237,6 +376,22 @@ x = Foo()
reveal_type(x) # revealed: Foo
```
## Annotations are deferred by default in Python 3.14 and later
```toml
[environment]
python-version = "3.14"
```
```py
x: Foo
class Foo: ...
x = Foo()
reveal_type(x) # revealed: Foo
```
## Annotated assignments in stub files are inferred correctly
```pyi

View File

@@ -820,22 +820,30 @@ reveal_type(C().c) # revealed: int
### Inheritance of class/instance attributes
#### Instance variable defined in a base class
```py
class Base:
declared_in_body: int | None = 1
attribute: int | None = 1
base_class_attribute_1: str | None
base_class_attribute_2: str | None
base_class_attribute_3: str | None
redeclared_with_same_type: str | None
redeclared_with_narrower_type: str | None
redeclared_with_wider_type: str | None
overwritten_in_subclass_body: str
overwritten_in_subclass_method: str
undeclared = "base"
def __init__(self) -> None:
self.defined_in_init: str | None = "value in base"
self.pure_attribute: str | None = "value in base"
self.pure_overwritten_in_subclass_body: str = "value in base"
self.pure_overwritten_in_subclass_method: str = "value in base"
self.pure_undeclared = "base"
class Intermediate(Base):
# Redeclaring base class attributes with the *same *type is fine:
base_class_attribute_1: str | None = None
redeclared_with_same_type: str | None = None
# Redeclaring them with a *narrower type* is unsound, because modifications
# through a `Base` reference could violate that constraint.
@@ -847,22 +855,67 @@ class Intermediate(Base):
# enabled by default can still be discussed.
#
# TODO: This should be an error
base_class_attribute_2: str
redeclared_with_narrower_type: str
# Redeclaring attributes with a *wider type* directly violates LSP.
#
# In this case, both mypy and pyright report an error.
#
# TODO: This should be an error
base_class_attribute_3: str | int | None
redeclared_with_wider_type: str | int | None
# TODO: This should be an `invalid-assignment` error
overwritten_in_subclass_body = None
# TODO: This should be an `invalid-assignment` error
pure_overwritten_in_subclass_body = None
undeclared = "intermediate"
def set_attributes(self) -> None:
# TODO: This should be an `invalid-assignment` error
self.overwritten_in_subclass_method = None
# TODO: This should be an `invalid-assignment` error
self.pure_overwritten_in_subclass_method = None
self.pure_undeclared = "intermediate"
class Derived(Intermediate): ...
reveal_type(Derived.declared_in_body) # revealed: int | None
reveal_type(Derived.attribute) # revealed: int | None
reveal_type(Derived().attribute) # revealed: int | None
reveal_type(Derived().declared_in_body) # revealed: int | None
reveal_type(Derived.redeclared_with_same_type) # revealed: str | None
reveal_type(Derived().redeclared_with_same_type) # revealed: str | None
reveal_type(Derived().defined_in_init) # revealed: str | None
# TODO: It would probably be more consistent if these were `str | None`
reveal_type(Derived.redeclared_with_narrower_type) # revealed: str
reveal_type(Derived().redeclared_with_narrower_type) # revealed: str
# TODO: It would probably be more consistent if these were `str | None`
reveal_type(Derived.redeclared_with_wider_type) # revealed: str | int | None
reveal_type(Derived().redeclared_with_wider_type) # revealed: str | int | None
# TODO: Both of these should be `str`
reveal_type(Derived.overwritten_in_subclass_body) # revealed: Unknown | None
reveal_type(Derived().overwritten_in_subclass_body) # revealed: Unknown | None | str
reveal_type(Derived.overwritten_in_subclass_method) # revealed: str
reveal_type(Derived().overwritten_in_subclass_method) # revealed: str
reveal_type(Derived().pure_attribute) # revealed: str | None
# TODO: This should be `str`
reveal_type(Derived().pure_overwritten_in_subclass_body) # revealed: Unknown | None | str
reveal_type(Derived().pure_overwritten_in_subclass_method) # revealed: str
# TODO: Both of these should be `Unknown | Literal["intermediate", "base"]`
reveal_type(Derived.undeclared) # revealed: Unknown | Literal["intermediate"]
reveal_type(Derived().undeclared) # revealed: Unknown | Literal["intermediate"]
reveal_type(Derived().pure_undeclared) # revealed: Unknown | Literal["intermediate", "base"]
```
## Accessing attributes on class objects

View File

@@ -0,0 +1,147 @@
# Bidirectional type inference
ty partially supports bidirectional type inference. This is a mechanism for inferring the type of an
expression "from the outside in". Normally, type inference proceeds "from the inside out". That is,
in order to infer the type of an expression, the types of all sub-expressions must first be
inferred. There is no reverse dependency. However, when performing complex type inference, such as
when generics are involved, the type of an outer expression can sometimes be useful in inferring
inner expressions. Bidirectional type inference is a mechanism that propagates such "expected types"
to the inference of inner expressions.
## Propagating target type annotation
```toml
[environment]
python-version = "3.12"
```
```py
def list1[T](x: T) -> list[T]:
return [x]
l1 = list1(1)
reveal_type(l1) # revealed: list[Literal[1]]
l2: list[int] = list1(1)
reveal_type(l2) # revealed: list[int]
# `list[Literal[1]]` and `list[int]` are incompatible, since `list[T]` is invariant in `T`.
# error: [invalid-assignment] "Object of type `list[Literal[1]]` is not assignable to `list[int]`"
l2 = l1
intermediate = list1(1)
# TODO: the error will not occur if we can infer the type of `intermediate` to be `list[int]`
# error: [invalid-assignment] "Object of type `list[Literal[1]]` is not assignable to `list[int]`"
l3: list[int] = intermediate
# TODO: it would be nice if this were `list[int]`
reveal_type(intermediate) # revealed: list[Literal[1]]
reveal_type(l3) # revealed: list[int]
l4: list[int | str] | None = list1(1)
reveal_type(l4) # revealed: list[int | str]
def _(l: list[int] | None = None):
l1 = l or list()
reveal_type(l1) # revealed: (list[int] & ~AlwaysFalsy) | list[Unknown]
l2: list[int] = l or list()
# 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]
# TODO: no error
# error: [invalid-assignment] "Object of type `Literal[1] | list[Literal[1]]` is not assignable to `int | list[int]`"
l5: int | list[int] = f(1, True)
```
`typed_dict.py`:
```py
from typing import TypedDict
class TD(TypedDict):
x: int
d1 = {"x": 1}
d2: TD = {"x": 1}
d3: dict[str, int] = {"x": 1}
reveal_type(d1) # revealed: dict[Unknown | str, Unknown | int]
reveal_type(d2) # revealed: TD
reveal_type(d3) # revealed: dict[str, int]
def _() -> TD:
return {"x": 1}
def _() -> TD:
# error: [missing-typed-dict-key] "Missing required key 'x' in TypedDict `TD` constructor"
return {}
```
## Propagating return type annotation
```toml
[environment]
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({})
reveal_type(list1(res)) # revealed: list[dict[Unknown, Unknown] & ~AlwaysFalsy]
# `list[dict[Unknown, Unknown] & ~AlwaysFalsy]` and `list[dict[Unknown, Unknown]]` are incompatible,
# but the return type check passes here because the type of `list1(res)` is inferred
# 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]:
if not (res := get_data()):
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
def f(x: str) -> list[str]: ...
def f(x: int | str) -> list[int] | list[str]:
# `list[int] | list[str]` is disjoint from `list[int | str]`.
if isinstance(x, int):
return list1(x)
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]
```

View File

@@ -689,7 +689,7 @@ def _(
# revealed: (obj: type) -> None
reveal_type(e)
# revealed: (fget: ((Any, /) -> Any) | None = None, fset: ((Any, Any, /) -> None) | None = None, fdel: ((Any, /) -> Any) | None = None, doc: str | None = None) -> Unknown
# revealed: (fget: ((Any, /) -> Any) | None = EllipsisType, fset: ((Any, Any, /) -> None) | None = EllipsisType, fdel: ((Any, /) -> None) | None = EllipsisType, doc: str | None = EllipsisType) -> property
reveal_type(f)
# revealed: Overload[(self: property, instance: None, owner: type, /) -> Unknown, (self: property, instance: object, owner: type | None = None, /) -> Unknown]

View File

@@ -99,8 +99,6 @@ If the arity check only matches a single overload, it should be evaluated as a r
call should be reported directly and not as a `no-matching-overload` error.
```py
from typing_extensions import reveal_type
from overloaded import f
reveal_type(f()) # revealed: None
@@ -1210,11 +1208,7 @@ from typing_extensions import LiteralString
def f(a: Foo, b: list[str], c: list[LiteralString], e):
reveal_type(e) # revealed: Unknown
# TODO: we should select the second overload here and reveal `str`
# (the incorrect result is due to missing logic in protocol subtyping/assignability)
reveal_type(a.join(b)) # revealed: LiteralString
reveal_type(a.join(b)) # revealed: str
reveal_type(a.join(c)) # revealed: LiteralString
# since both overloads match and they have return types that are not equivalent,

View File

@@ -14,9 +14,16 @@ common usage.
### Explicit Super Object
<!-- snapshot-diagnostics -->
`super(pivot_class, owner)` performs attribute lookup along the MRO, starting immediately after the
specified pivot class.
```toml
[environment]
python-version = "3.12"
```
```py
class A:
def a(self): ...
@@ -34,21 +41,15 @@ reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, <class 'B'>, <class 'A'>,
super(C, C()).a
super(C, C()).b
# error: [unresolved-attribute] "Type `<super: <class 'C'>, C>` has no attribute `c`"
super(C, C()).c
super(C, C()).c # error: [unresolved-attribute]
super(B, C()).a
# error: [unresolved-attribute] "Type `<super: <class 'B'>, C>` has no attribute `b`"
super(B, C()).b
# error: [unresolved-attribute] "Type `<super: <class 'B'>, C>` has no attribute `c`"
super(B, C()).c
super(B, C()).b # error: [unresolved-attribute]
super(B, C()).c # error: [unresolved-attribute]
# error: [unresolved-attribute] "Type `<super: <class 'A'>, C>` has no attribute `a`"
super(A, C()).a
# error: [unresolved-attribute] "Type `<super: <class 'A'>, C>` has no attribute `b`"
super(A, C()).b
# error: [unresolved-attribute] "Type `<super: <class 'A'>, C>` has no attribute `c`"
super(A, C()).c
super(A, C()).a # error: [unresolved-attribute]
super(A, C()).b # error: [unresolved-attribute]
super(A, C()).c # error: [unresolved-attribute]
reveal_type(super(C, C()).a) # revealed: bound method C.a() -> Unknown
reveal_type(super(C, C()).b) # revealed: bound method C.b() -> Unknown
@@ -56,12 +57,80 @@ reveal_type(super(C, C()).aa) # revealed: int
reveal_type(super(C, C()).bb) # revealed: int
```
Examples of explicit `super()` with unusual types. We allow almost any type to be passed as the
second argument to `super()` -- the only exceptions are "pure abstract" types such as `Callable` and
synthesized `Protocol`s that cannot be upcast to, or interpreted as, a non-`object` nominal type.
```py
import types
from typing_extensions import Callable, TypeIs, Literal, TypedDict
def f(): ...
class Foo[T]:
def method(self): ...
@property
def some_property(self): ...
type Alias = int
class SomeTypedDict(TypedDict):
x: int
y: bytes
# revealed: <super: <class 'object'>, FunctionType>
reveal_type(super(object, f))
# revealed: <super: <class 'object'>, WrapperDescriptorType>
reveal_type(super(object, types.FunctionType.__get__))
# revealed: <super: <class 'object'>, GenericAlias>
reveal_type(super(object, Foo[int]))
# revealed: <super: <class 'object'>, _SpecialForm>
reveal_type(super(object, Literal))
# revealed: <super: <class 'object'>, TypeAliasType>
reveal_type(super(object, Alias))
# revealed: <super: <class 'object'>, MethodType>
reveal_type(super(object, Foo().method))
# revealed: <super: <class 'object'>, property>
reveal_type(super(object, Foo.some_property))
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'>
reveal_type(x)
# error: [invalid-super-argument]
# revealed: Unknown
reveal_type(super(object, x))
# error: [invalid-super-argument]
# revealed: Unknown
reveal_type(super(object, z))
is_list = g(x)
# revealed: TypeIs[list[object] @ x]
reveal_type(is_list)
# revealed: <super: <class 'object'>, bool>
reveal_type(super(object, is_list))
# revealed: <super: <class 'object'>, dict[Literal["x", "y"], int | bytes]>
reveal_type(super(object, y))
```
### Implicit Super Object
<!-- snapshot-diagnostics -->
The implicit form `super()` is same as `super(__class__, <first argument>)`. The `__class__` refers
to the class that contains the function where `super()` is used. The first argument refers to the
current methods first parameter (typically `self` or `cls`).
```toml
[environment]
python-version = "3.12"
```
```py
from __future__ import annotations
@@ -74,6 +143,7 @@ class B(A):
def __init__(self, a: int):
# TODO: Once `Self` is supported, this should be `<super: <class 'B'>, B>`
reveal_type(super()) # revealed: <super: <class 'B'>, Unknown>
reveal_type(super(object, super())) # revealed: <super: <class 'object'>, super>
super().__init__(a)
@classmethod
@@ -86,6 +156,123 @@ super(B, B(42)).__init__(42)
super(B, B).f()
```
Some examples with unusual annotations for `self` or `cls`:
```py
import enum
from typing import Any, Self, Never, Protocol, Callable
from ty_extensions import Intersection
class BuilderMeta(type):
def __new__(
cls: type[Any],
name: str,
bases: tuple[type, ...],
dct: dict[str, Any],
) -> BuilderMeta:
# revealed: <super: <class 'BuilderMeta'>, Any>
s = reveal_type(super())
# revealed: Any
return reveal_type(s.__new__(cls, name, bases, dct))
class BuilderMeta2(type):
def __new__(
cls: type[BuilderMeta2],
name: str,
bases: tuple[type, ...],
dct: dict[str, Any],
) -> BuilderMeta2:
# revealed: <super: <class 'BuilderMeta2'>, <class 'BuilderMeta2'>>
s = reveal_type(super())
# TODO: should be `BuilderMeta2` (needs https://github.com/astral-sh/ty/issues/501)
# revealed: Unknown
return reveal_type(s.__new__(cls, name, bases, dct))
class Foo[T]:
x: T
def method(self: Any):
reveal_type(super()) # revealed: <super: <class 'Foo'>, Any>
if isinstance(self, Foo):
reveal_type(super()) # revealed: <super: <class 'Foo'>, Any>
def method2(self: Foo[T]):
# revealed: <super: <class 'Foo'>, Foo[T@Foo]>
reveal_type(super())
def method3(self: Foo):
# revealed: <super: <class 'Foo'>, Foo[Unknown]>
reveal_type(super())
def method4(self: Self):
# revealed: <super: <class 'Foo'>, Foo[T@Foo]>
reveal_type(super())
def method5[S: Foo[int]](self: S, other: S) -> S:
# revealed: <super: <class 'Foo'>, Foo[int]>
reveal_type(super())
return self
def method6[S: (Foo[int], Foo[str])](self: S, other: S) -> S:
# revealed: <super: <class 'Foo'>, Foo[int]> | <super: <class 'Foo'>, Foo[str]>
reveal_type(super())
return self
def method7[S](self: S, other: S) -> S:
# error: [invalid-super-argument]
# revealed: Unknown
reveal_type(super())
return self
def method8[S: int](self: S, other: S) -> S:
# error: [invalid-super-argument]
# revealed: Unknown
reveal_type(super())
return self
def method9[S: (int, str)](self: S, other: S) -> S:
# error: [invalid-super-argument]
# revealed: Unknown
reveal_type(super())
return self
def method10[S: Callable[..., str]](self: S, other: S) -> S:
# error: [invalid-super-argument]
# revealed: Unknown
reveal_type(super())
return self
type Alias = Bar
class Bar:
def method(self: Alias):
# revealed: <super: <class 'Bar'>, Bar>
reveal_type(super())
def pls_dont_call_me(self: Never):
# revealed: <super: <class 'Bar'>, Unknown>
reveal_type(super())
def only_call_me_on_callable_subclasses(self: Intersection[Bar, Callable[..., object]]):
# 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
def method(self: E):
match self:
case E.X:
# revealed: <super: <class 'E'>, E>
reveal_type(super())
```
### Unbound Super Object
Calling `super(cls)` without a second argument returns an _unbound super object_. This is treated as
@@ -167,11 +354,19 @@ class A:
## Built-ins and Literals
```py
from enum import Enum
reveal_type(super(bool, True)) # revealed: <super: <class 'bool'>, bool>
reveal_type(super(bool, bool())) # revealed: <super: <class 'bool'>, bool>
reveal_type(super(int, bool())) # revealed: <super: <class 'int'>, bool>
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>
```
## Descriptor Behavior with Super
@@ -342,7 +537,7 @@ def f(x: int):
# error: [invalid-super-argument] "`typing.TypeAliasType` is not a valid class"
super(IntAlias, 0)
# error: [invalid-super-argument] "`Literal[""]` is not an instance or subclass of `<class 'int'>` in `super(<class 'int'>, Literal[""])` call"
# 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()))

View File

@@ -67,7 +67,7 @@ class C:
name: str
```
## Types of decorators
## Types of dataclass-transformers
The examples from this section are straight from the Python documentation on
[`typing.dataclass_transform`].
@@ -165,7 +165,7 @@ Normal(1) < Normal(2) # error: [unsupported-operator]
class NormalOverwritten:
inner: int
NormalOverwritten(1) < NormalOverwritten(2)
reveal_type(NormalOverwritten(1) < NormalOverwritten(2)) # revealed: bool
@order_default_false
class OrderFalse:
@@ -177,13 +177,13 @@ OrderFalse(1) < OrderFalse(2) # error: [unsupported-operator]
class OrderFalseOverwritten:
inner: int
OrderFalseOverwritten(1) < OrderFalseOverwritten(2)
reveal_type(OrderFalseOverwritten(1) < OrderFalseOverwritten(2)) # revealed: bool
@order_default_true
class OrderTrue:
inner: int
OrderTrue(1) < OrderTrue(2)
reveal_type(OrderTrue(1) < OrderTrue(2)) # revealed: bool
@order_default_true(order=False)
class OrderTrueOverwritten:
@@ -193,6 +193,36 @@ class OrderTrueOverwritten:
OrderTrueOverwritten(1) < OrderTrueOverwritten(2)
```
This also works for metaclass-based transformers:
```py
@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
```
And for base-class-based transformers:
```py
@dataclass_transform(order_default=True)
class OrderedModelBase: ...
class TestWithBase(OrderedModelBase):
inner: int
# TODO: No errors here, should reveal `bool`
# error: [too-many-positional-arguments]
# error: [too-many-positional-arguments]
# error: [unsupported-operator]
reveal_type(TestWithBase(1) < TestWithBase(2)) # revealed: Unknown
```
### `kw_only_default`
When provided, sets the default value for the `kw_only` parameter of `field()`.
@@ -202,7 +232,7 @@ from typing import dataclass_transform
from dataclasses import field
@dataclass_transform(kw_only_default=True)
def create_model(*, init=True): ...
def create_model(*, kw_only: bool = True): ...
@create_model()
class A:
name: str
@@ -213,27 +243,252 @@ a = A(name="Harry")
a = A("Harry")
```
TODO: This can be overridden by the call to the decorator function.
This can be overridden by setting `kw_only=False` when applying the decorator:
```py
from typing import dataclass_transform
@dataclass_transform(kw_only_default=True)
def create_model(*, kw_only: bool = True): ...
@create_model(kw_only=False)
class CustomerModel:
id: int
name: str
# TODO: Should not emit errors
# error: [missing-argument]
# error: [too-many-positional-arguments]
c = CustomerModel(1, "Harry")
```
### `field_specifiers`
This also works for metaclass-based transformers:
To do
```py
@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
```
And for base-class-based transformers:
```py
@dataclass_transform(kw_only_default=True)
class ModelBase: ...
class TestBase(ModelBase):
name: str
# TODO: This should be `(self: TestBase, *, name: str) -> None`
reveal_type(TestBase.__init__) # revealed: def __init__(self) -> None
```
### `frozen_default`
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]
```
Again, this can be overridden by setting `frozen=False` when applying the decorator:
```py
@create_model(frozen=False)
class MutableModel:
name: str
m = MutableModel(name="test")
m.name = "new" # No error
```
This also works for metaclass-based transformers:
```py
@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]
```
And for base-class-based transformers:
```py
@dataclass_transform(frozen_default=True)
class ModelBase: ...
class TestMeta(ModelBase):
name: str
# TODO: no error here
# error: [unknown-argument]
t = TestMeta(name="test")
# TODO: this should be an `invalid-assignment` error
t.name = "new"
```
### Combining parameters
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)
class OverridesAllParametersModel:
name: str
age: int
# Positional arguments are allowed:
model = OverridesAllParametersModel("test", 25)
# Mutation is allowed:
model.name = "new" # No error
# Comparison methods are generated:
model < model # No error
```
### Overwriting of default parameters on the dataclass-like class
#### Using function-based transformers
```py
from typing import dataclass_transform
@dataclass_transform(frozen_default=True)
def default_frozen_model(*, frozen: bool = True): ...
@default_frozen_model()
class Frozen:
name: str
f = Frozen(name="test")
f.name = "new" # error: [invalid-assignment]
@default_frozen_model(frozen=False)
class Mutable:
name: str
m = Mutable(name="test")
m.name = "new" # No error
```
#### Using metaclass-based transformers
```py
from typing import dataclass_transform
@dataclass_transform(frozen_default=True)
class DefaultFrozenMeta(type):
def __new__(
cls,
name,
bases,
namespace,
*,
frozen: bool = True,
): ...
class DefaultFrozenModel(metaclass=DefaultFrozenMeta): ...
class Frozen(DefaultFrozenModel):
name: str
f = Frozen(name="test")
f.name = "new" # error: [invalid-assignment]
class Mutable(DefaultFrozenModel, frozen=False):
name: str
m = Mutable(name="test")
# TODO: no error here
m.name = "new" # error: [invalid-assignment]
```
#### Using base-class-based transformers
```py
from typing import dataclass_transform
@dataclass_transform(frozen_default=True)
class DefaultFrozenModel:
def __init_subclass__(
cls,
*,
frozen: bool = True,
): ...
class Frozen(DefaultFrozenModel):
name: str
# TODO: no error here
# error: [unknown-argument]
f = Frozen(name="test")
# TODO: this should be an `invalid-assignment` error
f.name = "new"
class Mutable(DefaultFrozenModel, frozen=False):
name: str
# TODO: no error here
# error: [unknown-argument]
m = Mutable(name="test")
m.name = "new" # No error
```
## `field_specifiers`
The `field_specifiers` argument can be used to specify a list of functions that should be treated
similar to `dataclasses.field` for normal dataclasses.
The [`typing.dataclass_transform`] specification also allows classes (such as `dataclasses.Field`)
to be listed in `field_specifiers`, but it is currently unclear how this should work, and other type
checkers do not seem to support this either.
### Basic example
```py
from typing_extensions import dataclass_transform, Any
def fancy_field(*, init: bool = True, kw_only: 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(init=False)
name: str = fancy_field()
age: int | None = fancy_field(kw_only=True)
# TODO: Should be `(self: Person, name: str, *, age: int | None) -> None`
reveal_type(Person.__init__) # revealed: (self: Person, id: int = Any, name: str = Any, age: int | None = Any) -> None
# TODO: No error here
# error: [invalid-argument-type]
alice = Person("Alice", age=30)
reveal_type(alice.id) # revealed: int
reveal_type(alice.name) # revealed: str
reveal_type(alice.age) # revealed: int | None
```
## Overloaded dataclass-like decorators
@@ -322,4 +577,32 @@ D1(1.2) # error: [invalid-argument-type]
D2(1.2) # error: [invalid-argument-type]
```
### Use cases
#### Home Assistant
Home Assistant uses a pattern like this, where a `@dataclass`-decorated class inherits from a base
class that is itself a `dataclass`-like construct via a metaclass-based dataclass transformer. Make
sure that we recognize all fields in a hierarchy like this:
```py
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
```
[`typing.dataclass_transform`]: https://docs.python.org/3/library/typing.html#typing.dataclass_transform

View File

@@ -1010,7 +1010,6 @@ python-version = "3.10"
```py
from dataclasses import dataclass, field, KW_ONLY
from typing_extensions import reveal_type
@dataclass
class C:
@@ -1205,9 +1204,9 @@ python-version = "3.12"
from dataclasses import dataclass
from typing import Callable
from types import FunctionType
from ty_extensions import CallableTypeOf, TypeOf, static_assert, is_subtype_of, is_assignable_to
from ty_extensions import CallableTypeOf, TypeOf, static_assert, is_subtype_of, is_assignable_to, is_equivalent_to
@dataclass
@dataclass(order=True)
class C:
x: int
@@ -1234,8 +1233,20 @@ static_assert(not is_assignable_to(EquivalentPureCallableType, DunderInitType))
static_assert(is_subtype_of(DunderInitType, EquivalentFunctionLikeCallableType))
static_assert(is_assignable_to(DunderInitType, EquivalentFunctionLikeCallableType))
static_assert(not is_subtype_of(EquivalentFunctionLikeCallableType, DunderInitType))
static_assert(not is_assignable_to(EquivalentFunctionLikeCallableType, DunderInitType))
static_assert(is_subtype_of(EquivalentFunctionLikeCallableType, DunderInitType))
static_assert(is_assignable_to(EquivalentFunctionLikeCallableType, DunderInitType))
static_assert(is_equivalent_to(EquivalentFunctionLikeCallableType, DunderInitType))
static_assert(is_subtype_of(DunderInitType, FunctionType))
```
It should be possible to mock out synthesized methods:
```py
from unittest.mock import Mock
def test_c():
c = C(1)
c.__lt__ = Mock()
```

View File

@@ -0,0 +1,139 @@
# Legacy typevar creation diagnostics
The full tests for these features are in `generics/legacy/variables.md`.
<!-- snapshot-diagnostics -->
## Must have a name
```py
from typing import TypeVar
# error: [invalid-legacy-type-variable]
T = TypeVar()
```
## Name can't be given more than once
```py
from typing import TypeVar
# error: [invalid-legacy-type-variable]
T = TypeVar("T", name="T")
```
## Must be directly assigned to a variable
> A `TypeVar()` expression must always directly be assigned to a variable (it should not be used as
> part of a larger expression).
```py
from typing import TypeVar
T = TypeVar("T")
# error: [invalid-legacy-type-variable]
U: TypeVar = TypeVar("U")
# error: [invalid-legacy-type-variable]
tuple_with_typevar = ("foo", TypeVar("W"))
```
## `TypeVar` parameter must match variable name
> The argument to `TypeVar()` must be a string equal to the variable name to which it is assigned.
```py
from typing import TypeVar
# error: [invalid-legacy-type-variable]
T = TypeVar("Q")
```
## No variadic arguments
```py
from typing import TypeVar
types = (int, str)
# error: [invalid-legacy-type-variable]
T = TypeVar("T", *types)
# error: [invalid-legacy-type-variable]
S = TypeVar("S", **{"bound": int})
```
## Cannot have only one constraint
> `TypeVar` supports constraining parametric types to a fixed set of possible types...There should
> be at least two constraints, if any; specifying a single constraint is disallowed.
```py
from typing import TypeVar
# error: [invalid-legacy-type-variable]
T = TypeVar("T", int)
```
## Cannot have both bound and constraint
```py
from typing import TypeVar
# error: [invalid-legacy-type-variable]
T = TypeVar("T", int, str, bound=bytes)
```
## Cannot be both covariant and contravariant
> To facilitate the declaration of container types where covariant or contravariant type checking is
> acceptable, type variables accept keyword arguments `covariant=True` or `contravariant=True`. At
> most one of these may be passed.
```py
from typing import TypeVar
# error: [invalid-legacy-type-variable]
T = TypeVar("T", covariant=True, contravariant=True)
```
## Boolean parameters must be unambiguous
```py
from typing_extensions import TypeVar
def cond() -> bool:
return True
# error: [invalid-legacy-type-variable]
T = TypeVar("T", covariant=cond())
# error: [invalid-legacy-type-variable]
U = TypeVar("U", contravariant=cond())
# error: [invalid-legacy-type-variable]
V = TypeVar("V", infer_variance=cond())
```
## Invalid keyword arguments
```py
from typing import TypeVar
# error: [invalid-legacy-type-variable]
T = TypeVar("T", invalid_keyword=True)
```
## Invalid feature for this Python version
```toml
[environment]
python-version = "3.10"
```
```py
from typing import TypeVar
# error: [invalid-legacy-type-variable]
T = TypeVar("T", default=int)
```

View File

@@ -61,6 +61,35 @@ class DataFrame:
pass
```
## Class from different module with the same qualified name
`package/__init__.py`:
```py
from .foo import MyClass
def make_MyClass() -> MyClass:
return MyClass()
```
`package/foo.pyi`:
```pyi
class MyClass: ...
```
`package/foo.py`:
```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`"
return make_MyClass()
```
## Enum from different modules
```py
@@ -205,8 +234,8 @@ from typing import Protocol
import proto_a
import proto_b
# TODO should be error: [invalid-assignment] "Object of type `proto_b.Drawable` is not assignable to `proto_a.Drawable`"
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
```

View File

@@ -3,8 +3,6 @@
## Invalid syntax
```py
from typing_extensions import reveal_type
try:
print
except as e: # error: [invalid-syntax]

View File

@@ -108,7 +108,7 @@ reveal_type(generic_context(ExplicitInheritedGenericPartiallySpecializedExtraTyp
The type parameter can be specified explicitly:
```py
from typing import Generic, Literal, TypeVar
from typing_extensions import Generic, Literal, TypeVar
T = TypeVar("T")
@@ -195,7 +195,7 @@ reveal_type(WithDefault[str]()) # revealed: WithDefault[str, int]
We can infer the type parameter from a type context:
```py
from typing import Generic, TypeVar
from typing_extensions import Generic, TypeVar
T = TypeVar("T")
@@ -240,7 +240,7 @@ consistent with each other.
### `__new__` only
```py
from typing import Generic, TypeVar
from typing_extensions import Generic, TypeVar
T = TypeVar("T")
@@ -257,7 +257,7 @@ wrong_innards: C[int] = C("five")
### `__init__` only
```py
from typing import Generic, TypeVar
from typing_extensions import Generic, TypeVar
T = TypeVar("T")
@@ -273,7 +273,7 @@ wrong_innards: C[int] = C("five")
### Identical `__new__` and `__init__` signatures
```py
from typing import Generic, TypeVar
from typing_extensions import Generic, TypeVar
T = TypeVar("T")
@@ -292,7 +292,7 @@ wrong_innards: C[int] = C("five")
### Compatible `__new__` and `__init__` signatures
```py
from typing import Generic, TypeVar
from typing_extensions import Generic, TypeVar
T = TypeVar("T")
@@ -325,7 +325,7 @@ If either method comes from a generic base class, we don't currently use its inf
to specialize the class.
```py
from typing import Generic, TypeVar
from typing_extensions import Generic, TypeVar
T = TypeVar("T")
U = TypeVar("U")
@@ -344,7 +344,7 @@ reveal_type(D(1)) # revealed: D[int]
### Generic class inherits `__init__` from generic base class
```py
from typing import Generic, TypeVar
from typing_extensions import Generic, TypeVar
T = TypeVar("T")
U = TypeVar("U")
@@ -364,7 +364,7 @@ reveal_type(D(1, "str")) # revealed: D[int, str]
This is a specific example of the above, since it was reported specifically by a user.
```py
from typing import Generic, TypeVar
from typing_extensions import Generic, TypeVar
T = TypeVar("T")
U = TypeVar("U")
@@ -382,7 +382,7 @@ for `tuple`, so we use a different mechanism to make sure it has the right inher
context. But from the user's point of view, this is another example of the above.)
```py
from typing import Generic, TypeVar
from typing_extensions import Generic, TypeVar
T = TypeVar("T")
U = TypeVar("U")
@@ -403,7 +403,7 @@ python-version = "3.11"
```
```py
from typing import TypeVar, Sequence, Never
from typing_extensions import TypeVar, Sequence, Never
T = TypeVar("T")
@@ -421,7 +421,7 @@ def func8(t1: tuple[complex, list[int]], t2: tuple[int, *tuple[str, ...]], t3: t
### `__init__` is itself generic
```py
from typing import Generic, TypeVar
from typing_extensions import Generic, TypeVar
S = TypeVar("S")
T = TypeVar("T")
@@ -440,7 +440,7 @@ wrong_innards: C[int] = C("five", 1)
### Some `__init__` overloads only apply to certain specializations
```py
from typing import overload, Generic, TypeVar
from typing_extensions import overload, Generic, TypeVar
T = TypeVar("T")
@@ -480,7 +480,7 @@ C[None](12)
```py
from dataclasses import dataclass
from typing import Generic, TypeVar
from typing_extensions import Generic, TypeVar
T = TypeVar("T")
@@ -494,7 +494,7 @@ reveal_type(A(x=1)) # revealed: A[int]
### Class typevar has another typevar as a default
```py
from typing import Generic, TypeVar
from typing_extensions import Generic, TypeVar
T = TypeVar("T")
U = TypeVar("U", default=T)
@@ -515,7 +515,7 @@ When a generic subclass fills its superclass's type parameter with one of its ow
propagate through:
```py
from typing import Generic, TypeVar
from typing_extensions import Generic, TypeVar
T = TypeVar("T")
U = TypeVar("U")
@@ -549,7 +549,7 @@ scope for the method.
```py
from ty_extensions import generic_context
from typing import Generic, TypeVar
from typing_extensions import Generic, TypeVar
T = TypeVar("T")
U = TypeVar("U")
@@ -581,7 +581,7 @@ In a specialized generic alias, the specialization is applied to the attributes
class.
```py
from typing import Generic, TypeVar, Protocol
from typing_extensions import Generic, TypeVar, Protocol
T = TypeVar("T")
U = TypeVar("U")
@@ -639,7 +639,7 @@ reveal_type(d.method3().x) # revealed: int
When a method is overloaded, the specialization is applied to all overloads.
```py
from typing import overload, Generic, TypeVar
from typing_extensions import overload, Generic, TypeVar
S = TypeVar("S")
@@ -667,7 +667,7 @@ A class can use itself as the type parameter of one of its superclasses. (This i
Here, `Sub` is not a generic class, since it fills its superclass's type parameter (with itself).
```pyi
from typing import Generic, TypeVar
from typing_extensions import Generic, TypeVar
T = TypeVar("T")
@@ -682,7 +682,7 @@ reveal_type(Sub) # revealed: <class 'Sub'>
A similar case can work in a non-stub file, if forward references are stringified:
```py
from typing import Generic, TypeVar
from typing_extensions import Generic, TypeVar
T = TypeVar("T")
@@ -697,7 +697,7 @@ reveal_type(Sub) # revealed: <class 'Sub'>
In a non-stub file, without stringified forward references, this raises a `NameError`:
```py
from typing import Generic, TypeVar
from typing_extensions import Generic, TypeVar
T = TypeVar("T")
@@ -710,7 +710,7 @@ class Sub(Base[Sub]): ...
### Cyclic inheritance as a generic parameter
```pyi
from typing import Generic, TypeVar
from typing_extensions import Generic, TypeVar
T = TypeVar("T")
@@ -722,7 +722,7 @@ class Derived(list[Derived[T]], Generic[T]): ...
Inheritance that would result in a cyclic MRO is detected as an error.
```py
from typing import Generic, TypeVar
from typing_extensions import Generic, TypeVar
T = TypeVar("T")

View File

@@ -181,7 +181,6 @@ reveal_type(takes_homogeneous_tuple((42, 43))) # revealed: Literal[42, 43]
```py
from typing import TypeVar
from typing_extensions import reveal_type
T = TypeVar("T", bound=int)
@@ -200,7 +199,6 @@ reveal_type(f("string")) # revealed: Unknown
```py
from typing import TypeVar
from typing_extensions import reveal_type
T = TypeVar("T", int, None)
@@ -323,6 +321,9 @@ def union_param(x: T | None) -> T:
reveal_type(union_param("a")) # revealed: Literal["a"]
reveal_type(union_param(1)) # revealed: Literal[1]
reveal_type(union_param(None)) # revealed: Unknown
def _(x: int | None):
reveal_type(union_param(x)) # revealed: int
```
```py

View File

@@ -6,6 +6,8 @@ for both type variable syntaxes.
Unless otherwise specified, all quotations come from the [Generics] section of the typing spec.
Diagnostics for invalid type variables are snapshotted in `diagnostics/legacy_typevars.md`.
## Type variables
### Defining legacy type variables
@@ -24,7 +26,16 @@ reveal_type(T) # revealed: typing.TypeVar
reveal_type(T.__name__) # revealed: Literal["T"]
```
### Directly assigned to a variable
The typevar name can also be provided as a keyword argument:
```py
from typing import TypeVar
T = TypeVar(name="T")
reveal_type(T.__name__) # revealed: Literal["T"]
```
### Must be directly assigned to a variable
> A `TypeVar()` expression must always directly be assigned to a variable (it should not be used as
> part of a larger expression).
@@ -33,13 +44,24 @@ reveal_type(T.__name__) # revealed: Literal["T"]
from typing import TypeVar
T = TypeVar("T")
# TODO: no error
# error: [invalid-legacy-type-variable]
U: TypeVar = TypeVar("U")
# error: [invalid-legacy-type-variable] "A legacy `typing.TypeVar` must be immediately assigned to a variable"
# error: [invalid-type-form] "Function calls are not allowed in type expressions"
TestList = list[TypeVar("W")]
# error: [invalid-legacy-type-variable]
tuple_with_typevar = ("foo", TypeVar("W"))
reveal_type(tuple_with_typevar[1]) # revealed: TypeVar
```
```py
from typing_extensions import TypeVar
T = TypeVar("T")
# error: [invalid-legacy-type-variable]
U: TypeVar = TypeVar("U")
# error: [invalid-legacy-type-variable]
tuple_with_typevar = ("foo", TypeVar("W"))
reveal_type(tuple_with_typevar[1]) # revealed: TypeVar
```
### `TypeVar` parameter must match variable name
@@ -49,7 +71,7 @@ TestList = list[TypeVar("W")]
```py
from typing import TypeVar
# error: [invalid-legacy-type-variable] "The name of a legacy `typing.TypeVar` (`Q`) must match the name of the variable it is assigned to (`T`)"
# error: [invalid-legacy-type-variable]
T = TypeVar("Q")
```
@@ -66,6 +88,22 @@ T = TypeVar("T")
T = TypeVar("T")
```
### No variadic arguments
```py
from typing import TypeVar
types = (int, str)
# error: [invalid-legacy-type-variable]
T = TypeVar("T", *types)
reveal_type(T) # revealed: TypeVar
# error: [invalid-legacy-type-variable]
S = TypeVar("S", **{"bound": int})
reveal_type(S) # revealed: TypeVar
```
### Type variables with a default
Note that the `__default__` property is only available in Python ≥3.13.
@@ -91,6 +129,11 @@ reveal_type(S.__default__) # revealed: NoDefault
### Using other typevars as a default
```toml
[environment]
python-version = "3.13"
```
```py
from typing import Generic, TypeVar, Union
@@ -124,6 +167,15 @@ S = TypeVar("S")
reveal_type(S.__bound__) # revealed: None
```
The upper bound must be a valid type expression:
```py
from typing import TypedDict
# error: [invalid-type-form]
T = TypeVar("T", bound=TypedDict)
```
### Type variables with constraints
```py
@@ -138,6 +190,16 @@ S = TypeVar("S")
reveal_type(S.__constraints__) # revealed: tuple[()]
```
Constraints are not simplified relative to each other, even if one is a subtype of the other:
```py
T = TypeVar("T", int, bool)
reveal_type(T.__constraints__) # revealed: tuple[int, bool]
S = TypeVar("S", float, str)
reveal_type(S.__constraints__) # revealed: tuple[int | float, str]
```
### Cannot have only one constraint
> `TypeVar` supports constraining parametric types to a fixed set of possible types...There should
@@ -146,10 +208,19 @@ reveal_type(S.__constraints__) # revealed: tuple[()]
```py
from typing import TypeVar
# TODO: error: [invalid-type-variable-constraints]
# error: [invalid-legacy-type-variable]
T = TypeVar("T", int)
```
### Cannot have both bound and constraint
```py
from typing import TypeVar
# error: [invalid-legacy-type-variable]
T = TypeVar("T", int, str, bound=bytes)
```
### Cannot be both covariant and contravariant
> To facilitate the declaration of container types where covariant or contravariant type checking is
@@ -163,10 +234,10 @@ from typing import TypeVar
T = TypeVar("T", covariant=True, contravariant=True)
```
### Variance parameters must be unambiguous
### Boolean parameters must be unambiguous
```py
from typing import TypeVar
from typing_extensions import TypeVar
def cond() -> bool:
return True
@@ -176,6 +247,73 @@ T = TypeVar("T", covariant=cond())
# error: [invalid-legacy-type-variable]
U = TypeVar("U", contravariant=cond())
# error: [invalid-legacy-type-variable]
V = TypeVar("V", infer_variance=cond())
```
### Invalid keyword arguments
```py
from typing import TypeVar
# error: [invalid-legacy-type-variable]
T = TypeVar("T", invalid_keyword=True)
```
```pyi
from typing import TypeVar
# error: [invalid-legacy-type-variable]
T = TypeVar("T", invalid_keyword=True)
```
### Constructor signature versioning
#### For `typing.TypeVar`
```toml
[environment]
python-version = "3.10"
```
In a stub file, features from the latest supported Python version can be used on any version.
There's no need to require use of `typing_extensions.TypeVar` in a stub file, when the type checker
can understand the typevar definition perfectly well either way, and there can be no runtime error.
(Perhaps it's arguable whether this special case is worth it, but other type checkers do it, so we
maintain compatibility.)
```pyi
from typing import TypeVar
T = TypeVar("T", default=int)
```
But this raises an error in a non-stub file:
```py
from typing import TypeVar
# error: [invalid-legacy-type-variable]
T = TypeVar("T", default=int)
```
#### For `typing_extensions.TypeVar`
`typing_extensions.TypeVar` always supports the latest features, on any Python version.
```toml
[environment]
python-version = "3.10"
```
```py
from typing_extensions import TypeVar
T = TypeVar("T", default=int)
# TODO: should not error, should reveal `int`
# error: [unresolved-attribute]
reveal_type(T.__default__) # revealed: Unknown
```
## Callability
@@ -231,4 +369,96 @@ def constrained(x: T_constrained):
reveal_type(type(x)) # revealed: type[int] | type[str]
```
## Cycles
### Bounds and constraints
A typevar's bounds and constraints cannot be generic, cyclic or otherwise:
```py
from typing import Any, TypeVar
S = TypeVar("S")
# TODO: error
T = TypeVar("T", bound=list[S])
# TODO: error
U = TypeVar("U", list["T"], str)
# TODO: error
V = TypeVar("V", list["V"], str)
```
However, they are lazily evaluated and can cyclically refer to their own type:
```py
from typing import TypeVar, Generic
T = TypeVar("T", bound=list["G"])
class G(Generic[T]):
x: T
reveal_type(G[list[G]]().x) # revealed: list[G[Unknown]]
```
### Defaults
```toml
[environment]
python-version = "3.13"
```
Defaults can be generic, but can only refer to typevars from the same scope if they were defined
earlier in that scope:
```py
from typing import Generic, TypeVar
T = TypeVar("T")
U = TypeVar("U", default=T)
class C(Generic[T, U]):
x: T
y: U
reveal_type(C[int, str]().x) # revealed: int
reveal_type(C[int, str]().y) # revealed: str
reveal_type(C[int]().x) # revealed: int
reveal_type(C[int]().y) # revealed: int
# TODO: error
V = TypeVar("V", default="V")
class D(Generic[V]):
x: V
# TODO: we shouldn't leak a typevar like this in type inference
reveal_type(D().x) # revealed: V@D
```
## Regression
### Use of typevar with default inside a function body that binds it
```toml
[environment]
python-version = "3.13"
```
```py
from typing import Generic, TypeVar
_DataT = TypeVar("_DataT", bound=int, default=int)
class Event(Generic[_DataT]):
def __init__(self, data: _DataT) -> None:
self.data = data
def async_fire_internal(event_data: _DataT):
event: Event[_DataT] | None = None
event = Event(event_data)
```
[generics]: https://typing.python.org/en/latest/spec/generics.html

View File

@@ -286,6 +286,9 @@ def union_param[T](x: T | None) -> T:
reveal_type(union_param("a")) # revealed: Literal["a"]
reveal_type(union_param(1)) # revealed: Literal[1]
reveal_type(union_param(None)) # revealed: Unknown
def _(x: int | None):
reveal_type(union_param(x)) # revealed: int
```
```py

View File

@@ -5,8 +5,6 @@ all members available on a given type. This routine is used for autocomplete sug
## Basic functionality
<!-- snapshot-diagnostics -->
The `ty_extensions.all_members` and `ty_extensions.has_member` functions expose a Python-level API
that can be used to query which attributes `ide_support::all_members` understands as being available
on a given object. For example, all member functions of `str` are available on `"a"`. The Python API
@@ -45,10 +43,10 @@ The full list of all members is relatively long, but `reveal_type` can be used i
`all_members` to see them all:
```py
from typing_extensions import reveal_type
from ty_extensions import all_members
reveal_type(all_members("a")) # error: [revealed-type]
# revealed: tuple[Literal["__add__"], Literal["__annotations__"], Literal["__class__"], Literal["__contains__"], Literal["__delattr__"], Literal["__dict__"], Literal["__dir__"], Literal["__doc__"], Literal["__eq__"], Literal["__format__"], Literal["__ge__"], Literal["__getattribute__"], Literal["__getitem__"], Literal["__getnewargs__"], Literal["__gt__"], Literal["__hash__"], Literal["__init__"], Literal["__init_subclass__"], Literal["__iter__"], Literal["__le__"], Literal["__len__"], Literal["__lt__"], Literal["__mod__"], Literal["__module__"], Literal["__mul__"], Literal["__ne__"], Literal["__new__"], Literal["__reduce__"], Literal["__reduce_ex__"], Literal["__repr__"], Literal["__reversed__"], Literal["__rmul__"], Literal["__setattr__"], Literal["__sizeof__"], Literal["__str__"], Literal["__subclasshook__"], Literal["capitalize"], Literal["casefold"], Literal["center"], Literal["count"], Literal["encode"], Literal["endswith"], Literal["expandtabs"], Literal["find"], Literal["format"], Literal["format_map"], Literal["index"], Literal["isalnum"], Literal["isalpha"], Literal["isascii"], Literal["isdecimal"], Literal["isdigit"], Literal["isidentifier"], Literal["islower"], Literal["isnumeric"], Literal["isprintable"], Literal["isspace"], Literal["istitle"], Literal["isupper"], Literal["join"], Literal["ljust"], Literal["lower"], Literal["lstrip"], Literal["maketrans"], Literal["partition"], Literal["removeprefix"], Literal["removesuffix"], Literal["replace"], Literal["rfind"], Literal["rindex"], Literal["rjust"], Literal["rpartition"], Literal["rsplit"], Literal["rstrip"], Literal["split"], Literal["splitlines"], Literal["startswith"], Literal["strip"], Literal["swapcase"], Literal["title"], Literal["translate"], Literal["upper"], Literal["zfill"]]
reveal_type(all_members("a"))
```
## Kinds of types

View File

@@ -42,8 +42,6 @@ async def foo():
### No `__aiter__` method
```py
from typing_extensions import reveal_type
class NotAsyncIterable: ...
async def foo():
@@ -55,8 +53,6 @@ async def foo():
### Synchronously iterable, but not asynchronously iterable
```py
from typing_extensions import reveal_type
async def foo():
class Iterator:
def __next__(self) -> int:
@@ -74,8 +70,6 @@ async def foo():
### No `__anext__` method
```py
from typing_extensions import reveal_type
class NoAnext: ...
class AsyncIterable:
@@ -91,8 +85,6 @@ async def foo():
### Possibly missing `__anext__` method
```py
from typing_extensions import reveal_type
async def foo(flag: bool):
class PossiblyUnboundAnext:
if flag:
@@ -111,8 +103,6 @@ async def foo(flag: bool):
### Possibly missing `__aiter__` method
```py
from typing_extensions import reveal_type
async def foo(flag: bool):
class AsyncIterable:
async def __anext__(self) -> int:
@@ -131,8 +121,6 @@ async def foo(flag: bool):
### Wrong signature for `__aiter__`
```py
from typing_extensions import reveal_type
class AsyncIterator:
async def __anext__(self) -> int:
return 42
@@ -150,8 +138,6 @@ async def foo():
### Wrong signature for `__anext__`
```py
from typing_extensions import reveal_type
class AsyncIterator:
async def __anext__(self, arg: int) -> int: # wrong
return 42

View File

@@ -108,8 +108,6 @@ reveal_type(x)
<!-- snapshot-diagnostics -->
```py
from typing_extensions import reveal_type
def _(flag: bool):
class NotIterable:
if flag:
@@ -247,8 +245,7 @@ class StrIterator:
def f(x: IntIterator | StrIterator):
for a in x:
# TODO: this should be `int | str` (https://github.com/astral-sh/ty/issues/1089)
reveal_type(a) # revealed: int
reveal_type(a) # revealed: int | str
```
Most real-world iterable types use `Iterator` as the return annotation of their `__iter__` methods:
@@ -260,14 +257,11 @@ def g(
c: Literal["foo", b"bar"],
):
for x in a:
# TODO: should be `int | str` (https://github.com/astral-sh/ty/issues/1089)
reveal_type(x) # revealed: int
reveal_type(x) # revealed: int | str
for y in b:
# TODO: should be `str | int` (https://github.com/astral-sh/ty/issues/1089)
reveal_type(y) # revealed: str
reveal_type(y) # revealed: str | int
for z in c:
# TODO: should be `LiteralString | int` (https://github.com/astral-sh/ty/issues/1089)
reveal_type(z) # revealed: LiteralString
reveal_type(z) # revealed: LiteralString | int
```
## Union type as iterable where one union element has no `__iter__` method
@@ -275,8 +269,6 @@ def g(
<!-- snapshot-diagnostics -->
```py
from typing_extensions import reveal_type
class TestIter:
def __next__(self) -> int:
return 42
@@ -296,8 +288,6 @@ def _(flag: bool):
<!-- snapshot-diagnostics -->
```py
from typing_extensions import reveal_type
class TestIter:
def __next__(self) -> int:
return 42
@@ -374,8 +364,6 @@ def _(flag: bool):
<!-- snapshot-diagnostics -->
```py
from typing_extensions import reveal_type
class Iterator:
def __next__(self) -> int:
return 42
@@ -394,8 +382,6 @@ for x in Iterable():
<!-- snapshot-diagnostics -->
```py
from typing_extensions import reveal_type
class Bad:
def __iter__(self) -> int:
return 42
@@ -428,8 +414,6 @@ def _(flag: bool):
<!-- snapshot-diagnostics -->
```py
from typing_extensions import reveal_type
class Iterator1:
def __next__(self, extra_arg) -> int:
return 42
@@ -459,8 +443,6 @@ for y in Iterable2():
<!-- snapshot-diagnostics -->
```py
from typing_extensions import reveal_type
def _(flag: bool):
class Iterator:
def __next__(self) -> int:
@@ -513,8 +495,6 @@ def _(flag: bool):
<!-- snapshot-diagnostics -->
```py
from typing_extensions import reveal_type
class Iterator:
def __next__(self) -> int:
return 42
@@ -538,8 +518,6 @@ def _(flag1: bool, flag2: bool):
<!-- snapshot-diagnostics -->
```py
from typing_extensions import reveal_type
class Bad:
__getitem__: None = None
@@ -553,8 +531,6 @@ for x in Bad():
<!-- snapshot-diagnostics -->
```py
from typing_extensions import reveal_type
def _(flag: bool):
class CustomCallable:
if flag:
@@ -589,8 +565,6 @@ def _(flag: bool):
<!-- snapshot-diagnostics -->
```py
from typing_extensions import reveal_type
class Iterable:
# invalid because it will implicitly be passed an `int`
# by the interpreter
@@ -630,8 +604,6 @@ def _(flag: bool):
<!-- snapshot-diagnostics -->
```py
from typing_extensions import reveal_type
class Iterator:
def __next__(self) -> int:
return 42
@@ -667,8 +639,6 @@ def _(flag: bool):
<!-- snapshot-diagnostics -->
```py
from typing_extensions import reveal_type
def _(flag: bool):
class Iterator1:
if flag:
@@ -708,8 +678,6 @@ def _(flag: bool):
<!-- snapshot-diagnostics -->
```py
from typing_extensions import reveal_type
def _(flag: bool):
class Iterable1:
if flag:
@@ -741,8 +709,6 @@ def _(flag: bool):
<!-- snapshot-diagnostics -->
```py
from typing_extensions import reveal_type
class Iterator:
def __next__(self) -> bytes:
return b"foo"

View File

@@ -241,8 +241,6 @@ find a union type in a class's bases, we infer the class's `__mro__` as being
`[<class>, Unknown, object]`, the same as for MROs that cause errors at runtime.
```py
from typing_extensions import reveal_type
def returns_bool() -> bool:
return True
@@ -391,8 +389,6 @@ class BadSub2(Bad2()): ... # error: [invalid-base]
<!-- snapshot-diagnostics -->
```py
from typing_extensions import reveal_type
class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`"
reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]

View File

@@ -281,7 +281,7 @@ def _(x: Foo | Bar, flag: bool) -> None:
The `TypeIs` type remains effective across generic boundaries:
```py
from typing_extensions import TypeVar, reveal_type
from typing_extensions import TypeVar
T = TypeVar("T")

View File

@@ -197,9 +197,9 @@ from typing_extensions import TypeAliasType, TypeVar
T = TypeVar("T")
IntAnd = TypeAliasType("IntAndT", tuple[int, T], type_params=(T,))
IntAndT = TypeAliasType("IntAndT", tuple[int, T], type_params=(T,))
def f(x: IntAnd[str]) -> None:
def f(x: IntAndT[str]) -> None:
reveal_type(x) # revealed: @Todo(Generic manual PEP-695 type alias)
```

View File

@@ -347,7 +347,7 @@ python-version = "3.12"
```
```py
from typing_extensions import Protocol, reveal_type
from typing_extensions import Protocol
# error: [call-non-callable]
reveal_type(Protocol()) # revealed: Unknown
@@ -381,9 +381,7 @@ And as a corollary, `type[MyProtocol]` can also be called:
```py
def f(x: type[MyProtocol]):
# TODO: add a `reveal_type` call here once it's no longer a `Todo` type
# (which doesn't work well with snapshots)
x()
reveal_type(x()) # revealed: @Todo(type[T] for protocols)
```
## Members of a protocol
@@ -534,7 +532,7 @@ python-version = "3.9"
```py
import sys
from typing_extensions import Protocol, get_protocol_members, reveal_type
from typing_extensions import Protocol, get_protocol_members
class Foo(Protocol):
if sys.version_info >= (3, 10):
@@ -617,11 +615,10 @@ static_assert(is_assignable_to(Foo, HasX))
static_assert(not is_subtype_of(Foo, HasXY))
static_assert(not is_assignable_to(Foo, HasXY))
# TODO: these should pass
static_assert(not is_subtype_of(HasXIntSub, HasX)) # error: [static-assert-error]
static_assert(not is_assignable_to(HasXIntSub, HasX)) # error: [static-assert-error]
static_assert(not is_subtype_of(HasX, HasXIntSub)) # error: [static-assert-error]
static_assert(not is_assignable_to(HasX, HasXIntSub)) # error: [static-assert-error]
static_assert(not is_subtype_of(HasXIntSub, HasX))
static_assert(not is_assignable_to(HasXIntSub, HasX))
static_assert(not is_subtype_of(HasX, HasXIntSub))
static_assert(not is_assignable_to(HasX, HasXIntSub))
class FooSub(Foo): ...
@@ -2291,10 +2288,9 @@ class MethodPUnrelated(Protocol):
static_assert(is_subtype_of(MethodPSub, MethodPSuper))
# TODO: these should pass
static_assert(not is_assignable_to(MethodPUnrelated, MethodPSuper)) # error: [static-assert-error]
static_assert(not is_assignable_to(MethodPSuper, MethodPUnrelated)) # error: [static-assert-error]
static_assert(not is_assignable_to(MethodPSuper, MethodPSub)) # error: [static-assert-error]
static_assert(not is_assignable_to(MethodPUnrelated, MethodPSuper))
static_assert(not is_assignable_to(MethodPSuper, MethodPUnrelated))
static_assert(not is_assignable_to(MethodPSuper, MethodPSub))
```
## Subtyping between protocols with method members and protocols with non-method members
@@ -2353,8 +2349,7 @@ And for the same reason, they are never assignable to attribute members (which a
class Attribute(Protocol):
f: Callable[[], bool]
# TODO: should pass
static_assert(not is_assignable_to(Method, Attribute)) # error: [static-assert-error]
static_assert(not is_assignable_to(Method, Attribute))
```
Protocols with attribute members, meanwhile, cannot be assigned to protocols with method members,
@@ -2363,9 +2358,8 @@ this is not true for attribute members. The same principle also applies for prot
members
```py
# TODO: this should pass
static_assert(not is_assignable_to(PropertyBool, Method)) # error: [static-assert-error]
static_assert(not is_assignable_to(Attribute, Method)) # error: [static-assert-error]
static_assert(not is_assignable_to(PropertyBool, Method))
static_assert(not is_assignable_to(Attribute, Method))
```
But an exception to this rule is if an attribute member is marked as `ClassVar`, as this guarantees
@@ -2384,9 +2378,8 @@ static_assert(is_assignable_to(ClassVarAttribute, Method))
class ClassVarAttributeBad(Protocol):
f: ClassVar[Callable[[], str]]
# TODO: these should pass:
static_assert(not is_subtype_of(ClassVarAttributeBad, Method)) # error: [static-assert-error]
static_assert(not is_assignable_to(ClassVarAttributeBad, Method)) # error: [static-assert-error]
static_assert(not is_subtype_of(ClassVarAttributeBad, Method))
static_assert(not is_assignable_to(ClassVarAttributeBad, Method))
```
## Narrowing of protocols
@@ -2398,7 +2391,7 @@ By default, a protocol class cannot be used as the second argument to `isinstanc
type inside these branches (this matches the behavior of other type checkers):
```py
from typing_extensions import Protocol, reveal_type
from typing_extensions import Protocol
class HasX(Protocol):
x: int
@@ -2707,9 +2700,8 @@ class RecursiveNonFullyStatic(Protocol):
parent: RecursiveNonFullyStatic
x: Any
# TODO: these should pass, once we take into account types of members
static_assert(not is_subtype_of(RecursiveFullyStatic, RecursiveNonFullyStatic)) # error: [static-assert-error]
static_assert(not is_subtype_of(RecursiveNonFullyStatic, RecursiveFullyStatic)) # error: [static-assert-error]
static_assert(not is_subtype_of(RecursiveFullyStatic, RecursiveNonFullyStatic))
static_assert(not is_subtype_of(RecursiveNonFullyStatic, RecursiveFullyStatic))
static_assert(is_assignable_to(RecursiveNonFullyStatic, RecursiveNonFullyStatic))
static_assert(is_assignable_to(RecursiveFullyStatic, RecursiveNonFullyStatic))
@@ -2727,9 +2719,7 @@ class RecursiveOptionalParent(Protocol):
static_assert(is_assignable_to(RecursiveOptionalParent, RecursiveOptionalParent))
# Due to invariance of mutable attribute members, neither is assignable to the other
#
# TODO: should pass
static_assert(not is_assignable_to(RecursiveNonFullyStatic, RecursiveOptionalParent)) # error: [static-assert-error]
static_assert(not is_assignable_to(RecursiveNonFullyStatic, RecursiveOptionalParent))
static_assert(not is_assignable_to(RecursiveOptionalParent, RecursiveNonFullyStatic))
class Other(Protocol):

View File

@@ -339,7 +339,7 @@ class A: ...
def f(x: A):
# TODO: no error
# error: [invalid-assignment] "Object of type `mdtest_snippet.A | mdtest_snippet.A` is not assignable to `mdtest_snippet.A`"
# error: [invalid-assignment] "Object of type `mdtest_snippet.A @ src/mdtest_snippet.py:12 | mdtest_snippet.A @ src/mdtest_snippet.py:13` is not assignable to `mdtest_snippet.A @ src/mdtest_snippet.py:13`"
x = A()
```

View File

@@ -1,43 +0,0 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: all_members.md - List all members - Basic functionality
mdtest path: crates/ty_python_semantic/resources/mdtest/ide_support/all_members.md
---
# Python source files
## mdtest_snippet.py
```
1 | from ty_extensions import static_assert, has_member
2 |
3 | static_assert(has_member("a", "replace"))
4 | static_assert(has_member("a", "startswith"))
5 | static_assert(has_member("a", "isupper"))
6 | static_assert(has_member("a", "__add__"))
7 | static_assert(has_member("a", "__gt__"))
8 | static_assert(has_member("a", "__doc__"))
9 | static_assert(has_member("a", "__repr__"))
10 | static_assert(not has_member("a", "non_existent"))
11 | from typing_extensions import reveal_type
12 | from ty_extensions import all_members
13 |
14 | reveal_type(all_members("a")) # error: [revealed-type]
```
# Diagnostics
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:14:13
|
12 | from ty_extensions import all_members
13 |
14 | reveal_type(all_members("a")) # error: [revealed-type]
| ^^^^^^^^^^^^^^^^ `tuple[Literal["__add__"], Literal["__annotations__"], Literal["__class__"], Literal["__contains__"], Literal["__delattr__"], Literal["__dict__"], Literal["__dir__"], Literal["__doc__"], Literal["__eq__"], Literal["__format__"], Literal["__ge__"], Literal["__getattribute__"], Literal["__getitem__"], Literal["__getnewargs__"], Literal["__gt__"], Literal["__hash__"], Literal["__init__"], Literal["__init_subclass__"], Literal["__iter__"], Literal["__le__"], Literal["__len__"], Literal["__lt__"], Literal["__mod__"], Literal["__module__"], Literal["__mul__"], Literal["__ne__"], Literal["__new__"], Literal["__reduce__"], Literal["__reduce_ex__"], Literal["__repr__"], Literal["__reversed__"], Literal["__rmul__"], Literal["__setattr__"], Literal["__sizeof__"], Literal["__str__"], Literal["__subclasshook__"], Literal["capitalize"], Literal["casefold"], Literal["center"], Literal["count"], Literal["encode"], Literal["endswith"], Literal["expandtabs"], Literal["find"], Literal["format"], Literal["format_map"], Literal["index"], Literal["isalnum"], Literal["isalpha"], Literal["isascii"], Literal["isdecimal"], Literal["isdigit"], Literal["isidentifier"], Literal["islower"], Literal["isnumeric"], Literal["isprintable"], Literal["isspace"], Literal["istitle"], Literal["isupper"], Literal["join"], Literal["ljust"], Literal["lower"], Literal["lstrip"], Literal["maketrans"], Literal["partition"], Literal["removeprefix"], Literal["removesuffix"], Literal["replace"], Literal["rfind"], Literal["rindex"], Literal["rjust"], Literal["rpartition"], Literal["rsplit"], Literal["rstrip"], Literal["split"], Literal["splitlines"], Literal["startswith"], Literal["strip"], Literal["swapcase"], Literal["title"], Literal["translate"], Literal["upper"], Literal["zfill"]]`
|
```

View File

@@ -12,41 +12,27 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/async_for.md
## mdtest_snippet.py
```
1 | from typing_extensions import reveal_type
1 | class NotAsyncIterable: ...
2 |
3 | class NotAsyncIterable: ...
4 |
5 | async def foo():
6 | # error: [not-iterable] "Object of type `NotAsyncIterable` is not async-iterable"
7 | async for x in NotAsyncIterable():
8 | reveal_type(x) # revealed: Unknown
3 | async def foo():
4 | # error: [not-iterable] "Object of type `NotAsyncIterable` is not async-iterable"
5 | async for x in NotAsyncIterable():
6 | reveal_type(x) # revealed: Unknown
```
# Diagnostics
```
error[not-iterable]: Object of type `NotAsyncIterable` is not async-iterable
--> src/mdtest_snippet.py:7:20
--> src/mdtest_snippet.py:5:20
|
5 | async def foo():
6 | # error: [not-iterable] "Object of type `NotAsyncIterable` is not async-iterable"
7 | async for x in NotAsyncIterable():
3 | async def foo():
4 | # error: [not-iterable] "Object of type `NotAsyncIterable` is not async-iterable"
5 | async for x in NotAsyncIterable():
| ^^^^^^^^^^^^^^^^^^
8 | reveal_type(x) # revealed: Unknown
6 | reveal_type(x) # revealed: Unknown
|
info: It has no `__aiter__` method
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:8:21
|
6 | # error: [not-iterable] "Object of type `NotAsyncIterable` is not async-iterable"
7 | async for x in NotAsyncIterable():
8 | reveal_type(x) # revealed: Unknown
| ^ `Unknown`
|
```

View File

@@ -12,45 +12,31 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/async_for.md
## mdtest_snippet.py
```
1 | from typing_extensions import reveal_type
1 | class NoAnext: ...
2 |
3 | class NoAnext: ...
4 |
5 | class AsyncIterable:
6 | def __aiter__(self) -> NoAnext:
7 | return NoAnext()
8 |
9 | async def foo():
10 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
11 | async for x in AsyncIterable():
12 | reveal_type(x) # revealed: Unknown
3 | class AsyncIterable:
4 | def __aiter__(self) -> NoAnext:
5 | return NoAnext()
6 |
7 | async def foo():
8 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
9 | async for x in AsyncIterable():
10 | reveal_type(x) # revealed: Unknown
```
# Diagnostics
```
error[not-iterable]: Object of type `AsyncIterable` is not async-iterable
--> src/mdtest_snippet.py:11:20
--> src/mdtest_snippet.py:9:20
|
9 | async def foo():
10 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
11 | async for x in AsyncIterable():
7 | async def foo():
8 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
9 | async for x in AsyncIterable():
| ^^^^^^^^^^^^^^^
12 | reveal_type(x) # revealed: Unknown
10 | reveal_type(x) # revealed: Unknown
|
info: Its `__aiter__` method returns an object of type `NoAnext`, which has no `__anext__` method
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:12:21
|
10 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
11 | async for x in AsyncIterable():
12 | reveal_type(x) # revealed: Unknown
| ^ `Unknown`
|
```

View File

@@ -12,47 +12,33 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/async_for.md
## mdtest_snippet.py
```
1 | from typing_extensions import reveal_type
2 |
3 | async def foo(flag: bool):
4 | class AsyncIterable:
5 | async def __anext__(self) -> int:
6 | return 42
7 |
8 | class PossiblyUnboundAiter:
9 | if flag:
10 | def __aiter__(self) -> AsyncIterable:
11 | return AsyncIterable()
12 |
13 | # error: "Object of type `PossiblyUnboundAiter` may not be async-iterable"
14 | async for x in PossiblyUnboundAiter():
15 | reveal_type(x) # revealed: int
1 | async def foo(flag: bool):
2 | class AsyncIterable:
3 | async def __anext__(self) -> int:
4 | return 42
5 |
6 | class PossiblyUnboundAiter:
7 | if flag:
8 | def __aiter__(self) -> AsyncIterable:
9 | return AsyncIterable()
10 |
11 | # error: "Object of type `PossiblyUnboundAiter` may not be async-iterable"
12 | async for x in PossiblyUnboundAiter():
13 | reveal_type(x) # revealed: int
```
# Diagnostics
```
error[not-iterable]: Object of type `PossiblyUnboundAiter` may not be async-iterable
--> src/mdtest_snippet.py:14:20
--> src/mdtest_snippet.py:12:20
|
13 | # error: "Object of type `PossiblyUnboundAiter` may not be async-iterable"
14 | async for x in PossiblyUnboundAiter():
11 | # error: "Object of type `PossiblyUnboundAiter` may not be async-iterable"
12 | async for x in PossiblyUnboundAiter():
| ^^^^^^^^^^^^^^^^^^^^^^
15 | reveal_type(x) # revealed: int
13 | reveal_type(x) # revealed: int
|
info: Its `__aiter__` attribute (with type `bound method PossiblyUnboundAiter.__aiter__() -> AsyncIterable`) may not be callable
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:15:21
|
13 | # error: "Object of type `PossiblyUnboundAiter` may not be async-iterable"
14 | async for x in PossiblyUnboundAiter():
15 | reveal_type(x) # revealed: int
| ^ `int`
|
```

View File

@@ -12,47 +12,33 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/async_for.md
## mdtest_snippet.py
```
1 | from typing_extensions import reveal_type
2 |
3 | async def foo(flag: bool):
4 | class PossiblyUnboundAnext:
5 | if flag:
6 | async def __anext__(self) -> int:
7 | return 42
8 |
9 | class AsyncIterable:
10 | def __aiter__(self) -> PossiblyUnboundAnext:
11 | return PossiblyUnboundAnext()
12 |
13 | # error: [not-iterable] "Object of type `AsyncIterable` may not be async-iterable"
14 | async for x in AsyncIterable():
15 | reveal_type(x) # revealed: int
1 | async def foo(flag: bool):
2 | class PossiblyUnboundAnext:
3 | if flag:
4 | async def __anext__(self) -> int:
5 | return 42
6 |
7 | class AsyncIterable:
8 | def __aiter__(self) -> PossiblyUnboundAnext:
9 | return PossiblyUnboundAnext()
10 |
11 | # error: [not-iterable] "Object of type `AsyncIterable` may not be async-iterable"
12 | async for x in AsyncIterable():
13 | reveal_type(x) # revealed: int
```
# Diagnostics
```
error[not-iterable]: Object of type `AsyncIterable` may not be async-iterable
--> src/mdtest_snippet.py:14:20
--> src/mdtest_snippet.py:12:20
|
13 | # error: [not-iterable] "Object of type `AsyncIterable` may not be async-iterable"
14 | async for x in AsyncIterable():
11 | # error: [not-iterable] "Object of type `AsyncIterable` may not be async-iterable"
12 | async for x in AsyncIterable():
| ^^^^^^^^^^^^^^^
15 | reveal_type(x) # revealed: int
13 | reveal_type(x) # revealed: int
|
info: Its `__aiter__` method returns an object of type `PossiblyUnboundAnext`, which may not have a `__anext__` method
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:15:21
|
13 | # error: [not-iterable] "Object of type `AsyncIterable` may not be async-iterable"
14 | async for x in AsyncIterable():
15 | reveal_type(x) # revealed: int
| ^ `int`
|
```

View File

@@ -12,46 +12,32 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/async_for.md
## mdtest_snippet.py
```
1 | from typing_extensions import reveal_type
2 |
3 | async def foo():
4 | class Iterator:
5 | def __next__(self) -> int:
6 | return 42
7 |
8 | class Iterable:
9 | def __iter__(self) -> Iterator:
10 | return Iterator()
11 |
12 | # error: [not-iterable] "Object of type `Iterator` is not async-iterable"
13 | async for x in Iterator():
14 | reveal_type(x) # revealed: Unknown
1 | async def foo():
2 | class Iterator:
3 | def __next__(self) -> int:
4 | return 42
5 |
6 | class Iterable:
7 | def __iter__(self) -> Iterator:
8 | return Iterator()
9 |
10 | # error: [not-iterable] "Object of type `Iterator` is not async-iterable"
11 | async for x in Iterator():
12 | reveal_type(x) # revealed: Unknown
```
# Diagnostics
```
error[not-iterable]: Object of type `Iterator` is not async-iterable
--> src/mdtest_snippet.py:13:20
--> src/mdtest_snippet.py:11:20
|
12 | # error: [not-iterable] "Object of type `Iterator` is not async-iterable"
13 | async for x in Iterator():
10 | # error: [not-iterable] "Object of type `Iterator` is not async-iterable"
11 | async for x in Iterator():
| ^^^^^^^^^^
14 | reveal_type(x) # revealed: Unknown
12 | reveal_type(x) # revealed: Unknown
|
info: It has no `__aiter__` method
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:14:21
|
12 | # error: [not-iterable] "Object of type `Iterator` is not async-iterable"
13 | async for x in Iterator():
14 | reveal_type(x) # revealed: Unknown
| ^ `Unknown`
|
```

View File

@@ -12,48 +12,34 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/async_for.md
## mdtest_snippet.py
```
1 | from typing_extensions import reveal_type
2 |
3 | class AsyncIterator:
4 | async def __anext__(self, arg: int) -> int: # wrong
5 | return 42
6 |
7 | class AsyncIterable:
8 | def __aiter__(self) -> AsyncIterator:
9 | return AsyncIterator()
10 |
11 | async def foo():
12 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
13 | async for x in AsyncIterable():
14 | reveal_type(x) # revealed: int
1 | class AsyncIterator:
2 | async def __anext__(self, arg: int) -> int: # wrong
3 | return 42
4 |
5 | class AsyncIterable:
6 | def __aiter__(self) -> AsyncIterator:
7 | return AsyncIterator()
8 |
9 | async def foo():
10 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
11 | async for x in AsyncIterable():
12 | reveal_type(x) # revealed: int
```
# Diagnostics
```
error[not-iterable]: Object of type `AsyncIterable` is not async-iterable
--> src/mdtest_snippet.py:13:20
--> src/mdtest_snippet.py:11:20
|
11 | async def foo():
12 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
13 | async for x in AsyncIterable():
9 | async def foo():
10 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
11 | async for x in AsyncIterable():
| ^^^^^^^^^^^^^^^
14 | reveal_type(x) # revealed: int
12 | reveal_type(x) # revealed: int
|
info: Its `__aiter__` method returns an object of type `AsyncIterator`, which has an invalid `__anext__` method
info: Expected signature for `__anext__` is `def __anext__(self): ...`
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:14:21
|
12 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
13 | async for x in AsyncIterable():
14 | reveal_type(x) # revealed: int
| ^ `int`
|
```

View File

@@ -12,48 +12,34 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/async_for.md
## mdtest_snippet.py
```
1 | from typing_extensions import reveal_type
2 |
3 | class AsyncIterator:
4 | async def __anext__(self) -> int:
5 | return 42
6 |
7 | class AsyncIterable:
8 | def __aiter__(self, arg: int) -> AsyncIterator: # wrong
9 | return AsyncIterator()
10 |
11 | async def foo():
12 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
13 | async for x in AsyncIterable():
14 | reveal_type(x) # revealed: int
1 | class AsyncIterator:
2 | async def __anext__(self) -> int:
3 | return 42
4 |
5 | class AsyncIterable:
6 | def __aiter__(self, arg: int) -> AsyncIterator: # wrong
7 | return AsyncIterator()
8 |
9 | async def foo():
10 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
11 | async for x in AsyncIterable():
12 | reveal_type(x) # revealed: int
```
# Diagnostics
```
error[not-iterable]: Object of type `AsyncIterable` is not async-iterable
--> src/mdtest_snippet.py:13:20
--> src/mdtest_snippet.py:11:20
|
11 | async def foo():
12 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
13 | async for x in AsyncIterable():
9 | async def foo():
10 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
11 | async for x in AsyncIterable():
| ^^^^^^^^^^^^^^^
14 | reveal_type(x) # revealed: int
12 | reveal_type(x) # revealed: int
|
info: Its `__aiter__` method has an invalid signature
info: Expected signature `def __aiter__(self): ...`
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:14:21
|
12 | # error: [not-iterable] "Object of type `AsyncIterable` is not async-iterable"
13 | async for x in AsyncIterable():
14 | reveal_type(x) # revealed: int
| ^ `int`
|
```

View File

@@ -13,86 +13,71 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/dataclasses/dataclasses.
```
1 | from dataclasses import dataclass, field, KW_ONLY
2 | from typing_extensions import reveal_type
3 |
4 | @dataclass
5 | class C:
6 | x: int
7 | _: KW_ONLY
8 | y: str
9 |
10 | reveal_type(C.__init__) # revealed: (self: C, x: int, *, y: str) -> None
11 |
12 | # error: [missing-argument]
13 | # error: [too-many-positional-arguments]
14 | C(3, "")
15 |
16 | C(3, y="")
17 | @dataclass
18 | class Fails: # error: [duplicate-kw-only]
19 | a: int
20 | b: KW_ONLY
21 | c: str
22 | d: KW_ONLY
23 | e: bytes
24 |
25 | reveal_type(Fails.__init__) # revealed: (self: Fails, a: int, *, c: str, e: bytes) -> None
26 | def flag() -> bool:
27 | return True
28 |
29 | @dataclass
30 | class D: # error: [duplicate-kw-only]
31 | x: int
32 | _1: KW_ONLY
33 |
34 | if flag():
35 | y: str
36 | _2: KW_ONLY
37 | z: float
38 | from dataclasses import dataclass, KW_ONLY
39 |
40 | @dataclass
41 | class D:
42 | x: int
43 | _: KW_ONLY
44 | y: str
45 |
46 | @dataclass
47 | class E(D):
48 | z: bytes
49 |
50 | # This should work: x=1 (positional), z=b"foo" (positional), y="foo" (keyword-only)
51 | E(1, b"foo", y="foo")
52 |
53 | reveal_type(E.__init__) # revealed: (self: E, x: int, z: bytes, *, y: str) -> None
2 |
3 | @dataclass
4 | class C:
5 | x: int
6 | _: KW_ONLY
7 | y: str
8 |
9 | reveal_type(C.__init__) # revealed: (self: C, x: int, *, y: str) -> None
10 |
11 | # error: [missing-argument]
12 | # error: [too-many-positional-arguments]
13 | C(3, "")
14 |
15 | C(3, y="")
16 | @dataclass
17 | class Fails: # error: [duplicate-kw-only]
18 | a: int
19 | b: KW_ONLY
20 | c: str
21 | d: KW_ONLY
22 | e: bytes
23 |
24 | reveal_type(Fails.__init__) # revealed: (self: Fails, a: int, *, c: str, e: bytes) -> None
25 | def flag() -> bool:
26 | return True
27 |
28 | @dataclass
29 | class D: # error: [duplicate-kw-only]
30 | x: int
31 | _1: KW_ONLY
32 |
33 | if flag():
34 | y: str
35 | _2: KW_ONLY
36 | z: float
37 | from dataclasses import dataclass, KW_ONLY
38 |
39 | @dataclass
40 | class D:
41 | x: int
42 | _: KW_ONLY
43 | y: str
44 |
45 | @dataclass
46 | class E(D):
47 | z: bytes
48 |
49 | # This should work: x=1 (positional), z=b"foo" (positional), y="foo" (keyword-only)
50 | E(1, b"foo", y="foo")
51 |
52 | reveal_type(E.__init__) # revealed: (self: E, x: int, z: bytes, *, y: str) -> None
```
# Diagnostics
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:10:13
|
8 | y: str
9 |
10 | reveal_type(C.__init__) # revealed: (self: C, x: int, *, y: str) -> None
| ^^^^^^^^^^ `(self: C, x: int, *, y: str) -> None`
11 |
12 | # error: [missing-argument]
|
```
```
error[missing-argument]: No argument provided for required parameter `y`
--> src/mdtest_snippet.py:14:1
--> src/mdtest_snippet.py:13:1
|
12 | # error: [missing-argument]
13 | # error: [too-many-positional-arguments]
14 | C(3, "")
11 | # error: [missing-argument]
12 | # error: [too-many-positional-arguments]
13 | C(3, "")
| ^^^^^^^^
15 |
16 | C(3, y="")
14 |
15 | C(3, y="")
|
info: rule `missing-argument` is enabled by default
@@ -100,14 +85,14 @@ info: rule `missing-argument` is enabled by default
```
error[too-many-positional-arguments]: Too many positional arguments: expected 1, got 2
--> src/mdtest_snippet.py:14:6
--> src/mdtest_snippet.py:13:6
|
12 | # error: [missing-argument]
13 | # error: [too-many-positional-arguments]
14 | C(3, "")
11 | # error: [missing-argument]
12 | # error: [too-many-positional-arguments]
13 | C(3, "")
| ^^
15 |
16 | C(3, y="")
14 |
15 | C(3, y="")
|
info: rule `too-many-positional-arguments` is enabled by default
@@ -115,57 +100,31 @@ info: rule `too-many-positional-arguments` is enabled by default
```
error[duplicate-kw-only]: Dataclass has more than one field annotated with `KW_ONLY`
--> src/mdtest_snippet.py:18:7
--> src/mdtest_snippet.py:17:7
|
16 | C(3, y="")
17 | @dataclass
18 | class Fails: # error: [duplicate-kw-only]
15 | C(3, y="")
16 | @dataclass
17 | class Fails: # error: [duplicate-kw-only]
| ^^^^^
19 | a: int
20 | b: KW_ONLY
18 | a: int
19 | b: KW_ONLY
|
info: `KW_ONLY` fields: `b`, `d`
info: rule `duplicate-kw-only` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:25:13
|
23 | e: bytes
24 |
25 | reveal_type(Fails.__init__) # revealed: (self: Fails, a: int, *, c: str, e: bytes) -> None
| ^^^^^^^^^^^^^^ `(self: Fails, a: int, *, c: str, e: bytes) -> None`
26 | def flag() -> bool:
27 | return True
|
```
```
error[duplicate-kw-only]: Dataclass has more than one field annotated with `KW_ONLY`
--> src/mdtest_snippet.py:30:7
--> src/mdtest_snippet.py:29:7
|
29 | @dataclass
30 | class D: # error: [duplicate-kw-only]
28 | @dataclass
29 | class D: # error: [duplicate-kw-only]
| ^
31 | x: int
32 | _1: KW_ONLY
30 | x: int
31 | _1: KW_ONLY
|
info: `KW_ONLY` fields: `_1`, `_2`
info: rule `duplicate-kw-only` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:53:13
|
51 | E(1, b"foo", y="foo")
52 |
53 | reveal_type(E.__init__) # revealed: (self: E, x: int, z: bytes, *, y: str) -> None
| ^^^^^^^^^^ `(self: E, x: int, z: bytes, *, y: str) -> None`
|
```

View File

@@ -12,44 +12,30 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
## mdtest_snippet.py
```
1 | from typing_extensions import reveal_type
2 |
3 | class Iterable:
4 | # invalid because it will implicitly be passed an `int`
5 | # by the interpreter
6 | def __getitem__(self, key: str) -> int:
7 | return 42
8 |
9 | # error: [not-iterable]
10 | for x in Iterable():
11 | reveal_type(x) # revealed: int
1 | class Iterable:
2 | # invalid because it will implicitly be passed an `int`
3 | # by the interpreter
4 | def __getitem__(self, key: str) -> int:
5 | return 42
6 |
7 | # error: [not-iterable]
8 | for x in Iterable():
9 | reveal_type(x) # revealed: int
```
# Diagnostics
```
error[not-iterable]: Object of type `Iterable` is not iterable
--> src/mdtest_snippet.py:10:10
|
9 | # error: [not-iterable]
10 | for x in Iterable():
| ^^^^^^^^^^
11 | reveal_type(x) # revealed: int
|
--> src/mdtest_snippet.py:8:10
|
7 | # error: [not-iterable]
8 | for x in Iterable():
| ^^^^^^^^^^
9 | reveal_type(x) # revealed: int
|
info: It has no `__iter__` method and its `__getitem__` method has an incorrect signature for the old-style iteration protocol
info: `__getitem__` must be at least as permissive as `def __getitem__(self, key: int): ...` to satisfy the old-style iteration protocol
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:11:17
|
9 | # error: [not-iterable]
10 | for x in Iterable():
11 | reveal_type(x) # revealed: int
| ^ `int`
|
```

View File

@@ -12,40 +12,26 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
## mdtest_snippet.py
```
1 | from typing_extensions import reveal_type
2 |
3 | class Bad:
4 | __getitem__: None = None
5 |
6 | # error: [not-iterable]
7 | for x in Bad():
8 | reveal_type(x) # revealed: Unknown
1 | class Bad:
2 | __getitem__: None = None
3 |
4 | # error: [not-iterable]
5 | for x in Bad():
6 | reveal_type(x) # revealed: Unknown
```
# Diagnostics
```
error[not-iterable]: Object of type `Bad` is not iterable
--> src/mdtest_snippet.py:7:10
--> src/mdtest_snippet.py:5:10
|
6 | # error: [not-iterable]
7 | for x in Bad():
4 | # error: [not-iterable]
5 | for x in Bad():
| ^^^^^
8 | reveal_type(x) # revealed: Unknown
6 | reveal_type(x) # revealed: Unknown
|
info: It has no `__iter__` method and its `__getitem__` attribute has type `None`, which is not callable
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:8:17
|
6 | # error: [not-iterable]
7 | for x in Bad():
8 | reveal_type(x) # revealed: Unknown
| ^ `Unknown`
|
```

View File

@@ -12,48 +12,46 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
## mdtest_snippet.py
```
1 | from typing_extensions import reveal_type
2 |
3 | def _(flag: bool):
4 | class CustomCallable:
5 | if flag:
6 | def __call__(self, *args, **kwargs) -> int:
7 | return 42
8 | else:
9 | __call__: None = None
10 |
11 | class Iterable1:
12 | __getitem__: CustomCallable = CustomCallable()
13 |
14 | class Iterable2:
15 | if flag:
16 | def __getitem__(self, key: int) -> int:
17 | return 42
18 | else:
19 | __getitem__: None = None
20 |
21 | # error: [not-iterable]
22 | for x in Iterable1():
23 | # TODO... `int` might be ideal here?
24 | reveal_type(x) # revealed: int | Unknown
25 |
26 | # error: [not-iterable]
27 | for y in Iterable2():
28 | # TODO... `int` might be ideal here?
29 | reveal_type(y) # revealed: int | Unknown
1 | def _(flag: bool):
2 | class CustomCallable:
3 | if flag:
4 | def __call__(self, *args, **kwargs) -> int:
5 | return 42
6 | else:
7 | __call__: None = None
8 |
9 | class Iterable1:
10 | __getitem__: CustomCallable = CustomCallable()
11 |
12 | class Iterable2:
13 | if flag:
14 | def __getitem__(self, key: int) -> int:
15 | return 42
16 | else:
17 | __getitem__: None = None
18 |
19 | # error: [not-iterable]
20 | for x in Iterable1():
21 | # TODO... `int` might be ideal here?
22 | reveal_type(x) # revealed: int | Unknown
23 |
24 | # error: [not-iterable]
25 | for y in Iterable2():
26 | # TODO... `int` might be ideal here?
27 | reveal_type(y) # revealed: int | Unknown
```
# Diagnostics
```
error[not-iterable]: Object of type `Iterable1` may not be iterable
--> src/mdtest_snippet.py:22:14
--> src/mdtest_snippet.py:20:14
|
21 | # error: [not-iterable]
22 | for x in Iterable1():
19 | # error: [not-iterable]
20 | for x in Iterable1():
| ^^^^^^^^^^^
23 | # TODO... `int` might be ideal here?
24 | reveal_type(x) # revealed: int | Unknown
21 | # TODO... `int` might be ideal here?
22 | reveal_type(x) # revealed: int | Unknown
|
info: It has no `__iter__` method and its `__getitem__` attribute is invalid
info: `__getitem__` has type `CustomCallable`, which is not callable
@@ -61,44 +59,18 @@ info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:24:21
|
22 | for x in Iterable1():
23 | # TODO... `int` might be ideal here?
24 | reveal_type(x) # revealed: int | Unknown
| ^ `int | Unknown`
25 |
26 | # error: [not-iterable]
|
```
```
error[not-iterable]: Object of type `Iterable2` may not be iterable
--> src/mdtest_snippet.py:27:14
--> src/mdtest_snippet.py:25:14
|
26 | # error: [not-iterable]
27 | for y in Iterable2():
24 | # error: [not-iterable]
25 | for y in Iterable2():
| ^^^^^^^^^^^
28 | # TODO... `int` might be ideal here?
29 | reveal_type(y) # revealed: int | Unknown
26 | # TODO... `int` might be ideal here?
27 | reveal_type(y) # revealed: int | Unknown
|
info: It has no `__iter__` method and its `__getitem__` attribute is invalid
info: `__getitem__` has type `(bound method Iterable2.__getitem__(key: int) -> int) | None`, which is not callable
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:29:21
|
27 | for y in Iterable2():
28 | # TODO... `int` might be ideal here?
29 | reveal_type(y) # revealed: int | Unknown
| ^ `int | Unknown`
|
```

View File

@@ -12,48 +12,46 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
## mdtest_snippet.py
```
1 | from typing_extensions import reveal_type
2 |
3 | class Iterator:
4 | def __next__(self) -> int:
5 | return 42
6 |
7 | def _(flag: bool):
8 | class Iterable1:
9 | if flag:
10 | def __iter__(self) -> Iterator:
11 | return Iterator()
12 | else:
13 | def __iter__(self, invalid_extra_arg) -> Iterator:
14 | return Iterator()
15 |
16 | # error: [not-iterable]
17 | for x in Iterable1():
18 | reveal_type(x) # revealed: int
19 |
20 | class Iterable2:
21 | if flag:
22 | def __iter__(self) -> Iterator:
23 | return Iterator()
24 | else:
25 | __iter__: None = None
26 |
27 | # error: [not-iterable]
28 | for x in Iterable2():
29 | # TODO: `int` would probably be better here:
30 | reveal_type(x) # revealed: int | Unknown
1 | class Iterator:
2 | def __next__(self) -> int:
3 | return 42
4 |
5 | def _(flag: bool):
6 | class Iterable1:
7 | if flag:
8 | def __iter__(self) -> Iterator:
9 | return Iterator()
10 | else:
11 | def __iter__(self, invalid_extra_arg) -> Iterator:
12 | return Iterator()
13 |
14 | # error: [not-iterable]
15 | for x in Iterable1():
16 | reveal_type(x) # revealed: int
17 |
18 | class Iterable2:
19 | if flag:
20 | def __iter__(self) -> Iterator:
21 | return Iterator()
22 | else:
23 | __iter__: None = None
24 |
25 | # error: [not-iterable]
26 | for x in Iterable2():
27 | # TODO: `int` would probably be better here:
28 | reveal_type(x) # revealed: int | Unknown
```
# Diagnostics
```
error[not-iterable]: Object of type `Iterable1` may not be iterable
--> src/mdtest_snippet.py:17:14
--> src/mdtest_snippet.py:15:14
|
16 | # error: [not-iterable]
17 | for x in Iterable1():
14 | # error: [not-iterable]
15 | for x in Iterable1():
| ^^^^^^^^^^^
18 | reveal_type(x) # revealed: int
16 | reveal_type(x) # revealed: int
|
info: Its `__iter__` method may have an invalid signature
info: Type of `__iter__` is `(bound method Iterable1.__iter__() -> Iterator) | (bound method Iterable1.__iter__(invalid_extra_arg) -> Iterator)`
@@ -62,43 +60,17 @@ info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:18:21
|
16 | # error: [not-iterable]
17 | for x in Iterable1():
18 | reveal_type(x) # revealed: int
| ^ `int`
19 |
20 | class Iterable2:
|
```
```
error[not-iterable]: Object of type `Iterable2` may not be iterable
--> src/mdtest_snippet.py:28:14
--> src/mdtest_snippet.py:26:14
|
27 | # error: [not-iterable]
28 | for x in Iterable2():
25 | # error: [not-iterable]
26 | for x in Iterable2():
| ^^^^^^^^^^^
29 | # TODO: `int` would probably be better here:
30 | reveal_type(x) # revealed: int | Unknown
27 | # TODO: `int` would probably be better here:
28 | reveal_type(x) # revealed: int | Unknown
|
info: Its `__iter__` attribute (with type `(bound method Iterable2.__iter__() -> Iterator) | None`) may not be callable
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:30:21
|
28 | for x in Iterable2():
29 | # TODO: `int` would probably be better here:
30 | reveal_type(x) # revealed: int | Unknown
| ^ `int | Unknown`
|
```

View File

@@ -12,45 +12,43 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
## mdtest_snippet.py
```
1 | from typing_extensions import reveal_type
2 |
3 | def _(flag: bool):
4 | class Iterable1:
5 | if flag:
6 | def __getitem__(self, item: int) -> str:
7 | return "foo"
8 | else:
9 | __getitem__: None = None
10 |
11 | class Iterable2:
12 | if flag:
13 | def __getitem__(self, item: int) -> str:
14 | return "foo"
15 | else:
16 | def __getitem__(self, item: str) -> int:
17 | return 42
18 |
19 | # error: [not-iterable]
20 | for x in Iterable1():
21 | # TODO: `str` might be better
22 | reveal_type(x) # revealed: str | Unknown
23 |
24 | # error: [not-iterable]
25 | for y in Iterable2():
26 | reveal_type(y) # revealed: str | int
1 | def _(flag: bool):
2 | class Iterable1:
3 | if flag:
4 | def __getitem__(self, item: int) -> str:
5 | return "foo"
6 | else:
7 | __getitem__: None = None
8 |
9 | class Iterable2:
10 | if flag:
11 | def __getitem__(self, item: int) -> str:
12 | return "foo"
13 | else:
14 | def __getitem__(self, item: str) -> int:
15 | return 42
16 |
17 | # error: [not-iterable]
18 | for x in Iterable1():
19 | # TODO: `str` might be better
20 | reveal_type(x) # revealed: str | Unknown
21 |
22 | # error: [not-iterable]
23 | for y in Iterable2():
24 | reveal_type(y) # revealed: str | int
```
# Diagnostics
```
error[not-iterable]: Object of type `Iterable1` may not be iterable
--> src/mdtest_snippet.py:20:14
--> src/mdtest_snippet.py:18:14
|
19 | # error: [not-iterable]
20 | for x in Iterable1():
17 | # error: [not-iterable]
18 | for x in Iterable1():
| ^^^^^^^^^^^
21 | # TODO: `str` might be better
22 | reveal_type(x) # revealed: str | Unknown
19 | # TODO: `str` might be better
20 | reveal_type(x) # revealed: str | Unknown
|
info: It has no `__iter__` method and its `__getitem__` attribute is invalid
info: `__getitem__` has type `(bound method Iterable1.__getitem__(item: int) -> str) | None`, which is not callable
@@ -58,43 +56,17 @@ info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:22:21
|
20 | for x in Iterable1():
21 | # TODO: `str` might be better
22 | reveal_type(x) # revealed: str | Unknown
| ^ `str | Unknown`
23 |
24 | # error: [not-iterable]
|
```
```
error[not-iterable]: Object of type `Iterable2` may not be iterable
--> src/mdtest_snippet.py:25:14
--> src/mdtest_snippet.py:23:14
|
24 | # error: [not-iterable]
25 | for y in Iterable2():
22 | # error: [not-iterable]
23 | for y in Iterable2():
| ^^^^^^^^^^^
26 | reveal_type(y) # revealed: str | int
24 | reveal_type(y) # revealed: str | int
|
info: It has no `__iter__` method and its `__getitem__` method (with type `(bound method Iterable2.__getitem__(item: int) -> str) | (bound method Iterable2.__getitem__(item: str) -> int)`) may have an incorrect signature for the old-style iteration protocol
info: `__getitem__` must be at least as permissive as `def __getitem__(self, key: int): ...` to satisfy the old-style iteration protocol
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:26:21
|
24 | # error: [not-iterable]
25 | for y in Iterable2():
26 | reveal_type(y) # revealed: str | int
| ^ `str | int`
|
```

View File

@@ -12,52 +12,50 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
## mdtest_snippet.py
```
1 | from typing_extensions import reveal_type
2 |
3 | def _(flag: bool):
4 | class Iterator1:
5 | if flag:
6 | def __next__(self) -> int:
7 | return 42
8 | else:
9 | def __next__(self, invalid_extra_arg) -> str:
10 | return "foo"
11 |
12 | class Iterator2:
13 | if flag:
14 | def __next__(self) -> int:
15 | return 42
16 | else:
17 | __next__: None = None
18 |
19 | class Iterable1:
20 | def __iter__(self) -> Iterator1:
21 | return Iterator1()
22 |
23 | class Iterable2:
24 | def __iter__(self) -> Iterator2:
25 | return Iterator2()
26 |
27 | # error: [not-iterable]
28 | for x in Iterable1():
29 | reveal_type(x) # revealed: int | str
30 |
31 | # error: [not-iterable]
32 | for y in Iterable2():
33 | # TODO: `int` would probably be better here:
34 | reveal_type(y) # revealed: int | Unknown
1 | def _(flag: bool):
2 | class Iterator1:
3 | if flag:
4 | def __next__(self) -> int:
5 | return 42
6 | else:
7 | def __next__(self, invalid_extra_arg) -> str:
8 | return "foo"
9 |
10 | class Iterator2:
11 | if flag:
12 | def __next__(self) -> int:
13 | return 42
14 | else:
15 | __next__: None = None
16 |
17 | class Iterable1:
18 | def __iter__(self) -> Iterator1:
19 | return Iterator1()
20 |
21 | class Iterable2:
22 | def __iter__(self) -> Iterator2:
23 | return Iterator2()
24 |
25 | # error: [not-iterable]
26 | for x in Iterable1():
27 | reveal_type(x) # revealed: int | str
28 |
29 | # error: [not-iterable]
30 | for y in Iterable2():
31 | # TODO: `int` would probably be better here:
32 | reveal_type(y) # revealed: int | Unknown
```
# Diagnostics
```
error[not-iterable]: Object of type `Iterable1` may not be iterable
--> src/mdtest_snippet.py:28:14
--> src/mdtest_snippet.py:26:14
|
27 | # error: [not-iterable]
28 | for x in Iterable1():
25 | # error: [not-iterable]
26 | for x in Iterable1():
| ^^^^^^^^^^^
29 | reveal_type(x) # revealed: int | str
27 | reveal_type(x) # revealed: int | str
|
info: Its `__iter__` method returns an object of type `Iterator1`, which may have an invalid `__next__` method
info: Expected signature for `__next__` is `def __next__(self): ...`
@@ -65,43 +63,17 @@ info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:29:21
|
27 | # error: [not-iterable]
28 | for x in Iterable1():
29 | reveal_type(x) # revealed: int | str
| ^ `int | str`
30 |
31 | # error: [not-iterable]
|
```
```
error[not-iterable]: Object of type `Iterable2` may not be iterable
--> src/mdtest_snippet.py:32:14
--> src/mdtest_snippet.py:30:14
|
31 | # error: [not-iterable]
32 | for y in Iterable2():
29 | # error: [not-iterable]
30 | for y in Iterable2():
| ^^^^^^^^^^^
33 | # TODO: `int` would probably be better here:
34 | reveal_type(y) # revealed: int | Unknown
31 | # TODO: `int` would probably be better here:
32 | reveal_type(y) # revealed: int | Unknown
|
info: Its `__iter__` method returns an object of type `Iterator2`, which has a `__next__` attribute that may not be callable
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:34:21
|
32 | for y in Iterable2():
33 | # TODO: `int` would probably be better here:
34 | reveal_type(y) # revealed: int | Unknown
| ^ `int | Unknown`
|
```

View File

@@ -12,99 +12,71 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
## mdtest_snippet.py
```
1 | from typing_extensions import reveal_type
2 |
3 | class Iterator:
4 | def __next__(self) -> bytes:
5 | return b"foo"
6 |
7 | def _(flag: bool, flag2: bool):
8 | class Iterable1:
9 | if flag:
10 | def __getitem__(self, item: int) -> str:
11 | return "foo"
12 | else:
13 | __getitem__: None = None
14 |
15 | if flag2:
16 | def __iter__(self) -> Iterator:
17 | return Iterator()
18 |
19 | class Iterable2:
20 | if flag:
21 | def __getitem__(self, item: int) -> str:
22 | return "foo"
23 | else:
24 | def __getitem__(self, item: str) -> int:
25 | return 42
26 | if flag2:
27 | def __iter__(self) -> Iterator:
28 | return Iterator()
29 |
30 | # error: [not-iterable]
31 | for x in Iterable1():
32 | # TODO: `bytes | str` might be better
33 | reveal_type(x) # revealed: bytes | str | Unknown
34 |
35 | # error: [not-iterable]
36 | for y in Iterable2():
37 | reveal_type(y) # revealed: bytes | str | int
1 | class Iterator:
2 | def __next__(self) -> bytes:
3 | return b"foo"
4 |
5 | def _(flag: bool, flag2: bool):
6 | class Iterable1:
7 | if flag:
8 | def __getitem__(self, item: int) -> str:
9 | return "foo"
10 | else:
11 | __getitem__: None = None
12 |
13 | if flag2:
14 | def __iter__(self) -> Iterator:
15 | return Iterator()
16 |
17 | class Iterable2:
18 | if flag:
19 | def __getitem__(self, item: int) -> str:
20 | return "foo"
21 | else:
22 | def __getitem__(self, item: str) -> int:
23 | return 42
24 | if flag2:
25 | def __iter__(self) -> Iterator:
26 | return Iterator()
27 |
28 | # error: [not-iterable]
29 | for x in Iterable1():
30 | # TODO: `bytes | str` might be better
31 | reveal_type(x) # revealed: bytes | str | Unknown
32 |
33 | # error: [not-iterable]
34 | for y in Iterable2():
35 | reveal_type(y) # revealed: bytes | str | int
```
# Diagnostics
```
error[not-iterable]: Object of type `Iterable1` may not be iterable
--> src/mdtest_snippet.py:31:14
--> src/mdtest_snippet.py:29:14
|
30 | # error: [not-iterable]
31 | for x in Iterable1():
28 | # error: [not-iterable]
29 | for x in Iterable1():
| ^^^^^^^^^^^
32 | # TODO: `bytes | str` might be better
33 | reveal_type(x) # revealed: bytes | str | Unknown
30 | # TODO: `bytes | str` might be better
31 | reveal_type(x) # revealed: bytes | str | Unknown
|
info: It may not have an `__iter__` method and its `__getitem__` attribute (with type `(bound method Iterable1.__getitem__(item: int) -> str) | None`) may not be callable
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:33:21
|
31 | for x in Iterable1():
32 | # TODO: `bytes | str` might be better
33 | reveal_type(x) # revealed: bytes | str | Unknown
| ^ `bytes | str | Unknown`
34 |
35 | # error: [not-iterable]
|
```
```
error[not-iterable]: Object of type `Iterable2` may not be iterable
--> src/mdtest_snippet.py:36:14
--> src/mdtest_snippet.py:34:14
|
35 | # error: [not-iterable]
36 | for y in Iterable2():
33 | # error: [not-iterable]
34 | for y in Iterable2():
| ^^^^^^^^^^^
37 | reveal_type(y) # revealed: bytes | str | int
35 | reveal_type(y) # revealed: bytes | str | int
|
info: It may not have an `__iter__` method and its `__getitem__` method (with type `(bound method Iterable2.__getitem__(item: int) -> str) | (bound method Iterable2.__getitem__(item: str) -> int)`) may have an incorrect signature for the old-style iteration protocol
info: `__getitem__` must be at least as permissive as `def __getitem__(self, key: int): ...` to satisfy the old-style iteration protocol
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:37:21
|
35 | # error: [not-iterable]
36 | for y in Iterable2():
37 | reveal_type(y) # revealed: bytes | str | int
| ^ `bytes | str | int`
|
```

View File

@@ -12,52 +12,38 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
## mdtest_snippet.py
```
1 | from typing_extensions import reveal_type
2 |
3 | def _(flag: bool):
4 | class Iterator:
5 | def __next__(self) -> int:
6 | return 42
7 |
8 | class Iterable:
9 | if flag:
10 | def __iter__(self) -> Iterator:
11 | return Iterator()
12 | # invalid signature because it only accepts a `str`,
13 | # but the old-style iteration protocol will pass it an `int`
14 | def __getitem__(self, key: str) -> bytes:
15 | return bytes()
16 |
17 | # error: [not-iterable]
18 | for x in Iterable():
19 | reveal_type(x) # revealed: int | bytes
1 | def _(flag: bool):
2 | class Iterator:
3 | def __next__(self) -> int:
4 | return 42
5 |
6 | class Iterable:
7 | if flag:
8 | def __iter__(self) -> Iterator:
9 | return Iterator()
10 | # invalid signature because it only accepts a `str`,
11 | # but the old-style iteration protocol will pass it an `int`
12 | def __getitem__(self, key: str) -> bytes:
13 | return bytes()
14 |
15 | # error: [not-iterable]
16 | for x in Iterable():
17 | reveal_type(x) # revealed: int | bytes
```
# Diagnostics
```
error[not-iterable]: Object of type `Iterable` may not be iterable
--> src/mdtest_snippet.py:18:14
--> src/mdtest_snippet.py:16:14
|
17 | # error: [not-iterable]
18 | for x in Iterable():
15 | # error: [not-iterable]
16 | for x in Iterable():
| ^^^^^^^^^^
19 | reveal_type(x) # revealed: int | bytes
17 | reveal_type(x) # revealed: int | bytes
|
info: It may not have an `__iter__` method and its `__getitem__` method has an incorrect signature for the old-style iteration protocol
info: `__getitem__` must be at least as permissive as `def __getitem__(self, key: int): ...` to satisfy the old-style iteration protocol
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:19:21
|
17 | # error: [not-iterable]
18 | for x in Iterable():
19 | reveal_type(x) # revealed: int | bytes
| ^ `int | bytes`
|
```

View File

@@ -12,50 +12,36 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
## mdtest_snippet.py
```
1 | from typing_extensions import reveal_type
2 |
3 | class Iterator:
4 | def __next__(self) -> int:
5 | return 42
6 |
7 | def _(flag1: bool, flag2: bool):
8 | class Iterable:
9 | if flag1:
10 | def __iter__(self) -> Iterator:
11 | return Iterator()
12 | if flag2:
13 | def __getitem__(self, key: int) -> bytes:
14 | return bytes()
15 |
16 | # error: [not-iterable]
17 | for x in Iterable():
18 | reveal_type(x) # revealed: int | bytes
1 | class Iterator:
2 | def __next__(self) -> int:
3 | return 42
4 |
5 | def _(flag1: bool, flag2: bool):
6 | class Iterable:
7 | if flag1:
8 | def __iter__(self) -> Iterator:
9 | return Iterator()
10 | if flag2:
11 | def __getitem__(self, key: int) -> bytes:
12 | return bytes()
13 |
14 | # error: [not-iterable]
15 | for x in Iterable():
16 | reveal_type(x) # revealed: int | bytes
```
# Diagnostics
```
error[not-iterable]: Object of type `Iterable` may not be iterable
--> src/mdtest_snippet.py:17:14
--> src/mdtest_snippet.py:15:14
|
16 | # error: [not-iterable]
17 | for x in Iterable():
14 | # error: [not-iterable]
15 | for x in Iterable():
| ^^^^^^^^^^
18 | reveal_type(x) # revealed: int | bytes
16 | reveal_type(x) # revealed: int | bytes
|
info: It may not have an `__iter__` method or a `__getitem__` method
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:18:21
|
16 | # error: [not-iterable]
17 | for x in Iterable():
18 | reveal_type(x) # revealed: int | bytes
| ^ `int | bytes`
|
```

View File

@@ -12,52 +12,38 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
## mdtest_snippet.py
```
1 | from typing_extensions import reveal_type
2 |
3 | class TestIter:
4 | def __next__(self) -> int:
5 | return 42
6 |
7 | class Test:
8 | def __iter__(self) -> TestIter:
9 | return TestIter()
10 |
11 | class Test2:
12 | def __iter__(self) -> int:
13 | return 42
14 |
15 | def _(flag: bool):
16 | # TODO: Improve error message to state which union variant isn't iterable (https://github.com/astral-sh/ruff/issues/13989)
17 | # error: [not-iterable]
18 | for x in Test() if flag else Test2():
19 | reveal_type(x) # revealed: int
1 | class TestIter:
2 | def __next__(self) -> int:
3 | return 42
4 |
5 | class Test:
6 | def __iter__(self) -> TestIter:
7 | return TestIter()
8 |
9 | class Test2:
10 | def __iter__(self) -> int:
11 | return 42
12 |
13 | def _(flag: bool):
14 | # TODO: Improve error message to state which union variant isn't iterable (https://github.com/astral-sh/ruff/issues/13989)
15 | # error: [not-iterable]
16 | for x in Test() if flag else Test2():
17 | reveal_type(x) # revealed: int
```
# Diagnostics
```
error[not-iterable]: Object of type `Test | Test2` may not be iterable
--> src/mdtest_snippet.py:18:14
--> src/mdtest_snippet.py:16:14
|
16 | # TODO: Improve error message to state which union variant isn't iterable (https://github.com/astral-sh/ruff/issues/13989)
17 | # error: [not-iterable]
18 | for x in Test() if flag else Test2():
14 | # TODO: Improve error message to state which union variant isn't iterable (https://github.com/astral-sh/ruff/issues/13989)
15 | # error: [not-iterable]
16 | for x in Test() if flag else Test2():
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
19 | reveal_type(x) # revealed: int
17 | reveal_type(x) # revealed: int
|
info: Its `__iter__` method returns an object of type `TestIter | int`, which may not have a `__next__` method
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:19:21
|
17 | # error: [not-iterable]
18 | for x in Test() if flag else Test2():
19 | reveal_type(x) # revealed: int
| ^ `int`
|
```

View File

@@ -12,47 +12,33 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
## mdtest_snippet.py
```
1 | from typing_extensions import reveal_type
2 |
3 | class TestIter:
4 | def __next__(self) -> int:
5 | return 42
6 |
7 | class Test:
8 | def __iter__(self) -> TestIter:
9 | return TestIter()
10 |
11 | def _(flag: bool):
12 | # error: [not-iterable]
13 | for x in Test() if flag else 42:
14 | reveal_type(x) # revealed: int
1 | class TestIter:
2 | def __next__(self) -> int:
3 | return 42
4 |
5 | class Test:
6 | def __iter__(self) -> TestIter:
7 | return TestIter()
8 |
9 | def _(flag: bool):
10 | # error: [not-iterable]
11 | for x in Test() if flag else 42:
12 | reveal_type(x) # revealed: int
```
# Diagnostics
```
error[not-iterable]: Object of type `Test | Literal[42]` may not be iterable
--> src/mdtest_snippet.py:13:14
--> src/mdtest_snippet.py:11:14
|
11 | def _(flag: bool):
12 | # error: [not-iterable]
13 | for x in Test() if flag else 42:
9 | def _(flag: bool):
10 | # error: [not-iterable]
11 | for x in Test() if flag else 42:
| ^^^^^^^^^^^^^^^^^^^^^^
14 | reveal_type(x) # revealed: int
12 | reveal_type(x) # revealed: int
|
info: It may not have an `__iter__` method and it doesn't have a `__getitem__` method
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:14:21
|
12 | # error: [not-iterable]
13 | for x in Test() if flag else 42:
14 | reveal_type(x) # revealed: int
| ^ `int`
|
```

View File

@@ -12,34 +12,32 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
## mdtest_snippet.py
```
1 | from typing_extensions import reveal_type
2 |
3 | def _(flag: bool):
4 | class NotIterable:
5 | if flag:
6 | __iter__: int = 1
7 | else:
8 | __iter__: None = None
9 |
10 | # error: [not-iterable]
11 | for x in NotIterable():
12 | pass
13 |
14 | # revealed: Unknown
15 | # error: [possibly-unresolved-reference]
16 | reveal_type(x)
1 | def _(flag: bool):
2 | class NotIterable:
3 | if flag:
4 | __iter__: int = 1
5 | else:
6 | __iter__: None = None
7 |
8 | # error: [not-iterable]
9 | for x in NotIterable():
10 | pass
11 |
12 | # revealed: Unknown
13 | # error: [possibly-unresolved-reference]
14 | reveal_type(x)
```
# Diagnostics
```
error[not-iterable]: Object of type `NotIterable` is not iterable
--> src/mdtest_snippet.py:11:14
--> src/mdtest_snippet.py:9:14
|
10 | # error: [not-iterable]
11 | for x in NotIterable():
8 | # error: [not-iterable]
9 | for x in NotIterable():
| ^^^^^^^^^^^^^
12 | pass
10 | pass
|
info: Its `__iter__` attribute has type `int | None`, which is not callable
info: rule `not-iterable` is enabled by default
@@ -48,25 +46,13 @@ info: rule `not-iterable` is enabled by default
```
info[possibly-unresolved-reference]: Name `x` used when possibly not defined
--> src/mdtest_snippet.py:16:17
--> src/mdtest_snippet.py:14:17
|
14 | # revealed: Unknown
15 | # error: [possibly-unresolved-reference]
16 | reveal_type(x)
12 | # revealed: Unknown
13 | # error: [possibly-unresolved-reference]
14 | reveal_type(x)
| ^
|
info: rule `possibly-unresolved-reference` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:16:17
|
14 | # revealed: Unknown
15 | # error: [possibly-unresolved-reference]
16 | reveal_type(x)
| ^ `Unknown`
|
```

View File

@@ -12,41 +12,27 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
## mdtest_snippet.py
```
1 | from typing_extensions import reveal_type
2 |
3 | class Bad:
4 | def __iter__(self) -> int:
5 | return 42
6 |
7 | # error: [not-iterable]
8 | for x in Bad():
9 | reveal_type(x) # revealed: Unknown
1 | class Bad:
2 | def __iter__(self) -> int:
3 | return 42
4 |
5 | # error: [not-iterable]
6 | for x in Bad():
7 | reveal_type(x) # revealed: Unknown
```
# Diagnostics
```
error[not-iterable]: Object of type `Bad` is not iterable
--> src/mdtest_snippet.py:8:10
--> src/mdtest_snippet.py:6:10
|
7 | # error: [not-iterable]
8 | for x in Bad():
5 | # error: [not-iterable]
6 | for x in Bad():
| ^^^^^
9 | reveal_type(x) # revealed: Unknown
7 | reveal_type(x) # revealed: Unknown
|
info: Its `__iter__` method returns an object of type `int`, which has no `__next__` method
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:9:17
|
7 | # error: [not-iterable]
8 | for x in Bad():
9 | reveal_type(x) # revealed: Unknown
| ^ `Unknown`
|
```

View File

@@ -12,46 +12,32 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
## mdtest_snippet.py
```
1 | from typing_extensions import reveal_type
2 |
3 | class Iterator:
4 | def __next__(self) -> int:
5 | return 42
6 |
7 | class Iterable:
8 | def __iter__(self, extra_arg) -> Iterator:
9 | return Iterator()
10 |
11 | # error: [not-iterable]
12 | for x in Iterable():
13 | reveal_type(x) # revealed: int
1 | class Iterator:
2 | def __next__(self) -> int:
3 | return 42
4 |
5 | class Iterable:
6 | def __iter__(self, extra_arg) -> Iterator:
7 | return Iterator()
8 |
9 | # error: [not-iterable]
10 | for x in Iterable():
11 | reveal_type(x) # revealed: int
```
# Diagnostics
```
error[not-iterable]: Object of type `Iterable` is not iterable
--> src/mdtest_snippet.py:12:10
--> src/mdtest_snippet.py:10:10
|
11 | # error: [not-iterable]
12 | for x in Iterable():
9 | # error: [not-iterable]
10 | for x in Iterable():
| ^^^^^^^^^^
13 | reveal_type(x) # revealed: int
11 | reveal_type(x) # revealed: int
|
info: Its `__iter__` method has an invalid signature
info: Expected signature `def __iter__(self): ...`
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:13:17
|
11 | # error: [not-iterable]
12 | for x in Iterable():
13 | reveal_type(x) # revealed: int
| ^ `int`
|
```

View File

@@ -12,42 +12,40 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/loops/for.md
## mdtest_snippet.py
```
1 | from typing_extensions import reveal_type
2 |
3 | class Iterator1:
4 | def __next__(self, extra_arg) -> int:
5 | return 42
6 |
7 | class Iterator2:
8 | __next__: None = None
9 |
10 | class Iterable1:
11 | def __iter__(self) -> Iterator1:
12 | return Iterator1()
13 |
14 | class Iterable2:
15 | def __iter__(self) -> Iterator2:
16 | return Iterator2()
17 |
18 | # error: [not-iterable]
19 | for x in Iterable1():
20 | reveal_type(x) # revealed: int
21 |
22 | # error: [not-iterable]
23 | for y in Iterable2():
24 | reveal_type(y) # revealed: Unknown
1 | class Iterator1:
2 | def __next__(self, extra_arg) -> int:
3 | return 42
4 |
5 | class Iterator2:
6 | __next__: None = None
7 |
8 | class Iterable1:
9 | def __iter__(self) -> Iterator1:
10 | return Iterator1()
11 |
12 | class Iterable2:
13 | def __iter__(self) -> Iterator2:
14 | return Iterator2()
15 |
16 | # error: [not-iterable]
17 | for x in Iterable1():
18 | reveal_type(x) # revealed: int
19 |
20 | # error: [not-iterable]
21 | for y in Iterable2():
22 | reveal_type(y) # revealed: Unknown
```
# Diagnostics
```
error[not-iterable]: Object of type `Iterable1` is not iterable
--> src/mdtest_snippet.py:19:10
--> src/mdtest_snippet.py:17:10
|
18 | # error: [not-iterable]
19 | for x in Iterable1():
16 | # error: [not-iterable]
17 | for x in Iterable1():
| ^^^^^^^^^^^
20 | reveal_type(x) # revealed: int
18 | reveal_type(x) # revealed: int
|
info: Its `__iter__` method returns an object of type `Iterator1`, which has an invalid `__next__` method
info: Expected signature for `__next__` is `def __next__(self): ...`
@@ -55,42 +53,16 @@ info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:20:17
|
18 | # error: [not-iterable]
19 | for x in Iterable1():
20 | reveal_type(x) # revealed: int
| ^ `int`
21 |
22 | # error: [not-iterable]
|
```
```
error[not-iterable]: Object of type `Iterable2` is not iterable
--> src/mdtest_snippet.py:23:10
--> src/mdtest_snippet.py:21:10
|
22 | # error: [not-iterable]
23 | for y in Iterable2():
20 | # error: [not-iterable]
21 | for y in Iterable2():
| ^^^^^^^^^^^
24 | reveal_type(y) # revealed: Unknown
22 | reveal_type(y) # revealed: Unknown
|
info: Its `__iter__` method returns an object of type `Iterator2`, which has a `__next__` attribute that is not callable
info: rule `not-iterable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:24:17
|
22 | # error: [not-iterable]
23 | for y in Iterable2():
24 | reveal_type(y) # revealed: Unknown
| ^ `Unknown`
|
```

View File

@@ -13,78 +13,38 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/generics/legacy/function
```
1 | from typing import TypeVar
2 | from typing_extensions import reveal_type
3 |
4 | T = TypeVar("T", bound=int)
5 |
6 | def f(x: T) -> T:
7 | return x
8 |
9 | reveal_type(f(1)) # revealed: Literal[1]
10 | reveal_type(f(True)) # revealed: Literal[True]
11 | # error: [invalid-argument-type]
12 | reveal_type(f("string")) # revealed: Unknown
2 |
3 | T = TypeVar("T", bound=int)
4 |
5 | def f(x: T) -> T:
6 | return x
7 |
8 | reveal_type(f(1)) # revealed: Literal[1]
9 | reveal_type(f(True)) # revealed: Literal[True]
10 | # error: [invalid-argument-type]
11 | reveal_type(f("string")) # revealed: Unknown
```
# Diagnostics
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:9:13
|
7 | return x
8 |
9 | reveal_type(f(1)) # revealed: Literal[1]
| ^^^^ `Literal[1]`
10 | reveal_type(f(True)) # revealed: Literal[True]
11 | # error: [invalid-argument-type]
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:10:13
|
9 | reveal_type(f(1)) # revealed: Literal[1]
10 | reveal_type(f(True)) # revealed: Literal[True]
| ^^^^^^^ `Literal[True]`
11 | # error: [invalid-argument-type]
12 | reveal_type(f("string")) # revealed: Unknown
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:12:13
|
10 | reveal_type(f(True)) # revealed: Literal[True]
11 | # error: [invalid-argument-type]
12 | reveal_type(f("string")) # revealed: Unknown
| ^^^^^^^^^^^ `Unknown`
|
```
```
error[invalid-argument-type]: Argument to function `f` is incorrect
--> src/mdtest_snippet.py:12:15
--> src/mdtest_snippet.py:11:15
|
10 | reveal_type(f(True)) # revealed: Literal[True]
11 | # error: [invalid-argument-type]
12 | reveal_type(f("string")) # revealed: Unknown
9 | reveal_type(f(True)) # revealed: Literal[True]
10 | # error: [invalid-argument-type]
11 | reveal_type(f("string")) # revealed: Unknown
| ^^^^^^^^ Argument type `Literal["string"]` does not satisfy upper bound `int` of type variable `T`
|
info: Type variable defined here
--> src/mdtest_snippet.py:4:1
--> src/mdtest_snippet.py:3:1
|
2 | from typing_extensions import reveal_type
3 |
4 | T = TypeVar("T", bound=int)
1 | from typing import TypeVar
2 |
3 | T = TypeVar("T", bound=int)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
5 |
6 | def f(x: T) -> T:
4 |
5 | def f(x: T) -> T:
|
info: rule `invalid-argument-type` is enabled by default

View File

@@ -13,93 +13,39 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/generics/legacy/function
```
1 | from typing import TypeVar
2 | from typing_extensions import reveal_type
3 |
4 | T = TypeVar("T", int, None)
5 |
6 | def f(x: T) -> T:
7 | return x
8 |
9 | reveal_type(f(1)) # revealed: int
10 | reveal_type(f(True)) # revealed: int
11 | reveal_type(f(None)) # revealed: None
12 | # error: [invalid-argument-type]
13 | reveal_type(f("string")) # revealed: Unknown
2 |
3 | T = TypeVar("T", int, None)
4 |
5 | def f(x: T) -> T:
6 | return x
7 |
8 | reveal_type(f(1)) # revealed: int
9 | reveal_type(f(True)) # revealed: int
10 | reveal_type(f(None)) # revealed: None
11 | # error: [invalid-argument-type]
12 | reveal_type(f("string")) # revealed: Unknown
```
# Diagnostics
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:9:13
|
7 | return x
8 |
9 | reveal_type(f(1)) # revealed: int
| ^^^^ `int`
10 | reveal_type(f(True)) # revealed: int
11 | reveal_type(f(None)) # revealed: None
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:10:13
|
9 | reveal_type(f(1)) # revealed: int
10 | reveal_type(f(True)) # revealed: int
| ^^^^^^^ `int`
11 | reveal_type(f(None)) # revealed: None
12 | # error: [invalid-argument-type]
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:11:13
|
9 | reveal_type(f(1)) # revealed: int
10 | reveal_type(f(True)) # revealed: int
11 | reveal_type(f(None)) # revealed: None
| ^^^^^^^ `None`
12 | # error: [invalid-argument-type]
13 | reveal_type(f("string")) # revealed: Unknown
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:13:13
|
11 | reveal_type(f(None)) # revealed: None
12 | # error: [invalid-argument-type]
13 | reveal_type(f("string")) # revealed: Unknown
| ^^^^^^^^^^^ `Unknown`
|
```
```
error[invalid-argument-type]: Argument to function `f` is incorrect
--> src/mdtest_snippet.py:13:15
--> src/mdtest_snippet.py:12:15
|
11 | reveal_type(f(None)) # revealed: None
12 | # error: [invalid-argument-type]
13 | reveal_type(f("string")) # revealed: Unknown
10 | reveal_type(f(None)) # revealed: None
11 | # error: [invalid-argument-type]
12 | reveal_type(f("string")) # revealed: Unknown
| ^^^^^^^^ Argument type `Literal["string"]` does not satisfy constraints (`int`, `None`) of type variable `T`
|
info: Type variable defined here
--> src/mdtest_snippet.py:4:1
--> src/mdtest_snippet.py:3:1
|
2 | from typing_extensions import reveal_type
3 |
4 | T = TypeVar("T", int, None)
1 | from typing import TypeVar
2 |
3 | T = TypeVar("T", int, None)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
5 |
6 | def f(x: T) -> T:
4 |
5 | def f(x: T) -> T:
|
info: rule `invalid-argument-type` is enabled by default

View File

@@ -25,45 +25,6 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/generics/pep695/function
# Diagnostics
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:6:13
|
4 | return x
5 |
6 | reveal_type(f(1)) # revealed: Literal[1]
| ^^^^ `Literal[1]`
7 | reveal_type(f(True)) # revealed: Literal[True]
8 | # error: [invalid-argument-type]
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:7:13
|
6 | reveal_type(f(1)) # revealed: Literal[1]
7 | reveal_type(f(True)) # revealed: Literal[True]
| ^^^^^^^ `Literal[True]`
8 | # error: [invalid-argument-type]
9 | reveal_type(f("string")) # revealed: Unknown
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:9:13
|
7 | reveal_type(f(True)) # revealed: Literal[True]
8 | # error: [invalid-argument-type]
9 | reveal_type(f("string")) # revealed: Unknown
| ^^^^^^^^^^^ `Unknown`
|
```
```
error[invalid-argument-type]: Argument to function `f` is incorrect
--> src/mdtest_snippet.py:9:15

View File

@@ -26,59 +26,6 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/generics/pep695/function
# Diagnostics
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:6:13
|
4 | return x
5 |
6 | reveal_type(f(1)) # revealed: int
| ^^^^ `int`
7 | reveal_type(f(True)) # revealed: int
8 | reveal_type(f(None)) # revealed: None
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:7:13
|
6 | reveal_type(f(1)) # revealed: int
7 | reveal_type(f(True)) # revealed: int
| ^^^^^^^ `int`
8 | reveal_type(f(None)) # revealed: None
9 | # error: [invalid-argument-type]
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:8:13
|
6 | reveal_type(f(1)) # revealed: int
7 | reveal_type(f(True)) # revealed: int
8 | reveal_type(f(None)) # revealed: None
| ^^^^^^^ `None`
9 | # error: [invalid-argument-type]
10 | reveal_type(f("string")) # revealed: Unknown
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:10:13
|
8 | reveal_type(f(None)) # revealed: None
9 | # error: [invalid-argument-type]
10 | reveal_type(f("string")) # revealed: Unknown
| ^^^^^^^^^^^ `Unknown`
|
```
```
error[invalid-argument-type]: Argument to function `f` is incorrect
--> src/mdtest_snippet.py:10:15

View File

@@ -0,0 +1,70 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - Boolean parameters must be unambiguous
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
---
# Python source files
## mdtest_snippet.py
```
1 | from typing_extensions import TypeVar
2 |
3 | def cond() -> bool:
4 | return True
5 |
6 | # error: [invalid-legacy-type-variable]
7 | T = TypeVar("T", covariant=cond())
8 |
9 | # error: [invalid-legacy-type-variable]
10 | U = TypeVar("U", contravariant=cond())
11 |
12 | # error: [invalid-legacy-type-variable]
13 | V = TypeVar("V", infer_variance=cond())
```
# Diagnostics
```
error[invalid-legacy-type-variable]: The `covariant` parameter of `TypeVar` cannot have an ambiguous truthiness
--> src/mdtest_snippet.py:7:28
|
6 | # error: [invalid-legacy-type-variable]
7 | T = TypeVar("T", covariant=cond())
| ^^^^^^
8 |
9 | # error: [invalid-legacy-type-variable]
|
info: rule `invalid-legacy-type-variable` is enabled by default
```
```
error[invalid-legacy-type-variable]: The `contravariant` parameter of `TypeVar` cannot have an ambiguous truthiness
--> src/mdtest_snippet.py:10:32
|
9 | # error: [invalid-legacy-type-variable]
10 | U = TypeVar("U", contravariant=cond())
| ^^^^^^
11 |
12 | # error: [invalid-legacy-type-variable]
|
info: rule `invalid-legacy-type-variable` is enabled by default
```
```
error[invalid-legacy-type-variable]: The `infer_variance` parameter of `TypeVar` cannot have an ambiguous truthiness
--> src/mdtest_snippet.py:13:33
|
12 | # error: [invalid-legacy-type-variable]
13 | V = TypeVar("V", infer_variance=cond())
| ^^^^^^
|
info: rule `invalid-legacy-type-variable` is enabled by default
```

View File

@@ -0,0 +1,33 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - Cannot be both covariant and contravariant
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
---
# Python source files
## mdtest_snippet.py
```
1 | from typing import TypeVar
2 |
3 | # error: [invalid-legacy-type-variable]
4 | T = TypeVar("T", covariant=True, contravariant=True)
```
# Diagnostics
```
error[invalid-legacy-type-variable]: A `TypeVar` cannot be both covariant and contravariant
--> src/mdtest_snippet.py:4:5
|
3 | # error: [invalid-legacy-type-variable]
4 | T = TypeVar("T", covariant=True, contravariant=True)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
info: rule `invalid-legacy-type-variable` is enabled by default
```

View File

@@ -0,0 +1,33 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - Cannot have both bound and constraint
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
---
# Python source files
## mdtest_snippet.py
```
1 | from typing import TypeVar
2 |
3 | # error: [invalid-legacy-type-variable]
4 | T = TypeVar("T", int, str, bound=bytes)
```
# Diagnostics
```
error[invalid-legacy-type-variable]: A `TypeVar` cannot have both a bound and constraints
--> src/mdtest_snippet.py:4:5
|
3 | # error: [invalid-legacy-type-variable]
4 | T = TypeVar("T", int, str, bound=bytes)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
info: rule `invalid-legacy-type-variable` is enabled by default
```

View File

@@ -0,0 +1,33 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - Cannot have only one constraint
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
---
# Python source files
## mdtest_snippet.py
```
1 | from typing import TypeVar
2 |
3 | # error: [invalid-legacy-type-variable]
4 | T = TypeVar("T", int)
```
# Diagnostics
```
error[invalid-legacy-type-variable]: A `TypeVar` cannot have exactly one constraint
--> src/mdtest_snippet.py:4:18
|
3 | # error: [invalid-legacy-type-variable]
4 | T = TypeVar("T", int)
| ^^^
|
info: rule `invalid-legacy-type-variable` is enabled by default
```

View File

@@ -0,0 +1,33 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - Invalid feature for this Python version
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
---
# Python source files
## mdtest_snippet.py
```
1 | from typing import TypeVar
2 |
3 | # error: [invalid-legacy-type-variable]
4 | T = TypeVar("T", default=int)
```
# Diagnostics
```
error[invalid-legacy-type-variable]: The `default` parameter of `typing.TypeVar` was added in Python 3.13
--> src/mdtest_snippet.py:4:18
|
3 | # error: [invalid-legacy-type-variable]
4 | T = TypeVar("T", default=int)
| ^^^^^^^^^^^
|
info: rule `invalid-legacy-type-variable` is enabled by default
```

View File

@@ -0,0 +1,33 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - Invalid keyword arguments
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
---
# Python source files
## mdtest_snippet.py
```
1 | from typing import TypeVar
2 |
3 | # error: [invalid-legacy-type-variable]
4 | T = TypeVar("T", invalid_keyword=True)
```
# Diagnostics
```
error[invalid-legacy-type-variable]: Unknown keyword argument `invalid_keyword` in `TypeVar` creation
--> src/mdtest_snippet.py:4:18
|
3 | # error: [invalid-legacy-type-variable]
4 | T = TypeVar("T", invalid_keyword=True)
| ^^^^^^^^^^^^^^^^^^^^
|
info: rule `invalid-legacy-type-variable` is enabled by default
```

View File

@@ -0,0 +1,52 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - Must be directly assigned to a variable
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
---
# Python source files
## mdtest_snippet.py
```
1 | from typing import TypeVar
2 |
3 | T = TypeVar("T")
4 | # error: [invalid-legacy-type-variable]
5 | U: TypeVar = TypeVar("U")
6 |
7 | # error: [invalid-legacy-type-variable]
8 | tuple_with_typevar = ("foo", TypeVar("W"))
```
# Diagnostics
```
error[invalid-legacy-type-variable]: A `TypeVar` definition must be a simple variable assignment
--> src/mdtest_snippet.py:5:14
|
3 | T = TypeVar("T")
4 | # error: [invalid-legacy-type-variable]
5 | U: TypeVar = TypeVar("U")
| ^^^^^^^^^^^^
6 |
7 | # error: [invalid-legacy-type-variable]
|
info: rule `invalid-legacy-type-variable` is enabled by default
```
```
error[invalid-legacy-type-variable]: A `TypeVar` definition must be a simple variable assignment
--> src/mdtest_snippet.py:8:30
|
7 | # error: [invalid-legacy-type-variable]
8 | tuple_with_typevar = ("foo", TypeVar("W"))
| ^^^^^^^^^^^^
|
info: rule `invalid-legacy-type-variable` is enabled by default
```

View File

@@ -0,0 +1,33 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - Must have a name
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
---
# Python source files
## mdtest_snippet.py
```
1 | from typing import TypeVar
2 |
3 | # error: [invalid-legacy-type-variable]
4 | T = TypeVar()
```
# Diagnostics
```
error[invalid-legacy-type-variable]: The `name` parameter of `TypeVar` is required.
--> src/mdtest_snippet.py:4:5
|
3 | # error: [invalid-legacy-type-variable]
4 | T = TypeVar()
| ^^^^^^^^^
|
info: rule `invalid-legacy-type-variable` is enabled by default
```

View File

@@ -0,0 +1,33 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - Name can't be given more than once
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
---
# Python source files
## mdtest_snippet.py
```
1 | from typing import TypeVar
2 |
3 | # error: [invalid-legacy-type-variable]
4 | T = TypeVar("T", name="T")
```
# Diagnostics
```
error[invalid-legacy-type-variable]: The `name` parameter of `TypeVar` can only be provided once.
--> src/mdtest_snippet.py:4:18
|
3 | # error: [invalid-legacy-type-variable]
4 | T = TypeVar("T", name="T")
| ^^^^^^^^
|
info: rule `invalid-legacy-type-variable` is enabled by default
```

View File

@@ -0,0 +1,52 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - No variadic arguments
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
---
# Python source files
## mdtest_snippet.py
```
1 | from typing import TypeVar
2 |
3 | types = (int, str)
4 |
5 | # error: [invalid-legacy-type-variable]
6 | T = TypeVar("T", *types)
7 |
8 | # error: [invalid-legacy-type-variable]
9 | S = TypeVar("S", **{"bound": int})
```
# Diagnostics
```
error[invalid-legacy-type-variable]: Starred arguments are not supported in `TypeVar` creation
--> src/mdtest_snippet.py:6:18
|
5 | # error: [invalid-legacy-type-variable]
6 | T = TypeVar("T", *types)
| ^^^^^^
7 |
8 | # error: [invalid-legacy-type-variable]
|
info: rule `invalid-legacy-type-variable` is enabled by default
```
```
error[invalid-legacy-type-variable]: Starred arguments are not supported in `TypeVar` creation
--> src/mdtest_snippet.py:9:18
|
8 | # error: [invalid-legacy-type-variable]
9 | S = TypeVar("S", **{"bound": int})
| ^^^^^^^^^^^^^^^^
|
info: rule `invalid-legacy-type-variable` is enabled by default
```

View File

@@ -0,0 +1,33 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: legacy_typevars.md - Legacy typevar creation diagnostics - `TypeVar` parameter must match variable name
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/legacy_typevars.md
---
# Python source files
## mdtest_snippet.py
```
1 | from typing import TypeVar
2 |
3 | # error: [invalid-legacy-type-variable]
4 | T = TypeVar("Q")
```
# Diagnostics
```
error[invalid-legacy-type-variable]: The name of a `TypeVar` (`Q`) must match the name of the variable it is assigned to (`T`)
--> src/mdtest_snippet.py:4:1
|
3 | # error: [invalid-legacy-type-variable]
4 | T = TypeVar("Q")
| ^
|
info: rule `invalid-legacy-type-variable` is enabled by default
```

View File

@@ -12,67 +12,39 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/mro.md
## mdtest_snippet.py
```
1 | from typing_extensions import reveal_type
2 |
3 | def returns_bool() -> bool:
4 | return True
5 |
6 | class A: ...
7 | class B: ...
8 |
9 | if returns_bool():
10 | x = A
11 | else:
12 | x = B
1 | def returns_bool() -> bool:
2 | return True
3 |
4 | class A: ...
5 | class B: ...
6 |
7 | if returns_bool():
8 | x = A
9 | else:
10 | x = B
11 |
12 | reveal_type(x) # revealed: <class 'A'> | <class 'B'>
13 |
14 | reveal_type(x) # revealed: <class 'A'> | <class 'B'>
15 |
16 | # error: 11 [unsupported-base] "Unsupported class base with type `<class 'A'> | <class 'B'>`"
17 | class Foo(x): ...
18 |
19 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
14 | # error: 11 [unsupported-base] "Unsupported class base with type `<class 'A'> | <class 'B'>`"
15 | class Foo(x): ...
16 |
17 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
```
# Diagnostics
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:14:13
|
12 | x = B
13 |
14 | reveal_type(x) # revealed: <class 'A'> | <class 'B'>
| ^ `<class 'A'> | <class 'B'>`
15 |
16 | # error: 11 [unsupported-base] "Unsupported class base with type `<class 'A'> | <class 'B'>`"
|
```
```
warning[unsupported-base]: Unsupported class base with type `<class 'A'> | <class 'B'>`
--> src/mdtest_snippet.py:17:11
--> src/mdtest_snippet.py:15:11
|
16 | # error: 11 [unsupported-base] "Unsupported class base with type `<class 'A'> | <class 'B'>`"
17 | class Foo(x): ...
14 | # error: 11 [unsupported-base] "Unsupported class base with type `<class 'A'> | <class 'B'>`"
15 | class Foo(x): ...
| ^
18 |
19 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
16 |
17 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
info: ty cannot resolve a consistent MRO for class `Foo` due to this base
info: Only class objects or `Any` are supported as class bases
info: rule `unsupported-base` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:19:13
|
17 | class Foo(x): ...
18 |
19 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
| ^^^^^^^^^^^ `tuple[<class 'Foo'>, Unknown, <class 'object'>]`
|
```

View File

@@ -12,167 +12,147 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/mro.md
## mdtest_snippet.py
```
1 | from typing_extensions import reveal_type
1 | class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`"
2 |
3 | class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`"
3 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
4 |
5 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
6 |
7 | class Spam: ...
8 | class Eggs: ...
9 | class Bar: ...
10 | class Baz: ...
5 | class Spam: ...
6 | class Eggs: ...
7 | class Bar: ...
8 | class Baz: ...
9 |
10 | # fmt: off
11 |
12 | # fmt: off
13 |
14 | # error: [duplicate-base] "Duplicate base class `Spam`"
15 | # error: [duplicate-base] "Duplicate base class `Eggs`"
16 | class Ham(
17 | Spam,
18 | Eggs,
19 | Bar,
20 | Baz,
21 | Spam,
22 | Eggs,
23 | ): ...
12 | # error: [duplicate-base] "Duplicate base class `Spam`"
13 | # error: [duplicate-base] "Duplicate base class `Eggs`"
14 | class Ham(
15 | Spam,
16 | Eggs,
17 | Bar,
18 | Baz,
19 | Spam,
20 | Eggs,
21 | ): ...
22 |
23 | # fmt: on
24 |
25 | # fmt: on
25 | reveal_type(Ham.__mro__) # revealed: tuple[<class 'Ham'>, Unknown, <class 'object'>]
26 |
27 | reveal_type(Ham.__mro__) # revealed: tuple[<class 'Ham'>, Unknown, <class 'object'>]
28 |
29 | class Mushrooms: ...
30 | class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base]
27 | class Mushrooms: ...
28 | class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base]
29 |
30 | reveal_type(Omelette.__mro__) # revealed: tuple[<class 'Omelette'>, Unknown, <class 'object'>]
31 |
32 | reveal_type(Omelette.__mro__) # revealed: tuple[<class 'Omelette'>, Unknown, <class 'object'>]
32 | # fmt: off
33 |
34 | # fmt: off
35 |
36 | # error: [duplicate-base] "Duplicate base class `Eggs`"
37 | class VeryEggyOmelette(
38 | Eggs,
39 | Ham,
40 | Spam,
41 | Eggs,
42 | Mushrooms,
43 | Bar,
34 | # error: [duplicate-base] "Duplicate base class `Eggs`"
35 | class VeryEggyOmelette(
36 | Eggs,
37 | Ham,
38 | Spam,
39 | Eggs,
40 | Mushrooms,
41 | Bar,
42 | Eggs,
43 | Baz,
44 | Eggs,
45 | Baz,
46 | Eggs,
47 | ): ...
48 |
49 | # fmt: off
50 | # fmt: off
45 | ): ...
46 |
47 | # fmt: off
48 | # fmt: off
49 |
50 | class A: ...
51 |
52 | class A: ...
53 |
54 | class B( # type: ignore[duplicate-base]
55 | A,
56 | A,
57 | ): ...
58 |
59 | class C(
60 | A,
61 | A
62 | ): # type: ignore[duplicate-base]
63 | x: int
64 |
65 | # fmt: on
66 | # fmt: off
67 |
68 | # error: [duplicate-base]
69 | class D(
70 | A,
71 | # error: [unused-ignore-comment]
72 | A, # type: ignore[duplicate-base]
73 | ): ...
74 |
75 | # error: [duplicate-base]
76 | class E(
77 | A,
78 | A
79 | ):
80 | # error: [unused-ignore-comment]
81 | x: int # type: ignore[duplicate-base]
82 |
83 | # fmt: on
52 | class B( # type: ignore[duplicate-base]
53 | A,
54 | A,
55 | ): ...
56 |
57 | class C(
58 | A,
59 | A
60 | ): # type: ignore[duplicate-base]
61 | x: int
62 |
63 | # fmt: on
64 | # fmt: off
65 |
66 | # error: [duplicate-base]
67 | class D(
68 | A,
69 | # error: [unused-ignore-comment]
70 | A, # type: ignore[duplicate-base]
71 | ): ...
72 |
73 | # error: [duplicate-base]
74 | class E(
75 | A,
76 | A
77 | ):
78 | # error: [unused-ignore-comment]
79 | x: int # type: ignore[duplicate-base]
80 |
81 | # fmt: on
```
# Diagnostics
```
error[duplicate-base]: Duplicate base class `str`
--> src/mdtest_snippet.py:3:7
--> src/mdtest_snippet.py:1:7
|
1 | from typing_extensions import reveal_type
2 |
3 | class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`"
1 | class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`"
| ^^^^^^^^^^^^^
4 |
5 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
2 |
3 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
info: The definition of class `Foo` will raise `TypeError` at runtime
--> src/mdtest_snippet.py:3:11
--> src/mdtest_snippet.py:1:11
|
1 | from typing_extensions import reveal_type
2 |
3 | class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`"
1 | class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`"
| --- ^^^ Class `str` later repeated here
| |
| Class `str` first included in bases list here
4 |
5 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
2 |
3 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
|
info: rule `duplicate-base` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:5:13
|
3 | class Foo(str, str): ... # error: [duplicate-base] "Duplicate base class `str`"
4 |
5 | reveal_type(Foo.__mro__) # revealed: tuple[<class 'Foo'>, Unknown, <class 'object'>]
| ^^^^^^^^^^^ `tuple[<class 'Foo'>, Unknown, <class 'object'>]`
6 |
7 | class Spam: ...
|
```
```
error[duplicate-base]: Duplicate base class `Spam`
--> src/mdtest_snippet.py:16:7
--> src/mdtest_snippet.py:14:7
|
14 | # error: [duplicate-base] "Duplicate base class `Spam`"
15 | # error: [duplicate-base] "Duplicate base class `Eggs`"
16 | class Ham(
12 | # error: [duplicate-base] "Duplicate base class `Spam`"
13 | # error: [duplicate-base] "Duplicate base class `Eggs`"
14 | class Ham(
| _______^
17 | | Spam,
18 | | Eggs,
19 | | Bar,
20 | | Baz,
21 | | Spam,
22 | | Eggs,
23 | | ): ...
15 | | Spam,
16 | | Eggs,
17 | | Bar,
18 | | Baz,
19 | | Spam,
20 | | Eggs,
21 | | ): ...
| |_^
24 |
25 | # fmt: on
22 |
23 | # fmt: on
|
info: The definition of class `Ham` will raise `TypeError` at runtime
--> src/mdtest_snippet.py:17:5
--> src/mdtest_snippet.py:15:5
|
15 | # error: [duplicate-base] "Duplicate base class `Eggs`"
16 | class Ham(
17 | Spam,
13 | # error: [duplicate-base] "Duplicate base class `Eggs`"
14 | class Ham(
15 | Spam,
| ---- Class `Spam` first included in bases list here
18 | Eggs,
19 | Bar,
20 | Baz,
21 | Spam,
16 | Eggs,
17 | Bar,
18 | Baz,
19 | Spam,
| ^^^^ Class `Spam` later repeated here
22 | Eggs,
23 | ): ...
20 | Eggs,
21 | ): ...
|
info: rule `duplicate-base` is enabled by default
@@ -180,134 +160,106 @@ info: rule `duplicate-base` is enabled by default
```
error[duplicate-base]: Duplicate base class `Eggs`
--> src/mdtest_snippet.py:16:7
--> src/mdtest_snippet.py:14:7
|
14 | # error: [duplicate-base] "Duplicate base class `Spam`"
15 | # error: [duplicate-base] "Duplicate base class `Eggs`"
16 | class Ham(
12 | # error: [duplicate-base] "Duplicate base class `Spam`"
13 | # error: [duplicate-base] "Duplicate base class `Eggs`"
14 | class Ham(
| _______^
17 | | Spam,
18 | | Eggs,
19 | | Bar,
20 | | Baz,
21 | | Spam,
22 | | Eggs,
23 | | ): ...
15 | | Spam,
16 | | Eggs,
17 | | Bar,
18 | | Baz,
19 | | Spam,
20 | | Eggs,
21 | | ): ...
| |_^
24 |
25 | # fmt: on
22 |
23 | # fmt: on
|
info: The definition of class `Ham` will raise `TypeError` at runtime
--> src/mdtest_snippet.py:18:5
--> src/mdtest_snippet.py:16:5
|
16 | class Ham(
17 | Spam,
18 | Eggs,
14 | class Ham(
15 | Spam,
16 | Eggs,
| ---- Class `Eggs` first included in bases list here
19 | Bar,
20 | Baz,
21 | Spam,
22 | Eggs,
17 | Bar,
18 | Baz,
19 | Spam,
20 | Eggs,
| ^^^^ Class `Eggs` later repeated here
23 | ): ...
21 | ): ...
|
info: rule `duplicate-base` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:27:13
|
25 | # fmt: on
26 |
27 | reveal_type(Ham.__mro__) # revealed: tuple[<class 'Ham'>, Unknown, <class 'object'>]
| ^^^^^^^^^^^ `tuple[<class 'Ham'>, Unknown, <class 'object'>]`
28 |
29 | class Mushrooms: ...
|
```
```
error[duplicate-base]: Duplicate base class `Mushrooms`
--> src/mdtest_snippet.py:30:7
--> src/mdtest_snippet.py:28:7
|
29 | class Mushrooms: ...
30 | class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base]
27 | class Mushrooms: ...
28 | class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
31 |
32 | reveal_type(Omelette.__mro__) # revealed: tuple[<class 'Omelette'>, Unknown, <class 'object'>]
29 |
30 | reveal_type(Omelette.__mro__) # revealed: tuple[<class 'Omelette'>, Unknown, <class 'object'>]
|
info: The definition of class `Omelette` will raise `TypeError` at runtime
--> src/mdtest_snippet.py:30:28
--> src/mdtest_snippet.py:28:28
|
29 | class Mushrooms: ...
30 | class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base]
27 | class Mushrooms: ...
28 | class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base]
| --------- ^^^^^^^^^ Class `Mushrooms` later repeated here
| |
| Class `Mushrooms` first included in bases list here
31 |
32 | reveal_type(Omelette.__mro__) # revealed: tuple[<class 'Omelette'>, Unknown, <class 'object'>]
29 |
30 | reveal_type(Omelette.__mro__) # revealed: tuple[<class 'Omelette'>, Unknown, <class 'object'>]
|
info: rule `duplicate-base` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:32:13
|
30 | class Omelette(Spam, Eggs, Mushrooms, Mushrooms): ... # error: [duplicate-base]
31 |
32 | reveal_type(Omelette.__mro__) # revealed: tuple[<class 'Omelette'>, Unknown, <class 'object'>]
| ^^^^^^^^^^^^^^^^ `tuple[<class 'Omelette'>, Unknown, <class 'object'>]`
33 |
34 | # fmt: off
|
```
```
error[duplicate-base]: Duplicate base class `Eggs`
--> src/mdtest_snippet.py:37:7
--> src/mdtest_snippet.py:35:7
|
36 | # error: [duplicate-base] "Duplicate base class `Eggs`"
37 | class VeryEggyOmelette(
34 | # error: [duplicate-base] "Duplicate base class `Eggs`"
35 | class VeryEggyOmelette(
| _______^
38 | | Eggs,
39 | | Ham,
40 | | Spam,
41 | | Eggs,
42 | | Mushrooms,
43 | | Bar,
36 | | Eggs,
37 | | Ham,
38 | | Spam,
39 | | Eggs,
40 | | Mushrooms,
41 | | Bar,
42 | | Eggs,
43 | | Baz,
44 | | Eggs,
45 | | Baz,
46 | | Eggs,
47 | | ): ...
45 | | ): ...
| |_^
48 |
49 | # fmt: off
46 |
47 | # fmt: off
|
info: The definition of class `VeryEggyOmelette` will raise `TypeError` at runtime
--> src/mdtest_snippet.py:38:5
--> src/mdtest_snippet.py:36:5
|
36 | # error: [duplicate-base] "Duplicate base class `Eggs`"
37 | class VeryEggyOmelette(
38 | Eggs,
34 | # error: [duplicate-base] "Duplicate base class `Eggs`"
35 | class VeryEggyOmelette(
36 | Eggs,
| ---- Class `Eggs` first included in bases list here
39 | Ham,
40 | Spam,
41 | Eggs,
37 | Ham,
38 | Spam,
39 | Eggs,
| ^^^^ Class `Eggs` later repeated here
42 | Mushrooms,
43 | Bar,
40 | Mushrooms,
41 | Bar,
42 | Eggs,
| ^^^^ Class `Eggs` later repeated here
43 | Baz,
44 | Eggs,
| ^^^^ Class `Eggs` later repeated here
45 | Baz,
46 | Eggs,
| ^^^^ Class `Eggs` later repeated here
47 | ): ...
45 | ): ...
|
info: rule `duplicate-base` is enabled by default
@@ -315,30 +267,30 @@ info: rule `duplicate-base` is enabled by default
```
error[duplicate-base]: Duplicate base class `A`
--> src/mdtest_snippet.py:69:7
--> src/mdtest_snippet.py:67:7
|
68 | # error: [duplicate-base]
69 | class D(
66 | # error: [duplicate-base]
67 | class D(
| _______^
70 | | A,
71 | | # error: [unused-ignore-comment]
72 | | A, # type: ignore[duplicate-base]
73 | | ): ...
68 | | A,
69 | | # error: [unused-ignore-comment]
70 | | A, # type: ignore[duplicate-base]
71 | | ): ...
| |_^
74 |
75 | # error: [duplicate-base]
72 |
73 | # error: [duplicate-base]
|
info: The definition of class `D` will raise `TypeError` at runtime
--> src/mdtest_snippet.py:70:5
--> src/mdtest_snippet.py:68:5
|
68 | # error: [duplicate-base]
69 | class D(
70 | A,
66 | # error: [duplicate-base]
67 | class D(
68 | A,
| - Class `A` first included in bases list here
71 | # error: [unused-ignore-comment]
72 | A, # type: ignore[duplicate-base]
69 | # error: [unused-ignore-comment]
70 | A, # type: ignore[duplicate-base]
| ^ Class `A` later repeated here
73 | ): ...
71 | ): ...
|
info: rule `duplicate-base` is enabled by default
@@ -346,42 +298,42 @@ info: rule `duplicate-base` is enabled by default
```
info[unused-ignore-comment]
--> src/mdtest_snippet.py:72:9
--> src/mdtest_snippet.py:70:9
|
70 | A,
71 | # error: [unused-ignore-comment]
72 | A, # type: ignore[duplicate-base]
68 | A,
69 | # error: [unused-ignore-comment]
70 | A, # type: ignore[duplicate-base]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Unused blanket `type: ignore` directive
73 | ): ...
71 | ): ...
|
```
```
error[duplicate-base]: Duplicate base class `A`
--> src/mdtest_snippet.py:76:7
--> src/mdtest_snippet.py:74:7
|
75 | # error: [duplicate-base]
76 | class E(
73 | # error: [duplicate-base]
74 | class E(
| _______^
77 | | A,
78 | | A
79 | | ):
75 | | A,
76 | | A
77 | | ):
| |_^
80 | # error: [unused-ignore-comment]
81 | x: int # type: ignore[duplicate-base]
78 | # error: [unused-ignore-comment]
79 | x: int # type: ignore[duplicate-base]
|
info: The definition of class `E` will raise `TypeError` at runtime
--> src/mdtest_snippet.py:77:5
--> src/mdtest_snippet.py:75:5
|
75 | # error: [duplicate-base]
76 | class E(
77 | A,
73 | # error: [duplicate-base]
74 | class E(
75 | A,
| - Class `A` first included in bases list here
78 | A
76 | A
| ^ Class `A` later repeated here
79 | ):
80 | # error: [unused-ignore-comment]
77 | ):
78 | # error: [unused-ignore-comment]
|
info: rule `duplicate-base` is enabled by default
@@ -389,14 +341,14 @@ info: rule `duplicate-base` is enabled by default
```
info[unused-ignore-comment]
--> src/mdtest_snippet.py:81:13
--> src/mdtest_snippet.py:79:13
|
79 | ):
80 | # error: [unused-ignore-comment]
81 | x: int # type: ignore[duplicate-base]
77 | ):
78 | # error: [unused-ignore-comment]
79 | x: int # type: ignore[duplicate-base]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Unused blanket `type: ignore` directive
82 |
83 | # fmt: on
80 |
81 | # fmt: on
|
```

View File

@@ -136,48 +136,3 @@ info: (x: B, /, **kwargs: int) -> B
info: rule `no-matching-overload` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:8:9
|
6 | # error: [no-matching-overload]
7 | # revealed: Unknown
8 | / f(
9 | | A(),
10 | | a1=a,
11 | | a2=a,
12 | | a3=a,
13 | | a4=a,
14 | | a5=a,
15 | | a6=a,
16 | | a7=a,
17 | | a8=a,
18 | | a9=a,
19 | | a10=a,
20 | | a11=a,
21 | | a12=a,
22 | | a13=a,
23 | | a14=a,
24 | | a15=a,
25 | | a16=a,
26 | | a17=a,
27 | | a18=a,
28 | | a19=a,
29 | | a20=a,
30 | | a21=a,
31 | | a22=a,
32 | | a23=a,
33 | | a24=a,
34 | | a25=a,
35 | | a26=a,
36 | | a27=a,
37 | | a28=a,
38 | | a29=a,
39 | | a30=a,
40 | | )
| |_________^ `Unknown`
41 | )
|
```

View File

@@ -12,7 +12,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/protocols.md
## mdtest_snippet.py
```
1 | from typing_extensions import Protocol, reveal_type
1 | from typing_extensions import Protocol
2 |
3 | # error: [call-non-callable]
4 | reveal_type(Protocol()) # revealed: Unknown
@@ -36,9 +36,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/protocols.md
22 |
23 | reveal_type(SubclassOfGenericProtocol[int]()) # revealed: SubclassOfGenericProtocol[int]
24 | def f(x: type[MyProtocol]):
25 | # TODO: add a `reveal_type` call here once it's no longer a `Todo` type
26 | # (which doesn't work well with snapshots)
27 | x()
25 | reveal_type(x()) # revealed: @Todo(type[T] for protocols)
```
# Diagnostics
@@ -57,19 +55,6 @@ info: rule `call-non-callable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:4:13
|
3 | # error: [call-non-callable]
4 | reveal_type(Protocol()) # revealed: Unknown
| ^^^^^^^^^^ `Unknown`
5 |
6 | class MyProtocol(Protocol):
|
```
```
error[call-non-callable]: Cannot instantiate class `MyProtocol`
--> src/mdtest_snippet.py:10:13
@@ -93,19 +78,6 @@ info: rule `call-non-callable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:10:13
|
9 | # error: [call-non-callable] "Cannot instantiate class `MyProtocol`"
10 | reveal_type(MyProtocol()) # revealed: MyProtocol
| ^^^^^^^^^^^^ `MyProtocol`
11 |
12 | class GenericProtocol[T](Protocol):
|
```
```
error[call-non-callable]: Cannot instantiate class `GenericProtocol`
--> src/mdtest_snippet.py:16:13
@@ -127,43 +99,3 @@ info: Protocol classes cannot be instantiated
info: rule `call-non-callable` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:16:13
|
15 | # error: [call-non-callable] "Cannot instantiate class `GenericProtocol`"
16 | reveal_type(GenericProtocol[int]()) # revealed: GenericProtocol[int]
| ^^^^^^^^^^^^^^^^^^^^^^ `GenericProtocol[int]`
17 | class SubclassOfMyProtocol(MyProtocol): ...
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:19:13
|
17 | class SubclassOfMyProtocol(MyProtocol): ...
18 |
19 | reveal_type(SubclassOfMyProtocol()) # revealed: SubclassOfMyProtocol
| ^^^^^^^^^^^^^^^^^^^^^^ `SubclassOfMyProtocol`
20 |
21 | class SubclassOfGenericProtocol[T](GenericProtocol[T]): ...
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:23:13
|
21 | class SubclassOfGenericProtocol[T](GenericProtocol[T]): ...
22 |
23 | reveal_type(SubclassOfGenericProtocol[int]()) # revealed: SubclassOfGenericProtocol[int]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `SubclassOfGenericProtocol[int]`
24 | def f(x: type[MyProtocol]):
25 | # TODO: add a `reveal_type` call here once it's no longer a `Todo` type
|
```

View File

@@ -12,7 +12,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/protocols.md
## mdtest_snippet.py
```
1 | from typing_extensions import Protocol, reveal_type
1 | from typing_extensions import Protocol
2 |
3 | class HasX(Protocol):
4 | x: int
@@ -69,7 +69,7 @@ error[invalid-argument-type]: Class `HasX` cannot be used as the second argument
info: `HasX` is declared as a protocol class, but it is not declared as runtime-checkable
--> src/mdtest_snippet.py:3:7
|
1 | from typing_extensions import Protocol, reveal_type
1 | from typing_extensions import Protocol
2 |
3 | class HasX(Protocol):
| ^^^^^^^^^^^^^^ `HasX` declared here
@@ -81,34 +81,6 @@ info: rule `invalid-argument-type` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:8:21
|
6 | def f(arg: object, arg2: type):
7 | if isinstance(arg, HasX): # error: [invalid-argument-type]
8 | reveal_type(arg) # revealed: HasX
| ^^^ `HasX`
9 | else:
10 | reveal_type(arg) # revealed: ~HasX
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:10:21
|
8 | reveal_type(arg) # revealed: HasX
9 | else:
10 | reveal_type(arg) # revealed: ~HasX
| ^^^ `~HasX`
11 |
12 | if issubclass(arg2, HasX): # error: [invalid-argument-type]
|
```
```
error[invalid-argument-type]: Class `HasX` cannot be used as the second argument to `issubclass`
--> src/mdtest_snippet.py:12:8
@@ -123,7 +95,7 @@ error[invalid-argument-type]: Class `HasX` cannot be used as the second argument
info: `HasX` is declared as a protocol class, but it is not declared as runtime-checkable
--> src/mdtest_snippet.py:3:7
|
1 | from typing_extensions import Protocol, reveal_type
1 | from typing_extensions import Protocol
2 |
3 | class HasX(Protocol):
| ^^^^^^^^^^^^^^ `HasX` declared here
@@ -134,110 +106,3 @@ info: See https://docs.python.org/3/library/typing.html#typing.runtime_checkable
info: rule `invalid-argument-type` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:13:21
|
12 | if issubclass(arg2, HasX): # error: [invalid-argument-type]
13 | reveal_type(arg2) # revealed: type[HasX]
| ^^^^ `type[HasX]`
14 | else:
15 | reveal_type(arg2) # revealed: type & ~type[HasX]
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:15:21
|
13 | reveal_type(arg2) # revealed: type[HasX]
14 | else:
15 | reveal_type(arg2) # revealed: type & ~type[HasX]
| ^^^^ `type & ~type[HasX]`
16 | from typing import runtime_checkable
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:24:21
|
22 | def f(arg: object):
23 | if isinstance(arg, RuntimeCheckableHasX): # no error!
24 | reveal_type(arg) # revealed: RuntimeCheckableHasX
| ^^^ `RuntimeCheckableHasX`
25 | else:
26 | reveal_type(arg) # revealed: ~RuntimeCheckableHasX
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:26:21
|
24 | reveal_type(arg) # revealed: RuntimeCheckableHasX
25 | else:
26 | reveal_type(arg) # revealed: ~RuntimeCheckableHasX
| ^^^ `~RuntimeCheckableHasX`
27 | @runtime_checkable
28 | class OnlyMethodMembers(Protocol):
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:33:21
|
31 | def f(arg1: type, arg2: type):
32 | if issubclass(arg1, RuntimeCheckableHasX): # TODO: should emit an error here (has non-method members)
33 | reveal_type(arg1) # revealed: type[RuntimeCheckableHasX]
| ^^^^ `type[RuntimeCheckableHasX]`
34 | else:
35 | reveal_type(arg1) # revealed: type & ~type[RuntimeCheckableHasX]
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:35:21
|
33 | reveal_type(arg1) # revealed: type[RuntimeCheckableHasX]
34 | else:
35 | reveal_type(arg1) # revealed: type & ~type[RuntimeCheckableHasX]
| ^^^^ `type & ~type[RuntimeCheckableHasX]`
36 |
37 | if issubclass(arg2, OnlyMethodMembers): # no error!
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:38:21
|
37 | if issubclass(arg2, OnlyMethodMembers): # no error!
38 | reveal_type(arg2) # revealed: type[OnlyMethodMembers]
| ^^^^ `type[OnlyMethodMembers]`
39 | else:
40 | reveal_type(arg2) # revealed: type & ~type[OnlyMethodMembers]
|
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:40:21
|
38 | reveal_type(arg2) # revealed: type[OnlyMethodMembers]
39 | else:
40 | reveal_type(arg2) # revealed: type & ~type[OnlyMethodMembers]
| ^^^^ `type & ~type[OnlyMethodMembers]`
|
```

View File

@@ -13,7 +13,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/protocols.md
```
1 | import sys
2 | from typing_extensions import Protocol, get_protocol_members, reveal_type
2 | from typing_extensions import Protocol, get_protocol_members
3 |
4 | class Foo(Protocol):
5 | if sys.version_info >= (3, 10):
@@ -43,7 +43,7 @@ warning[ambiguous-protocol-member]: Cannot assign to undeclared variable in the
info: Assigning to an undeclared variable in a protocol class leads to an ambiguous interface
--> src/mdtest_snippet.py:4:7
|
2 | from typing_extensions import Protocol, get_protocol_members, reveal_type
2 | from typing_extensions import Protocol, get_protocol_members
3 |
4 | class Foo(Protocol):
| ^^^^^^^^^^^^^ `Foo` declared as a protocol here
@@ -54,15 +54,3 @@ info: No declarations found for `e` in the body of `Foo` or any of its superclas
info: rule `ambiguous-protocol-member` is enabled by default
```
```
info[revealed-type]: Revealed type
--> src/mdtest_snippet.py:14:13
|
12 | def f(self) -> None: ...
13 |
14 | reveal_type(get_protocol_members(Foo)) # revealed: frozenset[Literal["d", "e", "f"]]
| ^^^^^^^^^^^^^^^^^^^^^^^^^ `frozenset[Literal["d", "e", "f"]]`
|
```

View File

@@ -0,0 +1,217 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: super.md - Super - Basic Usage - Explicit Super Object
mdtest path: crates/ty_python_semantic/resources/mdtest/class/super.md
---
# Python source files
## mdtest_snippet.py
```
1 | class A:
2 | def a(self): ...
3 | aa: int = 1
4 |
5 | class B(A):
6 | def b(self): ...
7 | bb: int = 2
8 |
9 | class C(B):
10 | def c(self): ...
11 | cc: int = 3
12 |
13 | reveal_type(C.__mro__) # revealed: tuple[<class 'C'>, <class 'B'>, <class 'A'>, <class 'object'>]
14 |
15 | super(C, C()).a
16 | super(C, C()).b
17 | super(C, C()).c # error: [unresolved-attribute]
18 |
19 | super(B, C()).a
20 | super(B, C()).b # error: [unresolved-attribute]
21 | super(B, C()).c # error: [unresolved-attribute]
22 |
23 | super(A, C()).a # error: [unresolved-attribute]
24 | super(A, C()).b # error: [unresolved-attribute]
25 | super(A, C()).c # error: [unresolved-attribute]
26 |
27 | reveal_type(super(C, C()).a) # revealed: bound method C.a() -> Unknown
28 | reveal_type(super(C, C()).b) # revealed: bound method C.b() -> Unknown
29 | reveal_type(super(C, C()).aa) # revealed: int
30 | reveal_type(super(C, C()).bb) # revealed: int
31 | import types
32 | from typing_extensions import Callable, TypeIs, Literal, TypedDict
33 |
34 | def f(): ...
35 |
36 | class Foo[T]:
37 | def method(self): ...
38 | @property
39 | def some_property(self): ...
40 |
41 | type Alias = int
42 |
43 | class SomeTypedDict(TypedDict):
44 | x: int
45 | y: bytes
46 |
47 | # revealed: <super: <class 'object'>, FunctionType>
48 | reveal_type(super(object, f))
49 | # revealed: <super: <class 'object'>, WrapperDescriptorType>
50 | reveal_type(super(object, types.FunctionType.__get__))
51 | # revealed: <super: <class 'object'>, GenericAlias>
52 | reveal_type(super(object, Foo[int]))
53 | # revealed: <super: <class 'object'>, _SpecialForm>
54 | reveal_type(super(object, Literal))
55 | # revealed: <super: <class 'object'>, TypeAliasType>
56 | reveal_type(super(object, Alias))
57 | # revealed: <super: <class 'object'>, MethodType>
58 | reveal_type(super(object, Foo().method))
59 | # revealed: <super: <class 'object'>, property>
60 | reveal_type(super(object, Foo.some_property))
61 |
62 | def g(x: object) -> TypeIs[list[object]]:
63 | return isinstance(x, list)
64 |
65 | def _(x: object, y: SomeTypedDict, z: Callable[[int, str], bool]):
66 | if hasattr(x, "bar"):
67 | # revealed: <Protocol with members 'bar'>
68 | reveal_type(x)
69 | # error: [invalid-super-argument]
70 | # revealed: Unknown
71 | reveal_type(super(object, x))
72 |
73 | # error: [invalid-super-argument]
74 | # revealed: Unknown
75 | reveal_type(super(object, z))
76 |
77 | is_list = g(x)
78 | # revealed: TypeIs[list[object] @ x]
79 | reveal_type(is_list)
80 | # revealed: <super: <class 'object'>, bool>
81 | reveal_type(super(object, is_list))
82 |
83 | # revealed: <super: <class 'object'>, dict[Literal["x", "y"], int | bytes]>
84 | reveal_type(super(object, y))
```
# Diagnostics
```
error[unresolved-attribute]: Type `<super: <class 'C'>, C>` has no attribute `c`
--> src/mdtest_snippet.py:17:1
|
15 | super(C, C()).a
16 | super(C, C()).b
17 | super(C, C()).c # error: [unresolved-attribute]
| ^^^^^^^^^^^^^^^
18 |
19 | super(B, C()).a
|
info: rule `unresolved-attribute` is enabled by default
```
```
error[unresolved-attribute]: Type `<super: <class 'B'>, C>` has no attribute `b`
--> src/mdtest_snippet.py:20:1
|
19 | super(B, C()).a
20 | super(B, C()).b # error: [unresolved-attribute]
| ^^^^^^^^^^^^^^^
21 | super(B, C()).c # error: [unresolved-attribute]
|
info: rule `unresolved-attribute` is enabled by default
```
```
error[unresolved-attribute]: Type `<super: <class 'B'>, C>` has no attribute `c`
--> src/mdtest_snippet.py:21:1
|
19 | super(B, C()).a
20 | super(B, C()).b # error: [unresolved-attribute]
21 | super(B, C()).c # error: [unresolved-attribute]
| ^^^^^^^^^^^^^^^
22 |
23 | super(A, C()).a # error: [unresolved-attribute]
|
info: rule `unresolved-attribute` is enabled by default
```
```
error[unresolved-attribute]: Type `<super: <class 'A'>, C>` has no attribute `a`
--> src/mdtest_snippet.py:23:1
|
21 | super(B, C()).c # error: [unresolved-attribute]
22 |
23 | super(A, C()).a # error: [unresolved-attribute]
| ^^^^^^^^^^^^^^^
24 | super(A, C()).b # error: [unresolved-attribute]
25 | super(A, C()).c # error: [unresolved-attribute]
|
info: rule `unresolved-attribute` is enabled by default
```
```
error[unresolved-attribute]: Type `<super: <class 'A'>, C>` has no attribute `b`
--> src/mdtest_snippet.py:24:1
|
23 | super(A, C()).a # error: [unresolved-attribute]
24 | super(A, C()).b # error: [unresolved-attribute]
| ^^^^^^^^^^^^^^^
25 | super(A, C()).c # error: [unresolved-attribute]
|
info: rule `unresolved-attribute` is enabled by default
```
```
error[unresolved-attribute]: Type `<super: <class 'A'>, C>` has no attribute `c`
--> src/mdtest_snippet.py:25:1
|
23 | super(A, C()).a # error: [unresolved-attribute]
24 | super(A, C()).b # error: [unresolved-attribute]
25 | super(A, C()).c # error: [unresolved-attribute]
| ^^^^^^^^^^^^^^^
26 |
27 | reveal_type(super(C, C()).a) # revealed: bound method C.a() -> Unknown
|
info: rule `unresolved-attribute` is enabled by default
```
```
error[invalid-super-argument]: `<Protocol with members 'bar'>` is an abstract/structural type in `super(<class 'object'>, <Protocol with members 'bar'>)` call
--> src/mdtest_snippet.py:71:21
|
69 | # error: [invalid-super-argument]
70 | # revealed: Unknown
71 | reveal_type(super(object, x))
| ^^^^^^^^^^^^^^^^
72 |
73 | # error: [invalid-super-argument]
|
info: rule `invalid-super-argument` is enabled by default
```
```
error[invalid-super-argument]: `(int, str, /) -> bool` is an abstract/structural type in `super(<class 'object'>, (int, str, /) -> bool)` call
--> src/mdtest_snippet.py:75:17
|
73 | # error: [invalid-super-argument]
74 | # revealed: Unknown
75 | reveal_type(super(object, z))
| ^^^^^^^^^^^^^^^^
76 |
77 | is_list = g(x)
|
info: rule `invalid-super-argument` is enabled by default
```

View File

@@ -0,0 +1,214 @@
---
source: crates/ty_test/src/lib.rs
expression: snapshot
---
---
mdtest name: super.md - Super - Basic Usage - Implicit Super Object
mdtest path: crates/ty_python_semantic/resources/mdtest/class/super.md
---
# Python source files
## mdtest_snippet.py
```
1 | from __future__ import annotations
2 |
3 | class A:
4 | def __init__(self, a: int): ...
5 | @classmethod
6 | def f(cls): ...
7 |
8 | class B(A):
9 | def __init__(self, a: int):
10 | # TODO: Once `Self` is supported, this should be `<super: <class 'B'>, B>`
11 | reveal_type(super()) # revealed: <super: <class 'B'>, Unknown>
12 | reveal_type(super(object, super())) # revealed: <super: <class 'object'>, super>
13 | super().__init__(a)
14 |
15 | @classmethod
16 | def f(cls):
17 | # TODO: Once `Self` is supported, this should be `<super: <class 'B'>, <class 'B'>>`
18 | reveal_type(super()) # revealed: <super: <class 'B'>, Unknown>
19 | super().f()
20 |
21 | super(B, B(42)).__init__(42)
22 | super(B, B).f()
23 | import enum
24 | from typing import Any, Self, Never, Protocol, Callable
25 | from ty_extensions import Intersection
26 |
27 | class BuilderMeta(type):
28 | def __new__(
29 | cls: type[Any],
30 | name: str,
31 | bases: tuple[type, ...],
32 | dct: dict[str, Any],
33 | ) -> BuilderMeta:
34 | # revealed: <super: <class 'BuilderMeta'>, Any>
35 | s = reveal_type(super())
36 | # revealed: Any
37 | return reveal_type(s.__new__(cls, name, bases, dct))
38 |
39 | class BuilderMeta2(type):
40 | def __new__(
41 | cls: type[BuilderMeta2],
42 | name: str,
43 | bases: tuple[type, ...],
44 | dct: dict[str, Any],
45 | ) -> BuilderMeta2:
46 | # revealed: <super: <class 'BuilderMeta2'>, <class 'BuilderMeta2'>>
47 | s = reveal_type(super())
48 | # TODO: should be `BuilderMeta2` (needs https://github.com/astral-sh/ty/issues/501)
49 | # revealed: Unknown
50 | return reveal_type(s.__new__(cls, name, bases, dct))
51 |
52 | class Foo[T]:
53 | x: T
54 |
55 | def method(self: Any):
56 | reveal_type(super()) # revealed: <super: <class 'Foo'>, Any>
57 |
58 | if isinstance(self, Foo):
59 | reveal_type(super()) # revealed: <super: <class 'Foo'>, Any>
60 |
61 | def method2(self: Foo[T]):
62 | # revealed: <super: <class 'Foo'>, Foo[T@Foo]>
63 | reveal_type(super())
64 |
65 | def method3(self: Foo):
66 | # revealed: <super: <class 'Foo'>, Foo[Unknown]>
67 | reveal_type(super())
68 |
69 | def method4(self: Self):
70 | # revealed: <super: <class 'Foo'>, Foo[T@Foo]>
71 | reveal_type(super())
72 |
73 | def method5[S: Foo[int]](self: S, other: S) -> S:
74 | # revealed: <super: <class 'Foo'>, Foo[int]>
75 | reveal_type(super())
76 | return self
77 |
78 | def method6[S: (Foo[int], Foo[str])](self: S, other: S) -> S:
79 | # revealed: <super: <class 'Foo'>, Foo[int]> | <super: <class 'Foo'>, Foo[str]>
80 | reveal_type(super())
81 | return self
82 |
83 | def method7[S](self: S, other: S) -> S:
84 | # error: [invalid-super-argument]
85 | # revealed: Unknown
86 | reveal_type(super())
87 | return self
88 |
89 | def method8[S: int](self: S, other: S) -> S:
90 | # error: [invalid-super-argument]
91 | # revealed: Unknown
92 | reveal_type(super())
93 | return self
94 |
95 | def method9[S: (int, str)](self: S, other: S) -> S:
96 | # error: [invalid-super-argument]
97 | # revealed: Unknown
98 | reveal_type(super())
99 | return self
100 |
101 | def method10[S: Callable[..., str]](self: S, other: S) -> S:
102 | # error: [invalid-super-argument]
103 | # revealed: Unknown
104 | reveal_type(super())
105 | return self
106 |
107 | type Alias = Bar
108 |
109 | class Bar:
110 | def method(self: Alias):
111 | # revealed: <super: <class 'Bar'>, Bar>
112 | reveal_type(super())
113 |
114 | def pls_dont_call_me(self: Never):
115 | # revealed: <super: <class 'Bar'>, Unknown>
116 | reveal_type(super())
117 |
118 | def only_call_me_on_callable_subclasses(self: Intersection[Bar, Callable[..., object]]):
119 | # revealed: <super: <class 'Bar'>, Bar>
120 | reveal_type(super())
121 |
122 | class P(Protocol):
123 | def method(self: P):
124 | # revealed: <super: <class 'P'>, P>
125 | reveal_type(super())
126 |
127 | class E(enum.Enum):
128 | X = 1
129 |
130 | def method(self: E):
131 | match self:
132 | case E.X:
133 | # revealed: <super: <class 'E'>, E>
134 | reveal_type(super())
```
# Diagnostics
```
error[invalid-super-argument]: `S@method7` is not an instance or subclass of `<class 'Foo'>` in `super(<class 'Foo'>, S@method7)` call
--> src/mdtest_snippet.py:86:21
|
84 | # error: [invalid-super-argument]
85 | # revealed: Unknown
86 | reveal_type(super())
| ^^^^^^^
87 | return self
|
info: Type variable `S` has `object` as its implicit upper bound
info: `object` is not an instance or subclass of `<class 'Foo'>`
info: rule `invalid-super-argument` is enabled by default
```
```
error[invalid-super-argument]: `S@method8` is not an instance or subclass of `<class 'Foo'>` in `super(<class 'Foo'>, S@method8)` call
--> src/mdtest_snippet.py:92:21
|
90 | # error: [invalid-super-argument]
91 | # revealed: Unknown
92 | reveal_type(super())
| ^^^^^^^
93 | return self
|
info: Type variable `S` has upper bound `int`
info: `int` is not an instance or subclass of `<class 'Foo'>`
info: rule `invalid-super-argument` is enabled by default
```
```
error[invalid-super-argument]: `S@method9` is not an instance or subclass of `<class 'Foo'>` in `super(<class 'Foo'>, S@method9)` call
--> src/mdtest_snippet.py:98:21
|
96 | # error: [invalid-super-argument]
97 | # revealed: Unknown
98 | reveal_type(super())
| ^^^^^^^
99 | return self
|
info: Type variable `S` has constraints `int, str`
info: `int | str` is not an instance or subclass of `<class 'Foo'>`
info: rule `invalid-super-argument` is enabled by default
```
```
error[invalid-super-argument]: `S@method10` is a type variable with an abstract/structural type as its bounds or constraints, in `super(<class 'Foo'>, S@method10)` call
--> src/mdtest_snippet.py:104:21
|
102 | # error: [invalid-super-argument]
103 | # revealed: Unknown
104 | reveal_type(super())
| ^^^^^^^
105 | return self
|
info: Type variable `S` has upper bound `(...) -> str`
info: rule `invalid-super-argument` is enabled by default
```

View File

@@ -493,16 +493,14 @@ def _(
c5: CallableTypeOf[Foo(42).__call__],
c6: CallableTypeOf[Foo(42).returns_self],
c7: CallableTypeOf[Foo.class_method],
c8: CallableTypeOf[Foo(42)],
) -> None:
reveal_type(c1) # revealed: () -> Unknown
reveal_type(c2) # revealed: () -> int
reveal_type(c3) # revealed: (x: int, y: str) -> None
# TODO: should be `(x: int) -> Foo`
reveal_type(c4) # revealed: (...) -> Foo
reveal_type(c4) # revealed: (x: int) -> Foo
reveal_type(c5) # revealed: (x: int) -> str
reveal_type(c6) # revealed: (x: int) -> Foo
reveal_type(c7) # revealed: (x: int) -> Foo
reveal_type(c8) # revealed: (x: int) -> str
```

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