Compare commits

...

19 Commits

Author SHA1 Message Date
Charlie Marsh
a3394a7167 [ty] Apply @runtime_checkable across ancestors 2026-01-12 08:54:10 -05:00
Charlie Marsh
f1db842821 [ty] Avoid panic for comparison on synthesized variants (#22509)
## Summary

Like `ProtocolInstance`, we now use `left.cmp(right)` by deriving
`PartialOrd` and `Ord`. IIUC, this uses Salsa ID for Salsa-interned
types, but avoids `None.cmp(None)` for synthesized variants.

Closes https://github.com/astral-sh/ty/issues/2451.
2026-01-12 13:35:56 +00:00
Alex Waygood
5a3deee353 [ty] Fix incorrect narrowing for if type(x) == y (#22531) 2026-01-12 12:26:47 +00:00
Dex Devlon
e15f88ff21 [ty] Fix contravariant type variable bound checking in specialization inference (#22488)
## Summary

Correctly handle upper bounds for contravariant type variables during
specialization inference. Previously, the type checker incorrectly
applied covariant subtyping rules, requiring the actual type to directly
satisfy the bound rather than checking for a valid intersection.

In contravariant positions, subtyping relationships are inverted. The
bug caused valid code like `f(x: Contra[str])` where `f` expects
`Contra[T: int]` to be incorrectly rejected, when it should solve `T` to
`Never` (the intersection of `int` and `str`).

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

## Details

- Added `is_contravariant()` helper to `TypeVarVariance` in
`variance.rs`
- Updated `SpecializationBuilder::infer_map_impl` in `generics.rs` to
treat bounds and constraints differently based on variance:
  * Skip immediate `ty <: bound` check for contravariant upper bounds
* Flip constraint check to `constraint <: ty` for contravariant
positions
- Added test case for bounded contravariant type variables in
`variance.md`
- All 308 mdtest cases pass & 150 ty_python_semantic unit tests pass

---------

Co-authored-by: Douglas Creager <dcreager@dcreager.net>
2026-01-12 05:00:35 -05:00
renovate[bot]
a559275c3e Update prek dependencies (#22526)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Micha Reiser <micha@reiser.io>
2026-01-12 08:11:01 +00:00
renovate[bot]
95199c4217 Update astral-sh/setup-uv action to v7.2.0 (#22523)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [astral-sh/setup-uv](https://redirect.github.com/astral-sh/setup-uv) |
action | minor | `v7.1.6` → `v7.2.0` |

---

### Release Notes

<details>
<summary>astral-sh/setup-uv (astral-sh/setup-uv)</summary>

###
[`v7.2.0`](https://redirect.github.com/astral-sh/setup-uv/releases/tag/v7.2.0):
🌈 add outputs python-version and python-cache-hit

[Compare
Source](https://redirect.github.com/astral-sh/setup-uv/compare/v7.1.6...v7.2.0)

##### Changes

Among some minor typo fixes and quality of life features for developers
of actions the main feature of this release are new outputs:

- **python-version:** The Python version that was set (same content as
existing `UV_PYTHON`)
- **python-cache-hit:** A boolean value to indicate the Python cache
entry was found

While implementing this it became clear, that it is easier to handle the
Python binaries in a separate cache entry. The added benefit for users
is that the "normal" cache containing the dependencies can be used in
all runs no matter if these cache the Python binaries or not.

> \[!NOTE]\
> This release will invalidate caches that contain the Python binaries.
This happens a single time.

##### 🐛 Bug fixes

- chore: remove stray space from UV\_PYTHON\_INSTALL\_DIR message
[@&#8203;akx](https://redirect.github.com/akx)
([#&#8203;720](https://redirect.github.com/astral-sh/setup-uv/issues/720))

##### 🚀 Enhancements

- add outputs python-version and python-cache-hit
[@&#8203;eifinger](https://redirect.github.com/eifinger)
([#&#8203;728](https://redirect.github.com/astral-sh/setup-uv/issues/728))
- Add action typings with validation
[@&#8203;krzema12](https://redirect.github.com/krzema12)
([#&#8203;721](https://redirect.github.com/astral-sh/setup-uv/issues/721))

##### 🧰 Maintenance

- fix: use uv\_build backend for old-python-constraint-project
[@&#8203;eifinger](https://redirect.github.com/eifinger)
([#&#8203;729](https://redirect.github.com/astral-sh/setup-uv/issues/729))
- chore: update known checksums for 0.9.22
@&#8203;[github-actions\[bot\]](https://redirect.github.com/apps/github-actions)
([#&#8203;727](https://redirect.github.com/astral-sh/setup-uv/issues/727))
- chore: update known checksums for 0.9.21
@&#8203;[github-actions\[bot\]](https://redirect.github.com/apps/github-actions)
([#&#8203;726](https://redirect.github.com/astral-sh/setup-uv/issues/726))
- chore: update known checksums for 0.9.20
@&#8203;[github-actions\[bot\]](https://redirect.github.com/apps/github-actions)
([#&#8203;725](https://redirect.github.com/astral-sh/setup-uv/issues/725))
- chore: update known checksums for 0.9.18
@&#8203;[github-actions\[bot\]](https://redirect.github.com/apps/github-actions)
([#&#8203;718](https://redirect.github.com/astral-sh/setup-uv/issues/718))

##### ⬆️ Dependency updates

- Bump peter-evans/create-pull-request from 7.0.9 to 8.0.0
@&#8203;[dependabot\[bot\]](https://redirect.github.com/apps/dependabot)
([#&#8203;719](https://redirect.github.com/astral-sh/setup-uv/issues/719))
- Bump github/codeql-action from 4.31.6 to 4.31.9
@&#8203;[dependabot\[bot\]](https://redirect.github.com/apps/dependabot)
([#&#8203;723](https://redirect.github.com/astral-sh/setup-uv/issues/723))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 4am on Monday" (UTC),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/astral-sh/ruff).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi43NC41IiwidXBkYXRlZEluVmVyIjoiNDIuNzQuNSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-12 08:07:15 +00:00
renovate[bot]
f92a818fdd Update Rust crate insta to v1.46.0 (#22527)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-12 07:56:12 +00:00
renovate[bot]
39ec97df79 Update taiki-e/install-action action to v2.65.13 (#22522)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-12 08:54:28 +01:00
renovate[bot]
a21988d820 Update NPM Development dependencies (#22525)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-12 08:53:56 +01:00
renovate[bot]
eab41d5a4c Update dependency ruff to v0.14.11 (#22517)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-12 08:52:14 +01:00
renovate[bot]
52f4a529f7 Update CodSpeedHQ/action action to v4.5.2 (#22516)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-12 08:49:54 +01:00
renovate[bot]
8fd142f4ef Update Rust crate libc to v0.2.179 (#22520)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-12 08:48:43 +01:00
renovate[bot]
5dca6d22df Update Rust crate clap to v4.5.54 (#22518)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-12 08:48:30 +01:00
renovate[bot]
4c5846c6fe Update Rust crate imperative to v1.0.7 (#22519)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-12 08:48:06 +01:00
renovate[bot]
0289d1b163 Update Rust crate syn to v2.0.113 (#22521)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-12 08:47:25 +01:00
Charlie Marsh
09ff3e7056 Set priority for prek to enable concurrent execution (#22506)
## Summary

prek allows you to set priorities, and can run tasks of the same
priority concurrently (e.g., we can run Ruff's Python formatting and
`cargo fmt` at the same time). On my machine, this takes `uvx prek run
-a` from 19.4s to 5.0s (~a 4x speed-up).
2026-01-11 19:21:38 +00:00
Charlie Marsh
7bacca9b62 Use prek in documentation and CI (#22505)
## Summary

AFAIK, many of us on the team are using prek now. It seems appropriate
to modify the docs to formalize it.
2026-01-11 14:17:10 -05:00
Charlie Marsh
2c68057c4b [ty] Preserve argument signature in @total_ordering (#22496)
## Summary

Closes https://github.com/astral-sh/ty/issues/2435.
2026-01-10 14:35:58 -05:00
Charlie Marsh
8e29be9c1c Add let chains preference to development guidelines (#22497) 2026-01-10 12:29:07 -05:00
32 changed files with 725 additions and 240 deletions

View File

@@ -5,4 +5,4 @@ rustup component add clippy rustfmt
cargo install cargo-insta
cargo fetch
pip install maturin pre-commit
pip install maturin prek

View File

@@ -1,4 +1,4 @@
# Configuration for the actionlint tool, which we run via pre-commit
# Configuration for the actionlint tool, which we run via prek
# to verify the correctness of the syntax in our GitHub Actions workflows.
self-hosted-runner:
@@ -17,4 +17,4 @@ self-hosted-runner:
paths:
".github/workflows/mypy_primer.yaml":
ignore:
- 'condition "false" is always evaluated to false. remove the if: section'
- 'constant expression "false" in condition. remove the if: section'

View File

@@ -76,9 +76,9 @@
enabled: false,
},
{
groupName: "pre-commit dependencies",
groupName: "prek dependencies",
matchManagers: ["pre-commit"],
description: "Weekly update of pre-commit dependencies",
description: "Weekly update of prek dependencies",
},
{
groupName: "NPM Development dependencies",

View File

@@ -281,15 +281,15 @@ jobs:
- name: "Install mold"
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- name: "Install cargo nextest"
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
uses: taiki-e/install-action@0e76c5c569f13f7eb21e8e5b26fe710062b57b62 # v2.65.13
with:
tool: cargo-nextest
- name: "Install cargo insta"
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
uses: taiki-e/install-action@0e76c5c569f13f7eb21e8e5b26fe710062b57b62 # v2.65.13
with:
tool: cargo-insta
- name: "Install uv"
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
with:
enable-cache: "true"
- name: ty mdtests (GitHub annotations)
@@ -343,11 +343,11 @@ jobs:
- name: "Install mold"
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- name: "Install cargo nextest"
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
uses: taiki-e/install-action@0e76c5c569f13f7eb21e8e5b26fe710062b57b62 # v2.65.13
with:
tool: cargo-nextest
- name: "Install uv"
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
with:
enable-cache: "true"
- name: "Run tests"
@@ -376,11 +376,11 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: "Install cargo nextest"
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
uses: taiki-e/install-action@0e76c5c569f13f7eb21e8e5b26fe710062b57b62 # v2.65.13
with:
tool: cargo-nextest
- name: "Install uv"
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
with:
enable-cache: "true"
- name: "Run tests"
@@ -486,7 +486,7 @@ jobs:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
shared-key: ruff-linux-debug
@@ -521,7 +521,7 @@ jobs:
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- name: "Install Rust toolchain"
run: rustup component add rustfmt
# Run all code generation scripts, and verify that the current output is
@@ -561,7 +561,7 @@ jobs:
ref: ${{ github.event.pull_request.base.ref }}
persist-credentials: false
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
with:
python-version: ${{ env.PYTHON_VERSION }}
activate-environment: true
@@ -667,7 +667,7 @@ jobs:
with:
fetch-depth: 0
persist-credentials: false
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
@@ -726,7 +726,7 @@ jobs:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
@@ -769,32 +769,32 @@ jobs:
- name: "Remove wheels from cache"
run: rm -rf target/wheels
pre-commit:
name: "pre-commit"
prek:
name: "prek"
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}
timeout-minutes: 10
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: 24
- name: "Cache pre-commit"
- name: "Cache prek"
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: ~/.cache/pre-commit
key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
- name: "Run pre-commit"
path: ~/.cache/prek
key: prek-${{ hashFiles('.pre-commit-config.yaml') }}
- name: "Run prek"
run: |
echo '```console' > "$GITHUB_STEP_SUMMARY"
# Enable color output for pre-commit and remove it for the summary
# Use --hook-stage=manual to enable slower pre-commit hooks that are skipped by default
SKIP=cargo-fmt uvx --python="${PYTHON_VERSION}" pre-commit run --all-files --show-diff-on-failure --color=always --hook-stage=manual | \
# Enable color output for prek and remove it for the summary
# Use --hook-stage=manual to enable slower hooks that are skipped by default
SKIP=cargo-fmt uvx prek run --all-files --show-diff-on-failure --color always --hook-stage manual | \
tee >(sed -E 's/\x1B\[([0-9]{1,2}(;[0-9]{1,2})*)?[mGK]//g' >> "$GITHUB_STEP_SUMMARY") >&1
exit_code="${PIPESTATUS[0]}"
echo '```' >> "$GITHUB_STEP_SUMMARY"
@@ -814,7 +814,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: Install uv
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
with:
python-version: 3.13
activate-environment: true
@@ -966,13 +966,13 @@ jobs:
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- name: "Install Rust toolchain"
run: rustup show
- name: "Install codspeed"
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
uses: taiki-e/install-action@0e76c5c569f13f7eb21e8e5b26fe710062b57b62 # v2.65.13
with:
tool: cargo-codspeed
@@ -980,7 +980,7 @@ jobs:
run: cargo codspeed build --features "codspeed,ruff_instrumented" --profile profiling --no-default-features -p ruff_benchmark --bench formatter --bench lexer --bench linter --bench parser
- name: "Run benchmarks"
uses: CodSpeedHQ/action@972e3437949c89e1357ebd1a2dbc852fcbc57245 # v4.5.1
uses: CodSpeedHQ/action@dbda7111f8ac363564b0c51b992d4ce76bb89f2f # v4.5.2
with:
mode: simulation
run: cargo codspeed run
@@ -1011,7 +1011,7 @@ jobs:
run: rustup show
- name: "Install codspeed"
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
uses: taiki-e/install-action@0e76c5c569f13f7eb21e8e5b26fe710062b57b62 # v2.65.13
with:
tool: cargo-codspeed
@@ -1044,10 +1044,10 @@ jobs:
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- name: "Install codspeed"
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
uses: taiki-e/install-action@0e76c5c569f13f7eb21e8e5b26fe710062b57b62 # v2.65.13
with:
tool: cargo-codspeed
@@ -1061,7 +1061,7 @@ jobs:
run: chmod +x target/codspeed/simulation/ruff_benchmark/ty
- name: "Run benchmarks"
uses: CodSpeedHQ/action@972e3437949c89e1357ebd1a2dbc852fcbc57245 # v4.5.1
uses: CodSpeedHQ/action@dbda7111f8ac363564b0c51b992d4ce76bb89f2f # v4.5.2
with:
mode: simulation
run: cargo codspeed run --bench ty "${{ matrix.benchmark }}"
@@ -1092,13 +1092,13 @@ jobs:
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- name: "Install Rust toolchain"
run: rustup show
- name: "Install codspeed"
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
uses: taiki-e/install-action@0e76c5c569f13f7eb21e8e5b26fe710062b57b62 # v2.65.13
with:
tool: cargo-codspeed
@@ -1133,10 +1133,10 @@ jobs:
with:
persist-credentials: false
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- name: "Install codspeed"
uses: taiki-e/install-action@28a9d316db64b78a951f3f8587a5d08cc97ad8eb # v2.65.6
uses: taiki-e/install-action@0e76c5c569f13f7eb21e8e5b26fe710062b57b62 # v2.65.13
with:
tool: cargo-codspeed
@@ -1150,7 +1150,7 @@ jobs:
run: chmod +x target/codspeed/walltime/ruff_benchmark/ty_walltime
- name: "Run benchmarks"
uses: CodSpeedHQ/action@972e3437949c89e1357ebd1a2dbc852fcbc57245 # v4.5.1
uses: CodSpeedHQ/action@dbda7111f8ac363564b0c51b992d4ce76bb89f2f # v4.5.2
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

View File

@@ -34,7 +34,7 @@ jobs:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
persist-credentials: false
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"

View File

@@ -48,7 +48,7 @@ jobs:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
@@ -87,7 +87,7 @@ jobs:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
@@ -129,7 +129,7 @@ jobs:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:

View File

@@ -22,7 +22,7 @@ jobs:
id-token: write
steps:
- name: "Install uv"
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
pattern: wheels-*

View File

@@ -76,7 +76,7 @@ jobs:
run: |
git config --global user.name typeshedbot
git config --global user.email '<>'
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- name: Sync typeshed stubs
run: |
rm -rf "ruff/${VENDORED_TYPESHED}"
@@ -130,7 +130,7 @@ jobs:
with:
persist-credentials: true
ref: ${{ env.UPSTREAM_BRANCH}}
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- name: Setup git
run: |
git config --global user.name typeshedbot
@@ -169,7 +169,7 @@ jobs:
with:
persist-credentials: true
ref: ${{ env.UPSTREAM_BRANCH}}
- uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
- uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
- name: Setup git
run: |
git config --global user.name typeshedbot

View File

@@ -38,7 +38,7 @@ jobs:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
with:
enable-cache: true

View File

@@ -32,7 +32,7 @@ jobs:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
with:
enable-cache: true

View File

@@ -21,15 +21,62 @@ exclude: |
)$
repos:
# Priority 0: Read-only hooks; hooks that modify disjoint file types.
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: check-merge-conflict
priority: 0
- repo: https://github.com/abravalheri/validate-pyproject
rev: v0.24.1
hooks:
- id: validate-pyproject
priority: 0
- repo: https://github.com/crate-ci/typos
rev: v1.41.0
hooks:
- id: typos
priority: 0
- repo: local
hooks:
- id: cargo-fmt
name: cargo fmt
entry: cargo fmt --
language: system
types: [rust]
pass_filenames: false # This makes it a lot faster
priority: 0
# Prettier
- repo: https://github.com/rbubley/mirrors-prettier
rev: v3.7.4
hooks:
- id: prettier
types: [yaml]
priority: 0
# zizmor detects security vulnerabilities in GitHub Actions workflows.
# Additional configuration for the tool is found in `.github/zizmor.yml`
- repo: https://github.com/zizmorcore/zizmor-pre-commit
rev: v1.19.0
hooks:
- id: zizmor
priority: 0
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.36.0
hooks:
- id: check-github-workflows
priority: 0
- repo: https://github.com/shellcheck-py/shellcheck-py
rev: v0.11.0.1
hooks:
- id: shellcheck
priority: 0
- repo: https://github.com/executablebooks/mdformat
rev: 1.0.0
@@ -44,7 +91,20 @@ repos:
docs/formatter/black\.md
| docs/\w+\.md
)$
priority: 0
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.14.10
hooks:
- id: ruff-format
priority: 0
- id: ruff-check
args: [--fix, --exit-non-zero-on-fix]
types_or: [python, pyi]
require_serial: true
priority: 1
# Priority 1: Second-pass fixers (e.g., markdownlint-fix runs after mdformat).
- repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.47.0
hooks:
@@ -54,7 +114,9 @@ repos:
docs/formatter/black\.md
| docs/\w+\.md
)$
priority: 1
# Priority 2: blacken-docs runs after markdownlint-fix (both modify markdown).
- repo: https://github.com/adamchainz/blacken-docs
rev: 1.20.0
hooks:
@@ -68,59 +130,18 @@ repos:
)$
additional_dependencies:
- black==25.12.0
- repo: https://github.com/crate-ci/typos
rev: v1.40.0
hooks:
- id: typos
- repo: local
hooks:
- id: cargo-fmt
name: cargo fmt
entry: cargo fmt --
language: system
types: [rust]
pass_filenames: false # This makes it a lot faster
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.14.10
hooks:
- id: ruff-format
- id: ruff-check
args: [--fix, --exit-non-zero-on-fix]
types_or: [python, pyi]
require_serial: true
# Prettier
- repo: https://github.com/rbubley/mirrors-prettier
rev: v3.7.4
hooks:
- id: prettier
types: [yaml]
# zizmor detects security vulnerabilities in GitHub Actions workflows.
# Additional configuration for the tool is found in `.github/zizmor.yml`
- repo: https://github.com/zizmorcore/zizmor-pre-commit
rev: v1.19.0
hooks:
- id: zizmor
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.36.0
hooks:
- id: check-github-workflows
priority: 2
# `actionlint` hook, for verifying correct syntax in GitHub Actions workflows.
# Some additional configuration for `actionlint` can be found in `.github/actionlint.yaml`.
- repo: https://github.com/rhysd/actionlint
rev: v1.7.9
rev: v1.7.10
hooks:
- id: actionlint
stages:
# This hook is disabled by default, since it's quite slow.
# To run all hooks *including* this hook, use `uvx pre-commit run -a --hook-stage=manual`.
# To run *just* this hook, use `uvx pre-commit run -a actionlint --hook-stage=manual`.
# To run all hooks *including* this hook, use `uvx prek run -a --hook-stage=manual`.
# To run *just* this hook, use `uvx prek run -a actionlint --hook-stage=manual`.
- manual
args:
- "-ignore=SC2129" # ignorable stylistic lint from shellcheck
@@ -131,8 +152,4 @@ repos:
# and checks these with shellcheck. This is arguably its most useful feature,
# but the integration only works if shellcheck is installed
- "github.com/wasilibs/go-shellcheck/cmd/shellcheck@v0.11.1"
- repo: https://github.com/shellcheck-py/shellcheck-py
rev: v0.11.0.1
hooks:
- id: shellcheck
priority: 0

View File

@@ -65,6 +65,7 @@ When working on ty, PR titles should start with `[ty]` and be tagged with the `t
- All changes must be tested. If you're not testing your changes, you're not done.
- Get your tests to pass. If you didn't run the tests, your code does not work.
- Follow existing code style. Check neighboring files for patterns.
- Always run `uvx pre-commit run -a` at the end of a task.
- Always run `uvx prek run -a` at the end of a task.
- Avoid writing significant amounts of new code. This is often a sign that we're missing an existing method or mechanism that could help solve the problem. Look for existing utilities first.
- Avoid falling back to patterns that require `panic!`, `unreachable!`, or `.unwrap()`. Instead, try to encode those constraints in the type system.
- Prefer let chains (`if let` combined with `&&`) over nested `if let` statements to reduce indentation and improve readability.

View File

@@ -53,12 +53,12 @@ cargo install cargo-insta
You'll need [uv](https://docs.astral.sh/uv/getting-started/installation/) (or `pipx` and `pip`) to
run Python utility commands.
You can optionally install pre-commit hooks to automatically run the validation checks
You can optionally install hooks to automatically run the validation checks
when making a commit:
```shell
uv tool install pre-commit
pre-commit install
uv tool install prek
prek install
```
We recommend [nextest](https://nexte.st/) to run Ruff's test suite (via `cargo nextest run`),
@@ -85,7 +85,7 @@ and that it passes both the lint and test validation checks:
```shell
cargo clippy --workspace --all-targets --all-features -- -D warnings # Rust linting
RUFF_UPDATE_SCHEMA=1 cargo test # Rust testing and updating ruff.schema.json
uvx pre-commit run --all-files --show-diff-on-failure # Rust and Python formatting, Markdown and Python linting, etc.
uvx prek run -a # Rust and Python formatting, Markdown and Python linting, etc.
```
These checks will run on GitHub Actions when you open your pull request, but running them locally
@@ -381,7 +381,7 @@ Commit each step of this process separately for easier review.
- Often labels will be missing from pull requests they will need to be manually organized into the proper section
- Changes should be edited to be user-facing descriptions, avoiding internal details
- Square brackets (eg, `[ruff]` project name) will be automatically escaped by `pre-commit`
- Square brackets (eg, `[ruff]` project name) will be automatically escaped by `prek`
Additionally, for minor releases:

55
Cargo.lock generated
View File

@@ -466,9 +466,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.53"
version = "4.5.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8"
checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394"
dependencies = [
"clap_builder",
"clap_derive",
@@ -476,9 +476,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.53"
version = "4.5.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00"
checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00"
dependencies = [
"anstream",
"anstyle",
@@ -1583,11 +1583,11 @@ dependencies = [
[[package]]
name = "imperative"
version = "1.0.6"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29a1f6526af721f9aec9ceed7ab8ebfca47f3399d08b80056c2acca3fcb694a9"
checksum = "35e1d0bd9c575c52e59aad8e122a11786e852a154678d0c86e9e243d55273970"
dependencies = [
"phf",
"phf 0.13.1",
"rust-stemmers",
]
@@ -1648,9 +1648,9 @@ dependencies = [
[[package]]
name = "insta"
version = "1.45.1"
version = "1.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "983e3b24350c84ab8a65151f537d67afbbf7153bb9f1110e03e9fa9b07f67a5c"
checksum = "1b66886d14d18d420ab5052cbff544fc5d34d0b2cdd35eb5976aaa10a4a472e5"
dependencies = [
"console 0.15.11",
"once_cell",
@@ -1874,9 +1874,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.178"
version = "0.2.179"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f"
[[package]]
name = "libcst"
@@ -2488,7 +2488,17 @@ version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
dependencies = [
"phf_shared",
"phf_shared 0.11.3",
]
[[package]]
name = "phf"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"
dependencies = [
"phf_shared 0.13.1",
"serde",
]
[[package]]
@@ -2498,7 +2508,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
dependencies = [
"phf_generator",
"phf_shared",
"phf_shared 0.11.3",
]
[[package]]
@@ -2507,7 +2517,7 @@ version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
dependencies = [
"phf_shared",
"phf_shared 0.11.3",
"rand 0.8.5",
]
@@ -2520,6 +2530,15 @@ dependencies = [
"siphasher",
]
[[package]]
name = "phf_shared"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266"
dependencies = [
"siphasher",
]
[[package]]
name = "pin-project-lite"
version = "0.2.16"
@@ -3994,9 +4013,9 @@ checksum = "e396b6523b11ccb83120b115a0b7366de372751aa6edf19844dfb13a6af97e91"
[[package]]
name = "syn"
version = "2.0.111"
version = "2.0.113"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
checksum = "678faa00651c9eb72dd2020cbdf275d92eccb2400d568e419efdd64838145cb4"
dependencies = [
"proc-macro2",
"quote",
@@ -4060,7 +4079,7 @@ checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662"
dependencies = [
"fnv",
"nom",
"phf",
"phf 0.11.3",
"phf_codegen",
]
@@ -4808,7 +4827,7 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1673eca9782c84de5f81b82e4109dcfb3611c8ba0d52930ec4a9478f547b2dd"
dependencies = [
"phf",
"phf 0.11.3",
"unicode_names2_generator",
]

View File

@@ -34,12 +34,12 @@ cargo install cargo-insta
You'll need [uv](https://docs.astral.sh/uv/getting-started/installation/) (or `pipx` and `pip`) to
run Python utility commands.
You can optionally install pre-commit hooks to automatically run the validation checks
You can optionally install hooks to automatically run the validation checks
when making a commit:
```shell
uv tool install pre-commit
pre-commit install
uv tool install prek
prek install
```
We recommend [nextest](https://nexte.st/) to run ty's test suite (via `cargo nextest run`),
@@ -66,7 +66,7 @@ and that it passes both the lint and test validation checks:
```shell
cargo clippy --workspace --all-targets --all-features -- -D warnings # Rust linting
cargo test # Rust testing
uvx pre-commit run --all-files --show-diff-on-failure # Rust and Python formatting, Markdown and Python linting, etc.
uvx prek run -a # Rust and Python formatting, Markdown and Python linting, etc.
```
These checks will run on GitHub Actions when you open your pull request, but running them locally

View File

@@ -38,6 +38,125 @@ reveal_type(s1 > s2) # revealed: bool
reveal_type(s1 >= s2) # revealed: bool
```
## Signature derived from source ordering method
When the source ordering method accepts a broader type (like `object`) for its `other` parameter,
the synthesized comparison methods should use the same signature. This allows comparisons with types
other than the class itself:
```py
from functools import total_ordering
@total_ordering
class Comparable:
def __init__(self, value: int):
self.value = value
def __eq__(self, other: object) -> bool:
if isinstance(other, Comparable):
return self.value == other.value
if isinstance(other, int):
return self.value == other
return NotImplemented
def __lt__(self, other: object) -> bool:
if isinstance(other, Comparable):
return self.value < other.value
if isinstance(other, int):
return self.value < other
return NotImplemented
a = Comparable(10)
b = Comparable(20)
# Comparisons with the same type work.
reveal_type(a <= b) # revealed: bool
reveal_type(a >= b) # revealed: bool
# Comparisons with `int` also work because `__lt__` accepts `object`.
reveal_type(a <= 15) # revealed: bool
reveal_type(a >= 5) # revealed: bool
```
## Multiple ordering methods with different signatures
When multiple ordering methods are defined with different signatures, the decorator selects a "root"
method using the priority order: `__lt__` > `__le__` > `__gt__` > `__ge__`. Synthesized methods use
the signature from the highest-priority method. Methods that are explicitly defined are not
overridden.
```py
from functools import total_ordering
@total_ordering
class MultiSig:
def __init__(self, value: int):
self.value = value
def __eq__(self, other: object) -> bool:
return True
# __lt__ accepts `object` (highest priority, used as root)
def __lt__(self, other: object) -> bool:
return True
# __gt__ only accepts `MultiSig` (not overridden by decorator)
def __gt__(self, other: "MultiSig") -> bool:
return True
a = MultiSig(10)
b = MultiSig(20)
# __le__ and __ge__ are synthesized with __lt__'s signature (accepts `object`)
reveal_type(a <= b) # revealed: bool
reveal_type(a <= 15) # revealed: bool
reveal_type(a >= b) # revealed: bool
reveal_type(a >= 15) # revealed: bool
# __gt__ keeps its original signature (only accepts MultiSig)
reveal_type(a > b) # revealed: bool
a > 15 # error: [unsupported-operator]
```
## Overloaded ordering method
When the source ordering method is overloaded, the synthesized comparison methods should preserve
all overloads:
```py
from functools import total_ordering
from typing import overload
@total_ordering
class Flexible:
def __init__(self, value: int):
self.value = value
def __eq__(self, other: object) -> bool:
return True
@overload
def __lt__(self, other: "Flexible") -> bool: ...
@overload
def __lt__(self, other: int) -> bool: ...
def __lt__(self, other: "Flexible | int") -> bool:
if isinstance(other, Flexible):
return self.value < other.value
return self.value < other
a = Flexible(10)
b = Flexible(20)
# Synthesized __le__ preserves overloads from __lt__
reveal_type(a <= b) # revealed: bool
reveal_type(a <= 15) # revealed: bool
# Synthesized __ge__ also preserves overloads
reveal_type(a >= b) # revealed: bool
reveal_type(a >= 15) # revealed: bool
# But comparison with an unsupported type should still error
a <= "string" # error: [unsupported-operator]
```
## Using `__gt__` as the root comparison method
When a class defines `__eq__` and `__gt__`, the decorator synthesizes `__lt__`, `__le__`, and
@@ -127,6 +246,41 @@ reveal_type(c1 > c2) # revealed: bool
reveal_type(c1 >= c2) # revealed: bool
```
## Method precedence with inheritance
The decorator always prefers `__lt__` > `__le__` > `__gt__` > `__ge__`, regardless of whether the
method is defined locally or inherited. In this example, the inherited `__lt__` takes precedence
over the locally-defined `__gt__`:
```py
from functools import total_ordering
from typing import Literal
class Base:
def __lt__(self, other: "Base") -> Literal[True]:
return True
@total_ordering
class Child(Base):
# __gt__ is defined locally, but __lt__ (inherited) takes precedence
def __gt__(self, other: "Child") -> Literal[False]:
return False
c1 = Child()
c2 = Child()
# __lt__ is inherited from Base
reveal_type(c1 < c2) # revealed: Literal[True]
# __gt__ is defined locally on Child
reveal_type(c1 > c2) # revealed: Literal[False]
# __le__ and __ge__ are synthesized from __lt__ (the highest-priority method),
# even though __gt__ is defined locally on the class itself
reveal_type(c1 <= c2) # revealed: bool
reveal_type(c1 >= c2) # revealed: bool
```
## Explicitly-defined methods are not overridden
When a class explicitly defines multiple comparison methods, the decorator does not override them.
@@ -245,6 +399,79 @@ n1 <= n2 # error: [unsupported-operator]
n1 >= n2 # error: [unsupported-operator]
```
## Non-bool return type
When the root ordering method returns a non-bool type (like `int`), the synthesized methods return a
union of that type and `bool`. This is because `@total_ordering` generates methods like:
```python
def __le__(self, other):
return self < other or self == other
```
If `__lt__` returns `int`, then the synthesized `__le__` could return either `int` (from
`self < other`) or `bool` (from `self == other`). Since `bool` is a subtype of `int`, the union
simplifies to `int`:
```py
from functools import total_ordering
@total_ordering
class IntReturn:
def __init__(self, value: int):
self.value = value
def __eq__(self, other: object) -> bool:
if not isinstance(other, IntReturn):
return NotImplemented
return self.value == other.value
def __lt__(self, other: "IntReturn") -> int:
return self.value - other.value
a = IntReturn(10)
b = IntReturn(20)
# User-defined __lt__ returns int.
reveal_type(a < b) # revealed: int
# Synthesized methods return int (the union int | bool simplifies to int
# because bool is a subtype of int in Python).
reveal_type(a <= b) # revealed: int
reveal_type(a > b) # revealed: int
reveal_type(a >= b) # revealed: int
```
When the root method returns a type that is not a supertype of `bool`, the union is preserved:
```py
from functools import total_ordering
@total_ordering
class StrReturn:
def __init__(self, value: str):
self.value = value
def __eq__(self, other: object) -> bool:
if not isinstance(other, StrReturn):
return NotImplemented
return self.value == other.value
def __lt__(self, other: "StrReturn") -> str:
return self.value
a = StrReturn("a")
b = StrReturn("b")
# User-defined __lt__ returns str.
reveal_type(a < b) # revealed: str
# Synthesized methods return str | bool.
reveal_type(a <= b) # revealed: str | bool
reveal_type(a > b) # revealed: str | bool
reveal_type(a >= b) # revealed: str | bool
```
## Function call form
When `total_ordering` is called as a function (not as a decorator), the same validation is

View File

@@ -171,6 +171,27 @@ static_assert(not is_equivalent_to(D[Any], C[Any]))
static_assert(not is_equivalent_to(D[Any], C[Unknown]))
```
## Bounded typevars in contravariant positions
When a bounded typevar appears in a contravariant position, the actual type doesn't need to satisfy
the bound directly. The typevar can be solved to the intersection of the actual type and the bound
(e.g., `Never` when disjoint).
```py
from typing import Generic, TypeVar
T = TypeVar("T", contravariant=True)
T_int = TypeVar("T_int", bound=int)
class Contra(Generic[T]): ...
def f(x: Contra[T_int]) -> T_int:
raise NotImplementedError
def _(x: Contra[str]):
reveal_type(f(x)) # revealed: Never
```
## Invariance
With an invariant typevar, only equivalent specializations of the generic class are subtypes of or

View File

@@ -172,6 +172,23 @@ static_assert(not is_equivalent_to(D[Any], C[Any]))
static_assert(not is_equivalent_to(D[Any], C[Unknown]))
```
## Bounded typevars in contravariant positions
When a bounded typevar appears in a contravariant position, the actual type doesn't need to satisfy
the bound directly. The typevar can be solved to the intersection of the actual type and the bound
(e.g., `Never` when disjoint).
```py
class Contra[T]:
def append(self, x: T): ...
def f[T: int](x: Contra[T]) -> T:
raise NotImplementedError
def _(x: Contra[str]):
reveal_type(f(x)) # revealed: Never
```
## Invariance
With an invariant typevar, only equivalent specializations of the generic class are subtypes of or

View File

@@ -556,3 +556,27 @@ def _(x: type[object], y: type[object], z: type[object]):
if issubclass(z, Invariant):
reveal_type(z) # revealed: type[Top[Invariant[Unknown]]]
```
## Narrowing with TypedDict unions
Narrowing unions of `int` and multiple TypedDicts using `isinstance(x, dict)` should not panic
during type ordering of normalized intersection types. Regression test for
<https://github.com/astral-sh/ty/issues/2451>.
```py
from typing import Any, TypedDict, cast
class A(TypedDict):
x: str
class B(TypedDict):
y: str
T = int | A | B
def test(a: Any, items: list[T]) -> None:
combined = a or items
v = combined[0]
if isinstance(v, dict):
cast(T, v) # no panic
```

View File

@@ -134,12 +134,17 @@ class IsEqualToEverything(type):
class A(metaclass=IsEqualToEverything): ...
class B(metaclass=IsEqualToEverything): ...
def _(x: A | B):
def _(x: A | B, y: object):
if type(x) == A:
reveal_type(x) # revealed: A | B
if type(x) != A:
reveal_type(x) # revealed: A | B
if type(y) == bool:
reveal_type(y) # revealed: object
else:
reveal_type(y) # revealed: object
```
## No narrowing for custom `type` callable

View File

@@ -2479,6 +2479,37 @@ def f(arg1: type, arg2: type):
reveal_type(arg2) # revealed: type & ~type[OnlyMethodMembers]
```
Per PEP 544, `@runtime_checkable` propagates to subclasses. A protocol that inherits from a
`@runtime_checkable` protocol is itself runtime-checkable, even without the decorator:
```py
@runtime_checkable
class RuntimeCheckableBase(Protocol):
x: int
class RuntimeCheckableChild(RuntimeCheckableBase, Protocol):
y: str
def g(arg: object):
if isinstance(arg, RuntimeCheckableChild): # no error!
reveal_type(arg) # revealed: RuntimeCheckableChild
else:
reveal_type(arg) # revealed: ~RuntimeCheckableChild
```
This also applies to deeper inheritance hierarchies:
```py
class RuntimeCheckableGrandchild(RuntimeCheckableChild, Protocol):
z: float
def h(arg: object):
if isinstance(arg, RuntimeCheckableGrandchild): # no error!
reveal_type(arg) # revealed: RuntimeCheckableGrandchild
else:
reveal_type(arg) # revealed: ~RuntimeCheckableGrandchild
```
## Truthiness of protocol instances
An instance of a protocol type generally has ambiguous truthiness:

View File

@@ -53,6 +53,26 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/protocols.md
38 | reveal_type(arg2) # revealed: type[OnlyMethodMembers]
39 | else:
40 | reveal_type(arg2) # revealed: type & ~type[OnlyMethodMembers]
41 | @runtime_checkable
42 | class RuntimeCheckableBase(Protocol):
43 | x: int
44 |
45 | class RuntimeCheckableChild(RuntimeCheckableBase, Protocol):
46 | y: str
47 |
48 | def g(arg: object):
49 | if isinstance(arg, RuntimeCheckableChild): # no error!
50 | reveal_type(arg) # revealed: RuntimeCheckableChild
51 | else:
52 | reveal_type(arg) # revealed: ~RuntimeCheckableChild
53 | class RuntimeCheckableGrandchild(RuntimeCheckableChild, Protocol):
54 | z: float
55 |
56 | def h(arg: object):
57 | if isinstance(arg, RuntimeCheckableGrandchild): # no error!
58 | reveal_type(arg) # revealed: RuntimeCheckableGrandchild
59 | else:
60 | reveal_type(arg) # revealed: ~RuntimeCheckableGrandchild
```
# Diagnostics

View File

@@ -1586,17 +1586,44 @@ impl<'db> ClassLiteral<'db> {
}
/// Returns `true` if any class in this class's MRO (excluding `object`) defines an ordering
/// method (`__lt__`, `__le__`, `__gt__`, `__ge__`). Used by `@total_ordering` validation and
/// for synthesizing comparison methods.
/// method (`__lt__`, `__le__`, `__gt__`, `__ge__`). Used by `@total_ordering` validation.
pub(super) fn has_ordering_method_in_mro(
self,
db: &'db dyn Db,
specialization: Option<Specialization<'db>>,
) -> bool {
self.iter_mro(db, specialization)
.filter_map(ClassBase::into_class)
.filter(|class| !class.class_literal(db).0.is_known(db, KnownClass::Object))
.any(|class| class.class_literal(db).0.has_own_ordering_method(db))
self.total_ordering_root_method(db, specialization)
.is_some()
}
/// Returns the type of the ordering method used by `@total_ordering`, if any.
///
/// Following `functools.total_ordering` precedence, we prefer `__lt__` > `__le__` > `__gt__` >
/// `__ge__`, regardless of whether the method is defined locally or inherited.
pub(super) fn total_ordering_root_method(
self,
db: &'db dyn Db,
specialization: Option<Specialization<'db>>,
) -> Option<Type<'db>> {
const ORDERING_METHODS: [&str; 4] = ["__lt__", "__le__", "__gt__", "__ge__"];
for name in ORDERING_METHODS {
for base in self.iter_mro(db, specialization) {
let Some(base_class) = base.into_class() else {
continue;
};
let (base_literal, base_specialization) = base_class.class_literal(db);
if base_literal.is_known(db, KnownClass::Object) {
continue;
}
let member = class_member(db, base_literal.body_scope(db), name);
if let Some(ty) = member.ignore_possibly_undefined() {
return Some(ty.apply_optional_specialization(db, base_specialization));
}
}
}
None
}
pub(crate) fn generic_context(self, db: &'db dyn Db) -> Option<GenericContext<'db>> {
@@ -2448,26 +2475,44 @@ impl<'db> ClassLiteral<'db> {
// ordering method. The decorator requires at least one of __lt__,
// __le__, __gt__, or __ge__ to be defined (either in this class or
// inherited from a superclass, excluding `object`).
if self.total_ordering(db) && matches!(name, "__lt__" | "__le__" | "__gt__" | "__ge__") {
if self.has_ordering_method_in_mro(db, specialization) {
let instance_ty =
Type::instance(db, self.apply_optional_specialization(db, specialization));
let signature = Signature::new(
Parameters::new(
db,
[
Parameter::positional_or_keyword(Name::new_static("self"))
.with_annotated_type(instance_ty),
Parameter::positional_or_keyword(Name::new_static("other"))
.with_annotated_type(instance_ty),
],
),
KnownClass::Bool.to_instance(db),
//
// Only synthesize methods that are not already defined in the MRO.
if self.total_ordering(db)
&& matches!(name, "__lt__" | "__le__" | "__gt__" | "__ge__")
&& !self
.iter_mro(db, specialization)
.filter_map(ClassBase::into_class)
.filter(|class| !class.class_literal(db).0.is_known(db, KnownClass::Object))
.any(|class| {
class_member(db, class.class_literal(db).0.body_scope(db), name)
.ignore_possibly_undefined()
.is_some()
})
&& self.has_ordering_method_in_mro(db, specialization)
&& let Some(root_method_ty) = self.total_ordering_root_method(db, specialization)
&& let Some(callables) = root_method_ty.try_upcast_to_callable(db)
{
let bool_ty = KnownClass::Bool.to_instance(db);
let synthesized_callables = callables.map(|callable| {
let signatures = CallableSignature::from_overloads(
callable.signatures(db).iter().map(|signature| {
// The generated methods return a union of the root method's return type
// and `bool`. This is because `@total_ordering` synthesizes methods like:
// def __gt__(self, other): return not (self == other or self < other)
// If `__lt__` returns `int`, then `__gt__` could return `int | bool`.
let return_ty =
UnionType::from_elements(db, [signature.return_ty, bool_ty]);
Signature::new_generic(
signature.generic_context,
signature.parameters().clone(),
return_ty,
)
}),
);
CallableType::new(db, signatures, CallableTypeKind::FunctionLike)
});
return Some(Type::function_like_callable(db, signature));
}
return Some(synthesized_callables.into_type(db));
}
let field_policy = CodeGeneratorKind::from_class(db, self, specialization)?;

View File

@@ -1878,6 +1878,21 @@ impl<'db> SpecializationBuilder<'db> {
{
match bound_typevar.typevar(self.db).bound_or_constraints(self.db) {
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
if polarity.is_contravariant() {
// In a contravariant position, the formal type variable is a subtype of
// the actual type (`T <: ty`). Since we also have the upper bound
// constraint `T <: bound`, we just need to ensure that the intersection
// of `ty` and `bound` is non-empty. Since `Never` is always a valid
// intersection if the types are disjoint, we don't need to perform any
// check here.
self.add_type_mapping(
bound_typevar,
IntersectionType::from_elements(self.db, [bound, ty]),
polarity,
f,
);
return Ok(());
}
if !ty
.when_assignable_to(self.db, bound, self.inferable)
.is_always_satisfied(self.db)
@@ -1899,10 +1914,16 @@ impl<'db> SpecializationBuilder<'db> {
}
for constraint in constraints.elements(self.db) {
if ty
.when_assignable_to(self.db, *constraint, self.inferable)
.is_always_satisfied(self.db)
{
let is_satisfied = if polarity.is_contravariant() {
constraint
.when_assignable_to(self.db, ty, self.inferable)
.is_always_satisfied(self.db)
} else {
ty.when_assignable_to(self.db, *constraint, self.inferable)
.is_always_satisfied(self.db)
};
if is_satisfied {
self.add_type_mapping(bound_typevar, *constraint, polarity, f);
return Ok(());
}

View File

@@ -1220,10 +1220,10 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
_ => continue,
};
let is_positive = if is_positive {
op == &ast::CmpOp::Is
} else {
op == &ast::CmpOp::IsNot
let is_positive = match op {
ast::CmpOp::Is => is_positive,
ast::CmpOp::IsNot => !is_positive,
_ => continue,
};
// `else`-branch narrowing for `if type(x) is Y` can only be done

View File

@@ -76,10 +76,17 @@ impl<'db> ProtocolClass<'db> {
}
pub(super) fn is_runtime_checkable(self, db: &'db dyn Db) -> bool {
self.class_literal(db)
.0
.known_function_decorators(db)
.contains(&KnownFunction::RuntimeCheckable)
// Check if this class or any ancestor protocol is decorated with @runtime_checkable.
// Per PEP 544, @runtime_checkable propagates to subclasses.
self.0.iter_mro(db).any(|base| {
base.into_class().is_some_and(|class| {
class
.class_literal(db)
.0
.known_function_decorators(db)
.contains(&KnownFunction::RuntimeCheckable)
})
})
}
/// Iterate through the body of the protocol class. Check that all definitions

View File

@@ -228,9 +228,7 @@ pub(super) fn union_or_intersection_elements_ordering<'db>(
(Type::TypeAlias(_), _) => Ordering::Less,
(_, Type::TypeAlias(_)) => Ordering::Greater,
(Type::TypedDict(left), Type::TypedDict(right)) => {
left.defining_class().cmp(&right.defining_class())
}
(Type::TypedDict(left), Type::TypedDict(right)) => left.cmp(right),
(Type::TypedDict(_), _) => Ordering::Less,
(_, Type::TypedDict(_)) => Ordering::Greater,

View File

@@ -48,7 +48,14 @@ impl Default for TypedDictParams {
/// Type that represents the set of all inhabitants (`dict` instances) that conform to
/// a given `TypedDict` schema.
#[derive(Debug, Copy, Clone, PartialEq, Eq, salsa::Update, Hash, get_size2::GetSize)]
///
/// # Ordering
/// Ordering is derived from the variant order (`Class` < `Synthesized`) and the inner types.
/// The Salsa IDs of inner types may change between runs or when the type was garbage collected
/// and recreated.
#[derive(
Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, salsa::Update, Hash, get_size2::GetSize,
)]
pub enum TypedDictType<'db> {
/// A reference to the class (inheriting from `typing.TypedDict`) that specifies the
/// schema of this `TypedDict`.
@@ -878,7 +885,11 @@ pub(super) fn validate_typed_dict_dict_literal<'db>(
}
}
/// # Ordering
/// Ordering is based on the type's salsa-assigned id and not on its values.
/// The id may change between runs, or when the type was garbage collected and recreated.
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct SynthesizedTypedDictType<'db> {
#[returns(ref)]
pub(crate) items: TypedDictSchema<'db>,

View File

@@ -92,6 +92,13 @@ impl TypeVarVariance {
TypeVarVariance::Covariant | TypeVarVariance::Bivariant
)
}
pub(crate) const fn is_contravariant(self) -> bool {
matches!(
self,
TypeVarVariance::Contravariant | TypeVarVariance::Bivariant
)
}
}
impl std::iter::FromIterator<Self> for TypeVarVariance {

View File

@@ -1,5 +1,5 @@
PyYAML==6.0.3
ruff==0.14.10
ruff==0.14.11
mkdocs==1.6.1
mkdocs-material==9.7.1
mkdocs-redirects==1.2.2

View File

@@ -118,9 +118,9 @@
}
},
"node_modules/@cloudflare/workers-types": {
"version": "4.20251229.0",
"resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20251229.0.tgz",
"integrity": "sha512-LgzxDZaT9bQhycQInf7S/fcZCQRTvWWQPE9xnEyedI+CXxWsXAD7hg84kvVyr+KUz+W9Oblzo75g6XZ3HdI5Yg==",
"version": "4.20260103.0",
"resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20260103.0.tgz",
"integrity": "sha512-jANmoGpJcXARnwlkvrQOeWyjYD1quTfHcs+++Z544XRHOSfLc4XSlts7snIhbiIGgA5bo66zDhraF+9lKUr2hw==",
"dev": true,
"license": "MIT OR Apache-2.0",
"peer": true

View File

@@ -61,6 +61,7 @@
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5",
@@ -1893,6 +1894,7 @@
"integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@@ -1915,20 +1917,20 @@
"optional": true
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.50.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.50.1.tgz",
"integrity": "sha512-PKhLGDq3JAg0Jk/aK890knnqduuI/Qj+udH7wCf0217IGi4gt+acgCyPVe79qoT+qKUvHMDQkwJeKW9fwl8Cyw==",
"version": "8.51.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.51.0.tgz",
"integrity": "sha512-XtssGWJvypyM2ytBnSnKtHYOGT+4ZwTnBVl36TA4nRO2f4PRNGz5/1OszHzcZCvcBMh+qb7I06uoCmLTRdR9og==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.50.1",
"@typescript-eslint/type-utils": "8.50.1",
"@typescript-eslint/utils": "8.50.1",
"@typescript-eslint/visitor-keys": "8.50.1",
"@typescript-eslint/scope-manager": "8.51.0",
"@typescript-eslint/type-utils": "8.51.0",
"@typescript-eslint/utils": "8.51.0",
"@typescript-eslint/visitor-keys": "8.51.0",
"ignore": "^7.0.0",
"natural-compare": "^1.4.0",
"ts-api-utils": "^2.1.0"
"ts-api-utils": "^2.2.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1938,7 +1940,7 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"@typescript-eslint/parser": "^8.50.1",
"@typescript-eslint/parser": "^8.51.0",
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
@@ -1954,16 +1956,17 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "8.50.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.50.1.tgz",
"integrity": "sha512-hM5faZwg7aVNa819m/5r7D0h0c9yC4DUlWAOvHAtISdFTc8xB86VmX5Xqabrama3wIPJ/q9RbGS1worb6JfnMg==",
"version": "8.51.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.51.0.tgz",
"integrity": "sha512-3xP4XzzDNQOIqBMWogftkwxhg5oMKApqY0BAflmLZiFYHqyhSOxv/cd/zPQLTcCXr4AkaKb25joocY0BD1WC6A==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.50.1",
"@typescript-eslint/types": "8.50.1",
"@typescript-eslint/typescript-estree": "8.50.1",
"@typescript-eslint/visitor-keys": "8.50.1",
"@typescript-eslint/scope-manager": "8.51.0",
"@typescript-eslint/types": "8.51.0",
"@typescript-eslint/typescript-estree": "8.51.0",
"@typescript-eslint/visitor-keys": "8.51.0",
"debug": "^4.3.4"
},
"engines": {
@@ -1979,14 +1982,14 @@
}
},
"node_modules/@typescript-eslint/project-service": {
"version": "8.50.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.50.1.tgz",
"integrity": "sha512-E1ur1MCVf+YiP89+o4Les/oBAVzmSbeRB0MQLfSlYtbWU17HPxZ6Bhs5iYmKZRALvEuBoXIZMOIRRc/P++Ortg==",
"version": "8.51.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.51.0.tgz",
"integrity": "sha512-Luv/GafO07Z7HpiI7qeEW5NW8HUtZI/fo/kE0YbtQEFpJRUuR0ajcWfCE5bnMvL7QQFrmT/odMe8QZww8X2nfQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.50.1",
"@typescript-eslint/types": "^8.50.1",
"@typescript-eslint/tsconfig-utils": "^8.51.0",
"@typescript-eslint/types": "^8.51.0",
"debug": "^4.3.4"
},
"engines": {
@@ -2001,14 +2004,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "8.50.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.50.1.tgz",
"integrity": "sha512-mfRx06Myt3T4vuoHaKi8ZWNTPdzKPNBhiblze5N50//TSHOAQQevl/aolqA/BcqqbJ88GUnLqjjcBc8EWdBcVw==",
"version": "8.51.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.51.0.tgz",
"integrity": "sha512-JhhJDVwsSx4hiOEQPeajGhCWgBMBwVkxC/Pet53EpBVs7zHHtayKefw1jtPaNRXpI9RA2uocdmpdfE7T+NrizA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.50.1",
"@typescript-eslint/visitor-keys": "8.50.1"
"@typescript-eslint/types": "8.51.0",
"@typescript-eslint/visitor-keys": "8.51.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2019,9 +2022,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.50.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.1.tgz",
"integrity": "sha512-ooHmotT/lCWLXi55G4mvaUF60aJa012QzvLK0Y+Mp4WdSt17QhMhWOaBWeGTFVkb2gDgBe19Cxy1elPXylslDw==",
"version": "8.51.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.51.0.tgz",
"integrity": "sha512-Qi5bSy/vuHeWyir2C8u/uqGMIlIDu8fuiYWv48ZGlZ/k+PRPHtaAu7erpc7p5bzw2WNNSniuxoMSO4Ar6V9OXw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2036,17 +2039,17 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "8.50.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.50.1.tgz",
"integrity": "sha512-7J3bf022QZE42tYMO6SL+6lTPKFk/WphhRPe9Tw/el+cEwzLz1Jjz2PX3GtGQVxooLDKeMVmMt7fWpYRdG5Etg==",
"version": "8.51.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.51.0.tgz",
"integrity": "sha512-0XVtYzxnobc9K0VU7wRWg1yiUrw4oQzexCG2V2IDxxCxhqBMSMbjB+6o91A+Uc0GWtgjCa3Y8bi7hwI0Tu4n5Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.50.1",
"@typescript-eslint/typescript-estree": "8.50.1",
"@typescript-eslint/utils": "8.50.1",
"@typescript-eslint/types": "8.51.0",
"@typescript-eslint/typescript-estree": "8.51.0",
"@typescript-eslint/utils": "8.51.0",
"debug": "^4.3.4",
"ts-api-utils": "^2.1.0"
"ts-api-utils": "^2.2.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2061,9 +2064,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "8.50.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.50.1.tgz",
"integrity": "sha512-v5lFIS2feTkNyMhd7AucE/9j/4V9v5iIbpVRncjk/K0sQ6Sb+Np9fgYS/63n6nwqahHQvbmujeBL7mp07Q9mlA==",
"version": "8.51.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.51.0.tgz",
"integrity": "sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2075,21 +2078,21 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "8.50.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.1.tgz",
"integrity": "sha512-woHPdW+0gj53aM+cxchymJCrh0cyS7BTIdcDxWUNsclr9VDkOSbqC13juHzxOmQ22dDkMZEpZB+3X1WpUvzgVQ==",
"version": "8.51.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.51.0.tgz",
"integrity": "sha512-1qNjGqFRmlq0VW5iVlcyHBbCjPB7y6SxpBkrbhNWMy/65ZoncXCEPJxkRZL8McrseNH6lFhaxCIaX+vBuFnRng==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/project-service": "8.50.1",
"@typescript-eslint/tsconfig-utils": "8.50.1",
"@typescript-eslint/types": "8.50.1",
"@typescript-eslint/visitor-keys": "8.50.1",
"@typescript-eslint/project-service": "8.51.0",
"@typescript-eslint/tsconfig-utils": "8.51.0",
"@typescript-eslint/types": "8.51.0",
"@typescript-eslint/visitor-keys": "8.51.0",
"debug": "^4.3.4",
"minimatch": "^9.0.4",
"semver": "^7.6.0",
"tinyglobby": "^0.2.15",
"ts-api-utils": "^2.1.0"
"ts-api-utils": "^2.2.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2142,16 +2145,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "8.50.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.50.1.tgz",
"integrity": "sha512-lCLp8H1T9T7gPbEuJSnHwnSuO9mDf8mfK/Nion5mZmiEaQD9sWf9W4dfeFqRyqRjF06/kBuTmAqcs9sewM2NbQ==",
"version": "8.51.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.51.0.tgz",
"integrity": "sha512-11rZYxSe0zabiKaCP2QAwRf/dnmgFgvTmeDTtZvUvXG3UuAdg/GU02NExmmIXzz3vLGgMdtrIosI84jITQOxUA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.7.0",
"@typescript-eslint/scope-manager": "8.50.1",
"@typescript-eslint/types": "8.50.1",
"@typescript-eslint/typescript-estree": "8.50.1"
"@typescript-eslint/scope-manager": "8.51.0",
"@typescript-eslint/types": "8.51.0",
"@typescript-eslint/typescript-estree": "8.51.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2166,13 +2169,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "8.50.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.1.tgz",
"integrity": "sha512-IrDKrw7pCRUR94zeuCSUWQ+w8JEf5ZX5jl/e6AHGSLi1/zIr0lgutfn/7JpfCey+urpgQEdrZVYzCaVVKiTwhQ==",
"version": "8.51.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.51.0.tgz",
"integrity": "sha512-mM/JRQOzhVN1ykejrvwnBRV3+7yTKK8tVANVN3o1O0t0v7o+jqdVu9crPy5Y9dov15TJk/FTIgoUGHrTOVL3Zg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.50.1",
"@typescript-eslint/types": "8.51.0",
"eslint-visitor-keys": "^4.2.1"
},
"engines": {
@@ -2206,6 +2209,7 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -2562,6 +2566,7 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
@@ -3215,6 +3220,7 @@
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -5087,6 +5093,7 @@
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz",
"integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==",
"license": "MIT",
"peer": true,
"dependencies": {
"dompurify": "3.2.7",
"marked": "14.0.0"
@@ -5531,6 +5538,7 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz",
"integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -5540,6 +5548,7 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz",
"integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==",
"license": "MIT",
"peer": true,
"dependencies": {
"scheduler": "^0.26.0"
},
@@ -6209,6 +6218,7 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -6230,9 +6240,9 @@
}
},
"node_modules/ts-api-utils": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.3.0.tgz",
"integrity": "sha512-6eg3Y9SF7SsAvGzRHQvvc1skDAhwI4YQ32ui1scxD1Ccr0G5qIIbUBT3pFTKX8kmWIQClHobtUdNuaBgwdfdWg==",
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz",
"integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -6360,6 +6370,7 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -6369,16 +6380,16 @@
}
},
"node_modules/typescript-eslint": {
"version": "8.50.1",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.50.1.tgz",
"integrity": "sha512-ytTHO+SoYSbhAH9CrYnMhiLx8To6PSSvqnvXyPUgPETCvB6eBKmTI9w6XMPS3HsBRGkwTVBX+urA8dYQx6bHfQ==",
"version": "8.51.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.51.0.tgz",
"integrity": "sha512-jh8ZuM5oEh2PSdyQG9YAEM1TCGuWenLSuSUhf/irbVUNW9O5FhbFVONviN2TgMTBnUmyHv7E56rYnfLZK6TkiA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/eslint-plugin": "8.50.1",
"@typescript-eslint/parser": "8.50.1",
"@typescript-eslint/typescript-estree": "8.50.1",
"@typescript-eslint/utils": "8.50.1"
"@typescript-eslint/eslint-plugin": "8.51.0",
"@typescript-eslint/parser": "8.51.0",
"@typescript-eslint/typescript-estree": "8.51.0",
"@typescript-eslint/utils": "8.51.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -6468,6 +6479,7 @@
"integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.27.0",
"fdir": "^6.5.0",
@@ -6581,6 +6593,7 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -6771,6 +6784,7 @@
"integrity": "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==",
"dev": true,
"license": "MIT",
"peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}