Use the vendored runtime and generator instead of the registry dependency,
providing Unicode 17 names with Python-compatible matching. Update workspace
dependencies and tool configuration, add parser regressions, and document how
to update Unicode data when supporting a new Python version.
Copy the upstream runtime and generator before the alias-lookup API changes.
Source: https://github.com/progval/unicode_names2/tree/63eda5de2bd0dd1e6243c9797efe5a84919a756b
Adapt vendored Unicode crates to the Ruff workspace
Wire the crates into the workspace, disable nightly-only targets, and configure
lint and formatting checks to preserve the vendored source. Replace the
relocated generator's broken license symlinks with copies of the licenses.
This PR updates quick-junit to
[v0.8.0](https://github.com/nextest-rs/quick-junit/releases/tag/quick-junit-0.8.0).
The original renovate description of the bump to v0.7.0 is below.
Note that I also had to update `fuzz/Cargo.lock` to get the fuzz build to run:
```console
cargo update --manifest-path fuzz/Cargo.toml thiserror@2.0.18 --precise 2.0.20
```
<hr>
This PR contains the following updates:
| Package | Type | Update | Change | Pending |
|---|---|---|---|---|
| [quick-junit](https://redirect.github.com/nextest-rs/quick-junit) |
workspace.dependencies | minor | `0.6.0` → `0.7.0` | `0.8.0` |
---
<details>
<summary>nextest-rs/quick-junit (quick-junit)</summary>
[`v0.7.0`](https://redirect.github.com/nextest-rs/quick-junit/blob/HEAD/CHANGELOG.md#070---2026-07-20)
[Compare
Source](https://redirect.github.com/nextest-rs/quick-junit/compare/quick-junit-0.6.1...quick-junit-0.7.0)
Skipped test counts are now reported via the `skipped` attribute rather
than `disabled`. For `<testsuite>`, `skipped` matches the [llg
schema](https://llg.cubic.org/docs/junit/) and the Jenkins [junit-10
schema](https://redirect.github.com/jenkinsci/xunit-plugin/blob/master/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd),
as well as pytest and Maven Surefire.
- Each `<testsuite>` now emits `skipped="N"`, and the root
`<testsuites>` element now emits an aggregated `skipped="N"` count.
Previously the root didn't carry a skip count at all.
- Root-level `skipped` is a quick-junit extension. It follows the modern
JUnit convention (as documented, for example, by
[testmoapp/junitxml](https://redirect.github.com/testmoapp/junitxml))
and is similar to quick-junit's existing extensions such as `uuid`.
- `TestSuite`'s `disabled` field has been renamed to `skipped`.
- `Report` gains new public `skipped` and `disabled` fields, and
`Report::add_test_suite` now aggregates skip counts into
`Report::skipped`.
`disabled` is now modeled as a separate optional count on both `Report`
and `TestSuite`, distinct from `skipped`. (`disabled` is intended to
count tests that are disabled by design, such as googletest's
`DISABLED_`-prefixed tests. Note, however, that quick-junit doesn't have
a notion of disabled tests at the moment.)
- `Report` is now `#[non_exhaustive]`, consistent with `TestSuite` and
`TestCase`. Construct it via `Report::new` and then assign fields, so
that future field additions are non-breaking.
- Childless `<testsuite>` and `<testcase>` elements are now serialized
as self-closing tags (for example, `<testcase name="my-test"/>` rather
than `<testcase name="my-test"></testcase>`). The output is semantically
identical XML, only more compact and idiomatic. The deserializer already
accepted these self-closing forms, so serializing them means round-trip
tests now cover those parse paths.
</details>
---
📅 **Schedule**: (UTC)
- Branch creation
- "before 4am on Wednesday"
- 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:eyJjcmVhdGVkSW5WZXIiOiI0My4yODAuMCIsInVwZGF0ZWRJblZlciI6IjQ0Ljc5LjEiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbImludGVybmFsIl19-->
---------
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
## Summary
- Update the Rust toolchain to 1.99.0 and the MSRV to 1.97, following
our N-2 support policy.
- Address the new Clippy warnings and preserve our existing assertion
and formatting macro conventions.
- Update Hawk to 0.1.15 and run it with Rust 1.99.0.
Ruff and ty interpret `@list.py` as a response-file reference even when
that literal path exists. This can turn a filename supplied by an
integration into command-line options.
Give existing literal `@` paths precedence in both CLIs, including
dangling symlinks, arguments inside nested response files, and arguments
after `--`. When the literal path is absent, keep expanding the response
file. Both CLIs share the implementation in `ruff_cli`.
Summary
--
This PR bumps our libcst version to 1.9 and incorporates the new support
for Python 3.15 into fixes
that involved imports and unpacking in comprehensions. The unpacking
fixes affect three C4 rules and are
in the first (non-empty) commit, while the lazy import fixes are a bit
more widespread and are in
the second commit.
I admittedly wasn't that familiar with our usage of libcst before this,
but it looks like we have
kind of a weird mix between using libcst and additional manual string
manipulation. My changes
preserve that with a couple of `format!("lazy {fix}")` calls. libcst
also itself makes this change a
bit awkward by using separate node types for lazy and non-lazy imports,
so I ended up returning the
parts of the imports instead of `Import` types directly in a couple of
cases.
Test Plan
--
Updated existing snapshots for the C4 rules and new mdtests for the
import helpers
In #28092 I fixed a bug where ty's redundant-cast removal fix could
create broken code if the original `cast()` call was multi-line. Doug
commented that this could perhaps be a general-purpose utility -- and it
turned out there were two existing similar Ruff rules with pre-existing
multi-line support that could benefit from such a shared utility.
This PR introduces a shared utility in `ruff_python_edits` for removing
a function call in favor of one of its arguments. This is the common
shape between redundant-cast removal, redundant `int()` removal, and
redundant `round()` removal.
The new utility is more robust than the previous code for any of the
three cases. Previously removing redundant `int`, `round`, and
`typing.cast` calls could discard parentheses needed for line
continuation or for token separation. For example, `int(1).to_bytes()`
could become `1.to_bytes()`, which is invalid.
The shared helper preserves optional argument parentheses and the
comments inside them, adds grouping where required, and avoids redundant
parentheses when the surrounding syntax already allows the expression.
Ruff and ty retain their own diagnostic eligibility and fix-safety
decisions.
## Test plan
- Extend RUF046 and RUF057 fixtures for split callees, assignment
expressions, integer attributes, adjacent keywords, and comments inside
optional parentheses. Existing snapshot cases retain their fix text and
safety classifications.
- Add compact helper tests in `ruff_python_edits` for conditional
callees, comprehension iterables, and dictionary expressions inside
f-strings.
- Existing ty cast mdtests cover positional and keyword arguments,
operator precedence, multiline values, and comment preservation.
## Summary
- Update the Rust toolchain to 1.98.0 and the MSRV to 1.96, following
our N-2 support policy.
- Remove a Clippy lint deleted in Rust 1.98 and address the new
formatting-related warnings.
## Summary
Make `opt-level = 1`, `debug = "line-tables-only"`, and `lto = "off"`
the default development settings. This makes ordinary Cargo commands
match our existing agent workflow, avoids local ThinLTO, and removes
repeated profile overrides from `AGENTS.md`.
Preserve existing unoptimized CI behavior by explicitly setting
`CARGO_PROFILE_DEV_OPT_LEVEL=0` and `CARGO_PROFILE_DEV_LTO=false` in the
affected workflows, including documentation generation, playground
validation, typeshed synchronization, typing conformance, and daily
fuzzing. Existing CI commands, cache configuration, and cache producers
remain unchanged. Release and profiling settings are unaffected.
This follows the discussion in
https://github.com/astral-sh/ruff/pull/27526#issuecomment-5206579931.
## Summary
Configure agent-directed Cargo commands and the existing `fast-test`
profile to consistently use `opt-level = 1`, `debug =
"line-tables-only"`, and `lto = "off"`. Apply the same settings to
tests, Clippy, debug runs, and code generation while preserving ordinary
development, CI, and release defaults.
Cargo's default `lto = false` still applies local ThinLTO when
optimization is enabled, adding substantial work to incremental Rust
edits. Across 222 recent agent test commands, 110 ran one mdtest file,
41 ran the full `ty_python_semantic` suite, and 48 ran a two-crate
suite. Holding `opt-level = 1` fixed, three alternating paired runs
give:
| Workflow | Local ThinLTO | Disabled | Change |
| ------------------------------------------- | ------------: | -------:
| -----: |
| Rust edit + one mdtest file | 9.84 s | 6.81 s | -30.7% |
| Rust edit + full semantic suite (797 tests) | 24.86 s | 18.11 s |
-27.1% |
| Warm semantic suite, no edits (797 tests) | 5.28 s | 5.64 s | +6.9% |
Keeping optimization enabled remains worthwhile for broader runs. With
ThinLTO disabled, `opt-level = 1` reduced a same-crate edit followed by
all 797 semantic tests from 22.73 s to 17.27 s, and an already-built
semantic suite from 13.64 s to 5.59 s. The mdtest executable grows from
49.0 MiB to 56.3 MiB (+14.7%).
## Summary
This is a first step towards #20411. After this, we would work to either
upstream changes or adjust things within Ruff.
Performance improvements have already been upstreamed
(https://github.com/rust-lang/annotate-snippets-rs/pull/437,
https://github.com/rust-lang/annotate-snippets-rs/pull/440)
Re-implemented the following from the fork:
- Hidden snippets
(https://github.com/rust-lang/annotate-snippets-rs/issues/448)
- Renderer-level disabling of hyperlinks
- Jupyter cell indices
(https://github.com/rust-lang/annotate-snippets-rs/issues/449)
- Fixable mark
(https://github.com/rust-lang/annotate-snippets-rs/issues/450)
- `lineno_offset` (maybe not needed if #20648 is taken care of)
- anonymized lines affects origin
(https://github.com/rust-lang/annotate-snippets-rs/issues/451)
- cut indicator (maybe not needed if we can switch to unicode renderer)
Additional hacks to minimize behavior change include
- Adjust upstream's hidden severity behavior to match Ruff's
(https://github.com/rust-lang/annotate-snippets-rs/issues/452)
- How newlines are annotated
(https://github.com/rust-lang/annotate-snippets-rs/issues/454)
- To not indent for line numbers when none will be present
(https://github.com/rust-lang/annotate-snippets-rs/issues/453)
- Enabling `simd` by default
All of the above represents work that needs to be resolved to address
#20411.
`lib.name` hack is used to simplify maintenance with the assumption that
we'll continue working towards #20411.
Behavior changes that were included
- Tweak when implicit padding lines are present
- Fix Origin's line/column is now derived from the primary annotation
- Make consistent the rendering of primary/context annotations
- Fix snippets to show up for empty source (but may degrade #24528)
- Fix handling of term width
- Fix rendering of annotations between newline and first character
(granted, maybe the caller is wrong)
- Tweak multiline annotation markers
New or upcoming features annotate-snippets features that we will be able
to leverage by moving this forward
- `AnnotationKind::Visible` to improve the quality of built-in
code-folding if Ruff were to switch to it
- `DecorStyle::Unicode`
- *(upcoming)* No-graphiscs mode for screen readers (may also be useful
for AIs)
Deferred:
- Moving related `Group`s into the same `Report`
- Using `secondary_title` for any `Group` after the first in a `Report`
- Using a `Snippet`, instead of titles, for "virtual code" like
https://github.com/astral-sh/ruff/pull/27033#discussion_r3622727236
- Switching hidden snippets to `Origin`
## Test Plan
Within cpython, I ran
```console
$ hyperfine -i "../../ruff/target-baseline/release/ruff check" "../../ruff/target-update/release/ruff check" -N -m20 --output=null
Benchmark 1: ../../ruff/target-baseline/release/ruff check
Time (mean ± σ): 27.5 ms ± 1.1 ms [User: 33.0 ms, System: 108.4 ms]
Range (min … max): 25.0 ms … 31.7 ms 94 runs
Warning: Ignoring non-zero exit code.
Benchmark 2: ../../ruff/target-update/release/ruff check
Time (mean ± σ): 28.1 ms ± 2.0 ms [User: 33.4 ms, System: 105.7 ms]
Range (min … max): 24.8 ms … 38.9 ms 102 runs
Warning: Ignoring non-zero exit code.
Warning: Statistical outliers were detected. Consider re-running this benchmark on a quiet system without any interferences from other programs. It might help to use t
he '--warmup' or '--prepare' options.
Summary
../../ruff/target-baseline/release/ruff check ran
1.03 ± 0.08 times faster than ../../ruff/target-update/release/ruff check
```
Before:
<img width="486" height="583" alt="image"
src="https://github.com/user-attachments/assets/2d94dabf-b28d-48c1-b17c-52aac59fd57e"
/>
After:
<img width="486" height="583" alt="image"
src="https://github.com/user-attachments/assets/b388ada3-e3d7-4ba0-b90b-1330eb81f4be"
/>
## Summary
Bumps gen-lsp-types to v0.11.0, which enables all LSP enumeration types
to accept custom values. Fixes
https://github.com/astral-sh/ty/issues/4082.
## Test Plan
Tests are kept the same, only changed where necessary due to the library
change.
## Summary
This PR changes `ruff_python_ast::Name` from a 24-byte `CompactString`
to a 16-byte immutable `CharStr` from
[`astral-sh/char_str`](https://github.com/astral-sh/char_str).
`char_str` is an Astral-maintained fork of
[`lean_string`](https://github.com/ryota2357/lean_string) that provides
compact owned strings with mutable and immutable variants: growable
`CharString` for construction and immutable, exactly allocated `CharStr`
for storage. (It also provides Salsa and get-size features.)
Names up to 16 UTF-8 bytes remain inline, static names remain
allocation-free, and longer names use an exactly sized,
reference-counted allocation with no unused capacity.
Removing eight bytes from every `Name` also shrinks common AST nodes.
For example, `Identifier` and `ExprName` drop from 40 to 32 bytes, while
`ExprAttribute`, `StmtFunctionDef`, `StmtClassDef`, and several other
variants each drop by eight bytes.
The parser interns names that exceed the inline capacity, so repeated
longer names share one backing allocation within a parser invocation.
## Why 16 bytes?
Across the full `mypy_primer` corpus, there 20.8 million name
occurrences:
| UTF-8 length | Occurrences | Share | Unique name/file pairs | Share |
|---|---:|---:|---:|---:|
| 1–16 bytes | 18,955,757 | 91.02% | 3,192,424 | 79.42% |
| 17–24 bytes | 1,348,894 | 6.48% | 529,826 | 13.18% |
| 25+ bytes | 522,376 | 2.51% | 297,575 | 7.40% |
| Total | 20,827,027 | 100% | 4,019,825 | 100% |
The 16-byte cutoff keeps 91% of occurrences inline while saving eight
bytes for every occurrence. The 1–16-byte bucket alone removes 151.6 MB
of handle storage across the corpus.
The tradeoff is the 17–24-byte bucket: `CompactString` stored those
names inline, while `CharStr` allocates once per unique name within a
file. But the interning amortizes that cost when a name repeats. Names
longer than 24 bytes already allocated on `main`; for those names, this
change both reduces the handle size and deduplicates repeated backing
allocations.
## Memory impact
Accounting for the observed per-file multiplicities and structural
sharing, modeled `Name` storage across the corpus changes as follows:
| | `main` | This PR | Difference |
|---|---:|---:|---:|
| `Name` handles and backing storage | 526.18 MB | 366.27 MB | -159.91
MB (-30.4%) |
The current [instrumented memory
report](https://github.com/astral-sh/ruff/pull/26594#issuecomment-4906725175)
shows a consistent reduction in total retained memory:
| Project | `main` | This PR | Difference |
|---|---:|---:|---:|
| flake8 | 29.21 MB | 28.71 MB | -1.69% |
| trio | 71.01 MB | 70.01 MB | -1.41% |
| sphinx | 168.81 MB | 166.82 MB | -1.18% |
| prefect | 454.75 MB | 449.06 MB | -1.25% |
Within those totals, `parsed_module` decreases by roughly 2.1% and
`semantic_index` by 2.4–2.7%. Several name-heavy collections decrease by
considerably more.
Summary
--
This PR was split off from #26772 to add support for testing TOML lint
rules to mdtest. We use the
toml_parser crate to extract comments and now call `lint_pyproject_toml`
in ruff_mdtest. Because we already use `toml` blocks for test
configuration and because Ruff's current TOML lints need to know the
filename, I opted to require a filename for linted TOML blocks. For
example,
````markdown
```toml
lint.select = ["F401"]
```
````
remains a configuration block, but
````markdown
`ruff.toml`:
```toml
lint.select = ["F401"]
```
````
will be linted.
Test Plan
--
Added one test for RUF200 and unit tests for the parsing itself.