Compare commits

...

301 Commits

Author SHA1 Message Date
Brent Westbrook
79d526cd91 attempting a fix 2025-11-11 16:33:59 -05:00
Brent Westbrook
376571eed1 add test files from #21385 2025-11-11 15:48:47 -05:00
Aria Desires
e4374f14ed [ty] Consider from thispackage import y to re-export y in __init__.pyi (#21387)
Fixes https://github.com/astral-sh/ty/issues/1487

This one is a true extension of non-standard semantics, and is therefore
a certified Hot Take we might conclude is simply a Bad Take (let's see
what ecosystem tests say...).
2025-11-11 14:41:14 -05:00
Alex Waygood
03bd0619e9 [ty] Silence false-positive diagnostics when using typing.Dict or typing.Callable as the second argument to isinstance() (#21386) 2025-11-11 19:30:01 +00:00
Aria Desires
bd8812127d [ty] support absolute from imports introducing local submodules in __init__.py files (#21372)
By resolving `.` and the LHS of the from import during semantic
indexing, we can check if the LHS is a submodule of `.`, and handle
`from whatever.thispackage.x.y import z` exactly like we do `from .x.y
import z`.

Fixes https://github.com/astral-sh/ty/issues/1484
2025-11-11 13:04:42 -05:00
Alex Waygood
44b0c9ebac [ty] Allow PEP-604 unions in stubs and TYPE_CHECKING blocks prior to 3.10 (#21379) 2025-11-11 14:33:43 +00:00
Micha Reiser
7b237d316f Add option to provide a reason to --add-noqa (#21294)
Co-authored-by: Claude <noreply@anthropic.com>
2025-11-11 14:03:46 +01:00
Micha Reiser
36cce347fd Reduce notebook memory footprint (#21319) 2025-11-11 10:43:37 +01:00
Douglas Creager
33b942c7ad [ty] Handle annotated self parameter in constructor of non-invariant generic classes (#21325)
This manifested as an error when inferring the type of a PEP-695 generic
class via its constructor parameters:

```py
class D[T, U]:
    @overload
    def __init__(self: "D[str, U]", u: U) -> None: ...
    @overload
    def __init__(self, t: T, u: U) -> None: ...
    def __init__(self, *args) -> None: ...

# revealed: D[Unknown, str]
# SHOULD BE: D[str, str]
reveal_type(D("string"))
```

This manifested because `D` is inferred to be bivariant in both `T` and
`U`. We weren't seeing this in the equivalent example for legacy
typevars, since those default to invariant. (This issue also showed up
for _covariant_ typevars, so this issue was not limited to bivariance.)

The underlying cause was because of a heuristic that we have in our
current constraint solver, which attempts to handle situations like
this:

```py
def f[T](t: T | None): ...
f(None)
```

Here, the `None` argument matches the non-typevar union element, so this
argument should not add any constraints on what `T` can specialize to.
Our previous heuristic would check for this by seeing if the argument
type is a subtype of the parameter annotation as a whole — even if it
isn't a union! That would cause us to erroneously ignore the `self`
parameter in our constructor call, since bivariant classes are
equivalent to each other, regardless of their specializations.

The quick fix is to move this heuristic "down a level", so that we only
apply it when the parameter annotation is a union. This heuristic should
go away completely 🤞 with the new constraint solver.
2025-11-10 19:46:49 -05:00
Aria Desires
9ce3230add [ty] Make implicit submodule imports only occur in global scope (#21370)
This loses any ability to have "per-function" implicit submodule
imports, to avoid the "ok but now we need per-scope imports" and "ok but
this should actually introduce a global that only exists during this
function" problems. A simple and clean implementation with no weird
corners.

Fixes https://github.com/astral-sh/ty/issues/1482
2025-11-10 18:59:48 -05:00
Aria Desires
2bc6c78e26 [ty] introduce local variables for from imports of submodules in __init__.py(i) (#21173)
This rips out the previous implementation in favour of a new
implementation with 3 rules:

- **froms are locals**: a `from..import` can only define locals, it does
not have global
side-effects. Specifically any submodule attribute `a` that's implicitly
introduced by either
`from .a import b` or `from . import a as b` (in an `__init__.py(i)`) is
a local and not a
global. If you do such an import at the top of a file you won't notice
this. However if you do
such an import in a function, that means it will only be function-scoped
(so you'll need to do
it in every function that wants to access it, making your code less
sensitive to execution
    order).

- **first from first serve**: only the *first* `from..import` in an
`__init__.py(i)` that imports a
particular direct submodule of the current package introduces that
submodule as a local.
Subsequent imports of the submodule will not introduce that local. This
reflects the fact that
in actual python only the first import of a submodule (in the entire
execution of the program)
introduces it as an attribute of the package. By "first" we mean "the
first time in this scope
(or any parent scope)". This pairs well with the fact that we are
specifically introducing a
local (as long as you don't accidentally shadow or overwrite the local).

- **dot re-exports**: `from . import a` in an `__init__.pyi` is
considered a re-export of `a`
(equivalent to `from . import a as a`). This is required to properly
handle many stubs in the
    wild. Currently it must be *exactly* `from . import ...`.
    
This implementation is intentionally limited/conservative (notably,
often requiring a from import to be relative). I'm going to file a ton
of followups for improvements so that their impact can be evaluated
separately.


Fixes https://github.com/astral-sh/ty/issues/133
2025-11-10 23:04:56 +00:00
Dan Parizher
1fd852fb3f [ruff] Ignore str() when not used for simple conversion (RUF065) (#21330)
## Summary

Fixed RUF065 (`logging-eager-conversion`) to only flag `str()` calls
when they perform a simple conversion that can be safely removed. The
rule now ignores `str()` calls with no arguments, multiple arguments,
starred arguments, or keyword unpacking, preventing false positives.

Fixes #21315

## Problem Analysis

The RUF065 rule was incorrectly flagging all `str()` calls in logging
statements, even when `str()` was performing actual conversion work
beyond simple type coercion. Specifically, the rule flagged:

- `str()` with no arguments - which returns an empty string
- `str(b"data", "utf-8")` with multiple arguments - which performs
encoding conversion
- `str(*args)` with starred arguments - which unpacks arguments
- `str(**kwargs)` with keyword unpacking - which passes keyword
arguments

These cases cannot be safely removed because `str()` is doing meaningful
work (encoding conversion, argument unpacking, etc.), not just redundant
type conversion.

The root cause was that the rule only checked if the function was
`str()` without validating the call signature. It didn't distinguish
between simple `str(value)` conversions (which can be removed) and more
complex `str()` calls that perform actual work.

## Approach

The fix adds validation to the `str()` detection logic in
`logging_eager_conversion.rs`:

1. **Check argument count**: Only flag `str()` calls with exactly one
positional argument (`str_call_args.args.len() == 1`)
2. **Check for starred arguments**: Ensure the single argument is not
starred (`!str_call_args.args[0].is_starred_expr()`)
3. **Check for keyword arguments**: Ensure there are no keyword
arguments (`str_call_args.keywords.is_empty()`)

This ensures the rule only flags cases like `str(value)` where `str()`
is truly redundant and can be removed, while ignoring cases where
`str()` performs actual conversion work.

The fix maintains backward compatibility - all existing valid test cases
continue to be flagged correctly, while the new edge cases are properly
ignored.

---------

Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2025-11-10 18:04:41 -05:00
Jack O'Connor
5f3e086ee4 [ty] implement typing.NewType by adding Type::NewTypeInstance 2025-11-10 14:55:47 -08:00
Aria Desires
039a69fa8c [ty] supress inlay hints for +1 and -1 (#21368)
It's everyone's favourite language corner case!

Also having kicked the tires on it, I'm pretty happy to call this (in
conjunction with #21367):

Fixes https://github.com/astral-sh/ty/issues/494

There's cases where you can make noisy Literal hints appear, so we can
always iterate on it, but this handles like, 98% of the cases in the
wild, which is great.

---------

Co-authored-by: David Peter <sharkdp@users.noreply.github.com>
2025-11-10 21:56:25 +00:00
Ibraheem Ahmed
3656b44877 [ty] Use type context for inference of generic constructors (#20933)
## Summary

Resolves https://github.com/astral-sh/ty/issues/1228.

This PR is stacked on https://github.com/astral-sh/ruff/pull/21210.
2025-11-10 21:49:48 +00:00
Ibraheem Ahmed
98869f0307 [ty] Improve generic call expression inference (#21210)
## Summary

Implements https://github.com/astral-sh/ty/issues/1356 and https://github.com/astral-sh/ty/issues/136#issuecomment-3413669994.
2025-11-10 21:29:05 +00:00
Aria Desires
d258302b08 [ty] supress some trivial expr inlay hints (#21367)
I'm not 100% sold on this implementation, but it's a strict improvement
and it adds a ton of snapshot tests for future iteration.

Part of https://github.com/astral-sh/ty/issues/494
2025-11-10 19:51:14 +00:00
Dan Parizher
deeda56906 [configuration] Fix unclear error messages for line-length values exceeding u16::MAX (#21329)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-11-10 18:29:35 +00:00
justin
f63a9f2334 [ty] Fix incorrect inference of enum.auto() for enums with non-int mixins, and imprecise inference of enum.auto() for single-member enums (#20541)
Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
2025-11-10 17:53:08 +00:00
Dan Parizher
e4dc406a3d [refurb] Detect empty f-strings (FURB105) (#21348)
## Summary

Fixes FURB105 (`print-empty-string`) to detect empty f-strings in
addition to regular empty strings. Previously, the rule only flagged
`print("")` but missed `print(f"")`. This fix ensures both cases are
detected and can be automatically fixed.

Fixes #21346

## Problem Analysis

The FURB105 rule checks for unnecessary empty strings passed to
`print()` calls. The `is_empty_string` helper function was only checking
for `Expr::StringLiteral` with empty values, but did not handle
`Expr::FString` (f-strings). As a result, `print(f"")` was not being
flagged as a violation, even though it's semantically equivalent to
`print("")` and should be simplified to `print()`.

The issue occurred because the function used a `matches!` macro that
only checked for string literals:

```rust
fn is_empty_string(expr: &Expr) -> bool {
    matches!(
        expr,
        Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) if value.is_empty()
    )
}
```

## Approach

1. **Import the helper function**: Added `is_empty_f_string` to the
imports from `ruff_python_ast::helpers`, which already provides logic to
detect empty f-strings.

2. **Update `is_empty_string` function**: Changed the implementation
from a `matches!` macro to a `match` expression that handles both string
literals and f-strings:

   ```rust
   fn is_empty_string(expr: &Expr) -> bool {
       match expr {
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) =>
value.is_empty(),
           Expr::FString(f_string) => is_empty_f_string(f_string),
           _ => false,
       }
   }
   ```

The fix leverages the existing `is_empty_f_string` helper function which
properly handles the complexity of f-strings, including nested f-strings
and interpolated expressions. This ensures the detection is accurate and
consistent with how empty strings are detected elsewhere in the
codebase.
2025-11-10 12:41:44 -05:00
Matthew Mckee
1d188476b6 [ty] provide import completion when in from <name> <name> statement (#21291)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

Resolves https://github.com/astral-sh/ty/issues/1494

## Test Plan

Add a test showing if we are in `from <name> <name> ` we provide the
keyword completion "import"
2025-11-10 11:59:45 -05:00
Aria Desires
4821c050ef [ty] elide redundant inlay hints for function args (#21365)
This elides the following inlay hints:

```py
foo([x=]x)
foo([x=]y.x)
foo([x=]x[0])
foo([x=]x(...))

# composes to complex situations
foo([x=]y.x(..)[0])
```

Fixes https://github.com/astral-sh/ty/issues/1514
2025-11-10 11:42:12 -05:00
Brent Westbrook
835e31b3ff Fix syntax error false positive on alternative match patterns (#21362)
Summary
--

Fixes #21360 by using the union of names instead of overwriting them, as
Micha suggested originally on #21104.

This avoids overwriting the `n` name in the `Subscript` by the empty set
of names visited in the nested OR pattern before visiting the other arm
of the outer OR pattern.

Test Plan
--

A new inline test case taken from the issue
2025-11-10 10:51:51 -05:00
Brent Westbrook
8d1efe964a Add a new "Opening a PR" section to the contribution guide (#21298)
Summary
--

This PR adds a new section to CONTRIBUTING.md describing the expected
contents of the PR summary and test plan, using the ecosystem report,
and communicating the status of a PR.

This seemed like a pretty good place to insert this in the document, at
the end of the advice on preparing actual code changes, but I'm
certainly open to other suggestions about both the content and
placement.

Test Plan
--

Future PRs :)

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
2025-11-10 14:12:32 +00:00
Dan Parizher
04e7cecab3 [flake8-simplify] Fix SIM222 false positive for tuple(generator) or None (SIM222) (#21187)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-11-10 14:27:31 +01:00
Micha Reiser
84a810736d Rebuild ruff binary instead of sharing it across jobs (#21361) 2025-11-10 14:27:07 +01:00
Micha Reiser
f44598dc11 [ty] Fix --exclude and src.exclude merging (#21341) 2025-11-10 12:52:45 +01:00
David Peter
ab46c8de0f [ty] Add support for properties that return Self (#21335)
## Summary

Detect usages of implicit `self` in property getters, which allows us to
treat their signature as being generic.

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

## Typing conformance

Two new type assertions that are succeeding.

## Ecosystem results

Mostly look good. There are a few new false positives related to a bug
with constrained typevars that is unrelated to the work here. I reported
this as https://github.com/astral-sh/ty/issues/1503.

## Test Plan

Added regression tests.
2025-11-10 11:13:36 +01:00
Henry Schreiner
a6f2dee33b Add upstream linter URL to ruff linter --output-format=json (#21316) 2025-11-10 10:42:09 +01:00
David Peter
238f151371 [ty] Add support for Optional and Annotated in implicit type aliases (#21321)
## Summary

Add support for `Optional` and `Annotated` in implicit type aliases

part of https://github.com/astral-sh/ty/issues/221

## Typing conformance changes

New expected diagnostics.

## Ecosystem

A lot of true positives, some known limitations unrelated to this PR.

## Test Plan

New Markdown tests
2025-11-10 10:24:38 +01:00
Micha Reiser
3fa609929f Save rust cache for CI jobs on main only (#21359) 2025-11-10 10:04:44 +01:00
Alex Waygood
73b1fce74a [ty] Add diagnostics for isinstance() and issubclass() calls that use invalid PEP-604 unions for their second argument (#21343)
## Summary

This PR adds extra validation for `isinstance()` and `issubclass()`
calls that use `UnionType` instances for their second argument.
According to typeshed's annotations, any `UnionType` is accepted for the
second argument, but this isn't true at runtime: at runtime, all
elements in the `UnionType` must either be class objects or be `None` in
order for the `isinstance()` or `issubclass()` call to reliably succeed:

```pycon
% uvx python3.14                            
Python 3.14.0 (main, Oct 10 2025, 12:54:13) [Clang 20.1.4 ] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from typing import LiteralString
>>> import types
>>> type(LiteralString | int) is types.UnionType
True
>>> isinstance(42, LiteralString | int)
Traceback (most recent call last):
  File "<python-input-5>", line 1, in <module>
    isinstance(42, LiteralString | int)
    ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/alexw/Library/Application Support/uv/python/cpython-3.14.0-macos-aarch64-none/lib/python3.14/typing.py", line 559, in __instancecheck__
    raise TypeError(f"{self} cannot be used with isinstance()")
TypeError: typing.LiteralString cannot be used with isinstance()
```

## Test Plan

Added mdtests/snapshots
2025-11-10 08:46:31 +00:00
renovate[bot]
52bd22003b Update Rust crate libcst to v1.8.6 (#21355)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-10 09:39:46 +01:00
renovate[bot]
0a6d6b6194 Update cargo-bins/cargo-binstall action to v1.15.11 (#21352)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[cargo-bins/cargo-binstall](https://redirect.github.com/cargo-bins/cargo-binstall)
| action | patch | `v1.15.10` -> `v1.15.11` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>cargo-bins/cargo-binstall (cargo-bins/cargo-binstall)</summary>

###
[`v1.15.11`](https://redirect.github.com/cargo-bins/cargo-binstall/releases/tag/v1.15.11)

[Compare
Source](https://redirect.github.com/cargo-bins/cargo-binstall/compare/v1.15.10...v1.15.11)

*Binstall is a tool to fetch and install Rust-based executables as
binaries. It aims to be a drop-in replacement for `cargo install` in
most cases. Install it today with `cargo install cargo-binstall`, from
the binaries below, or if you already have it, upgrade with `cargo
binstall cargo-binstall`.*

##### In this release:

- Fix binstalk-downloader cannot decode some zip files on macos (x64 and
arm64) platforms
([#&#8203;2049](https://redirect.github.com/cargo-bins/cargo-binstall/issues/2049)
[#&#8203;2362](https://redirect.github.com/cargo-bins/cargo-binstall/issues/2362))
- Fix grammer in `HELP.md` and `--help` output
([#&#8203;2357](https://redirect.github.com/cargo-bins/cargo-binstall/issues/2357)
[#&#8203;2359](https://redirect.github.com/cargo-bins/cargo-binstall/issues/2359))
- Update documentation link in Cargo.toml
([#&#8203;2355](https://redirect.github.com/cargo-bins/cargo-binstall/issues/2355))

##### Other changes:

- Upgrade dependencies

</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:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNTkuNCIsInVwZGF0ZWRJblZlciI6IjQxLjE1OS40IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJpbnRlcm5hbCJdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-10 09:17:37 +01:00
renovate[bot]
ca51feb319 Update Rust crate jiff to v0.2.16 (#21354)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [jiff](https://redirect.github.com/BurntSushi/jiff) |
workspace.dependencies | patch | `0.2.15` -> `0.2.16` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>BurntSushi/jiff (jiff)</summary>

###
[`v0.2.16`](https://redirect.github.com/BurntSushi/jiff/blob/HEAD/CHANGELOG.md#0216-2025-11-07)

[Compare
Source](https://redirect.github.com/BurntSushi/jiff/compare/0.2.15...0.2.16)

\===================
This release contains a number of enhancements and bug fixes that have
accrued
over the last few months. Most are small polishes. A couple of the bug
fixes
apply to panics that could occur when parsing invalid `TZ` strings or
invalid
`strptime` format strings.

Also, parsing into a `Span` should now be much faster (for both the ISO
8601
and "friendly" duration formats).

Enhancements:

- [#&#8203;298](https://redirect.github.com/BurntSushi/jiff/issues/298):
  Add Serde helpers for (de)serializing `std::time::Duration` values.
- [#&#8203;396](https://redirect.github.com/BurntSushi/jiff/issues/396):
Add `Sub` and `Add` trait implementations for `Zoned` (in addition to
the
  already existing trait implementations for `&Zoned`).
- [#&#8203;397](https://redirect.github.com/BurntSushi/jiff/pull/397):
Add `BrokenDownTime::set_meridiem` and ensure it overrides the hour when
  formatting.
- [#&#8203;409](https://redirect.github.com/BurntSushi/jiff/pull/409):
Switch dependency on `serde` to `serde_core`. This should help speed up
  compilation times in some cases.
- [#&#8203;430](https://redirect.github.com/BurntSushi/jiff/pull/430):
Add new `Zoned::series` API, making it consistent with the same API on
other
  datetime types.
- [#&#8203;432](https://redirect.github.com/BurntSushi/jiff/pull/432):
When `lenient` mode is enabled for `strftime`, Jiff will no longer error
when
  the formatting string contains invalid UTF-8.
- [#&#8203;432](https://redirect.github.com/BurntSushi/jiff/pull/432):
Formatting of `%y` and `%g` no longer fails based on the specific year
value.
- [#&#8203;432](https://redirect.github.com/BurntSushi/jiff/pull/432):
Parsing of `%s` is now a bit more consistent with other fields.
Moreover,
`BrokenDownTime::{to_timestamp,to_zoned}` will now prefer timestamps
parsed
  with `%s` over any other fields that have been parsed.
- [#&#8203;433](https://redirect.github.com/BurntSushi/jiff/pull/433):
Allow parsing just a `%s` into a `Zoned` via the `Etc/Unknown` time
zone.

Bug fixes:

- [#&#8203;386](https://redirect.github.com/BurntSushi/jiff/issues/386):
Fix a bug where `2087-12-31T23:00:00Z` in the `Africa/Casablanca` time
zone
could not be round-tripped (because its offset was calculated
incorrectly as
  a result of not handling "permanent DST" POSIX time zones).
- [#&#8203;407](https://redirect.github.com/BurntSushi/jiff/issues/407):
Fix a panic that occurred when parsing an empty string as a POSIX time
zone.
- [#&#8203;410](https://redirect.github.com/BurntSushi/jiff/issues/410):
  Fix a panic that could occur when parsing `%:` via `strptime` APIs.
- [#&#8203;414](https://redirect.github.com/BurntSushi/jiff/pull/414):
Update some parts of the documentation to indicate that
`TimeZone::unknown()`
is a fallback for `TimeZone::system()` (instead of the `jiff 0.1`
behavior of
  using `TimeZone::UTC`).
- [#&#8203;423](https://redirect.github.com/BurntSushi/jiff/issues/423):
  Fix a panicking bug when reading malformed TZif data.
- [#&#8203;426](https://redirect.github.com/BurntSushi/jiff/issues/426):
  Fix a panicking bug when parsing century (`%C`) via `strptime`.
- [#&#8203;445](https://redirect.github.com/BurntSushi/jiff/pull/445):
  Fixed bugs with parsing durations like `-9223372036854775808s`
  and `-PT9223372036854775808S`.

Performance:

- [#&#8203;445](https://redirect.github.com/BurntSushi/jiff/pull/445):
Parsing into `Span` or `SignedDuration` is now a fair bit faster in some
cases.

</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:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNTkuNCIsInVwZGF0ZWRJblZlciI6IjQxLjE1OS40IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJpbnRlcm5hbCJdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-10 09:16:50 +01:00
renovate[bot]
e0a3cbb048 Update Rust crate quote to v1.0.42 (#21356)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [quote](https://redirect.github.com/dtolnay/quote) |
workspace.dependencies | patch | `1.0.41` -> `1.0.42` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>dtolnay/quote (quote)</summary>

###
[`v1.0.42`](https://redirect.github.com/dtolnay/quote/releases/tag/1.0.42)

[Compare
Source](https://redirect.github.com/dtolnay/quote/compare/1.0.41...1.0.42)

- Tweaks to improve build speed
([#&#8203;305](https://redirect.github.com/dtolnay/quote/issues/305),
[#&#8203;306](https://redirect.github.com/dtolnay/quote/issues/306),
[#&#8203;307](https://redirect.github.com/dtolnay/quote/issues/307),
[#&#8203;308](https://redirect.github.com/dtolnay/quote/issues/308),
thanks [@&#8203;dishmaker](https://redirect.github.com/dishmaker))

</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:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNTkuNCIsInVwZGF0ZWRJblZlciI6IjQxLjE1OS40IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJpbnRlcm5hbCJdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-10 09:15:51 +01:00
renovate[bot]
f4f259395c Update taiki-e/install-action action to v2.62.49 (#21358)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[taiki-e/install-action](https://redirect.github.com/taiki-e/install-action)
| action | patch | `v2.62.45` -> `v2.62.49` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>taiki-e/install-action (taiki-e/install-action)</summary>

###
[`v2.62.49`](https://redirect.github.com/taiki-e/install-action/blob/HEAD/CHANGELOG.md#100---2021-12-30)

[Compare
Source](https://redirect.github.com/taiki-e/install-action/compare/v2.62.48...v2.62.49)

Initial release

[Unreleased]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.49...HEAD

[2.62.49]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.48...v2.62.49

[2.62.48]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.47...v2.62.48

[2.62.47]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.46...v2.62.47

[2.62.46]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.45...v2.62.46

[2.62.45]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.44...v2.62.45

[2.62.44]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.43...v2.62.44

[2.62.43]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.42...v2.62.43

[2.62.42]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.41...v2.62.42

[2.62.41]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.40...v2.62.41

[2.62.40]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.39...v2.62.40

[2.62.39]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.38...v2.62.39

[2.62.38]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.37...v2.62.38

[2.62.37]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.36...v2.62.37

[2.62.36]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.35...v2.62.36

[2.62.35]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.34...v2.62.35

[2.62.34]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.33...v2.62.34

[2.62.33]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.32...v2.62.33

[2.62.32]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.31...v2.62.32

[2.62.31]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.30...v2.62.31

[2.62.30]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.29...v2.62.30

[2.62.29]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.28...v2.62.29

[2.62.28]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.27...v2.62.28

[2.62.27]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.26...v2.62.27

[2.62.26]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.25...v2.62.26

[2.62.25]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.24...v2.62.25

[2.62.24]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.23...v2.62.24

[2.62.23]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.22...v2.62.23

[2.62.22]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.21...v2.62.22

[2.62.21]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.20...v2.62.21

[2.62.20]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.19...v2.62.20

[2.62.19]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.18...v2.62.19

[2.62.18]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.17...v2.62.18

[2.62.17]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.16...v2.62.17

[2.62.16]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.15...v2.62.16

[2.62.15]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.14...v2.62.15

[2.62.14]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.13...v2.62.14

[2.62.13]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.12...v2.62.13

[2.62.12]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.11...v2.62.12

[2.62.11]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.10...v2.62.11

[2.62.10]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.9...v2.62.10

[2.62.9]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.8...v2.62.9

[2.62.8]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.7...v2.62.8

[2.62.7]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.6...v2.62.7

[2.62.6]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.5...v2.62.6

[2.62.5]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.4...v2.62.5

[2.62.4]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.3...v2.62.4

[2.62.3]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.2...v2.62.3

[2.62.2]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.1...v2.62.2

[2.62.1]:
https://redirect.github.com/taiki-e/install-action/compare/v2.62.0...v2.62.1

[2.62.0]:
https://redirect.github.com/taiki-e/install-action/compare/v2.61.13...v2.62.0

[2.61.13]:
https://redirect.github.com/taiki-e/install-action/compare/v2.61.12...v2.61.13

[2.61.12]:
https://redirect.github.com/taiki-e/install-action/compare/v2.61.11...v2.61.12

[2.61.11]:
https://redirect.github.com/taiki-e/install-action/compare/v2.61.10...v2.61.11

[2.61.10]:
https://redirect.github.com/taiki-e/install-action/compare/v2.61.9...v2.61.10

[2.61.9]:
https://redirect.github.com/taiki-e/install-action/compare/v2.61.8...v2.61.9

[2.61.8]:
https://redirect.github.com/taiki-e/install-action/compare/v2.61.7...v2.61.8

[2.61.7]:
https://redirect.github.com/taiki-e/install-action/compare/v2.61.6...v2.61.7

[2.61.6]:
https://redirect.github.com/taiki-e/install-action/compare/v2.61.5...v2.61.6

[2.61.5]:
https://redirect.github.com/taiki-e/install-action/compare/v2.61.4...v2.61.5

[2.61.4]:
https://redirect.github.com/taiki-e/install-action/compare/v2.61.3...v2.61.4

[2.61.3]:
https://redirect.github.com/taiki-e/install-action/compare/v2.61.2...v2.61.3

[2.61.2]:
https://redirect.github.com/taiki-e/install-action/compare/v2.61.1...v2.61.2

[2.61.1]:
https://redirect.github.com/taiki-e/install-action/compare/v2.61.0...v2.61.1

[2.61.0]:
https://redirect.github.com/taiki-e/install-action/compare/v2.60.0...v2.61.0

[2.60.0]:
https://redirect.github.com/taiki-e/install-action/compare/v2.59.1...v2.60.0

[2.59.1]:
https://redirect.github.com/taiki-e/install-action/compare/v2.59.0...v2.59.1

[2.59.0]:
https://redirect.github.com/taiki-e/install-action/compare/v2.58.33...v2.59.0

[2.58.33]:
https://redirect.github.com/taiki-e/install-action/compare/v2.58.32...v2.58.33

[2.58.32]:
https://redirect.github.com/taiki-e/install-action/compare/v2.58.31...v2.58.32

[2.58.31]:
https://redirect.github.com/taiki-e/install-action/compare/v2.58.30...v2.58.31

[2.58.30]:
https://redirect.github.com/taiki-e/install-action/compare/v2.58.29...v2.58.30

[2.58.29]:
https://redirect.github.com/taiki-e/install-action/compare/v2.58.28...v2.58.29

[2.58.28]:
https://redirect.github.com/taiki-e/install-action/compare/v2.58.27...v2.58.28

[2.58.27]:
https://redirect.github.com/taiki-e/install-action/compare/v2.58.26...v2.58.27

[2.58.26]:
https://redirect.github.com/taiki-e/install-action/compare/v2.58.25...v2.58.26

[2.58.25]:
https://redirect.github.com/taiki-e/install-action/compare/v2.58.24...v2.58.25

[2.58.24]:
https://redirect.github.com/taiki-e/install-action/compare/v2.58.23...v2.58.24

[2.58.23]:
https://redirect.github.com/taiki-e/install-action/compare/v2.58.22...v2.58.23

[2.58.22]:
https://redirect.github.com/taiki-e/install-action/compare/v2.58.21...v2.58.22

[2.58.21]:
https://redirect.github.com/taiki-e/install-action/compare/v2.58.20...v2.58.21

[2.58.20]:
https://redirect.github.com/taiki-e/install-action/compare/v2.58.19...v2.58.20

[2.58.19]:
https://redirect.github.com/taiki-e/install-action/compare/v2.58.18...v2.58.19

[2.58.18]:
https://redirect.github.com/taiki-e/install-action/compare/v2.58.17...v2.58.18

[2.58.17]:
https://redirect.github.com/taiki-e/install-action/compare/v2.58.16...v2.58.17

[2.58.16]:
https://redirect.github.com/taiki-e/install-action/compare/v2.58.15...v2.58.16

[2.58.15]:
https://redirect.github.com/taiki-e/install-action/compare/v2.58.14...v2.58.15

[2.58.14]:
https://redirect.github.com/taiki-e/install-action/compare/v2.58.13...v2.58.14

[2.58.13]:
https://redirect.github.com/taiki-e/install-action/compare/v2.58.12...v2.58.13

[2.58.12]:
https://redirect.github.com/taiki-e/install-action/compare/v2.58.11...v2.58.12

[2.58.11]:
https://redirect.github.com/taiki-e/install-action/compare/v2.58.10...v2.58.11

[2.58.10]:
https://redirect.github.com/taiki-e/install-action/compare/v2.58.9...v2.58.10

[2.58.9]:
https://redirect.github.com/taiki-e/install-action/compare/v2.58.8...v2.58.9

[2.58.8]:
https://redirect.github.com/taiki-e/install-action/compare/v2.58.7...v2.58.8

[2.58.7]:
https://redirect.github.com/taiki-e/install-action/compare/v2.58.6...v2.58.7

[2.58.6]:
https://redirect.github.com/taiki-e/install-action/compare/v2.58.5...v2.58.6

[2.58.5]:
https://redirect.github.com/taiki-e/install-action/compare/v2.58.4...v2.58.5

[2.58.4]:
https://redirect.github.com/taiki-e/install-action/compare/v2.58.3...v2.58.4

[2.58.3]:
https://redirect.github.com/taiki-e/install-action/compare/v2.58.2...v2.58.3

[2.58.2]:
https://redirect.github.com/taiki-e/install-action/compare/v2.58.1...v2.58.2

[2.58.1]:
https://redirect.github.com/taiki-e/install-action/compare/v2.58.0...v2.58.1

[2.58.0]:
https://redirect.github.com/taiki-e/install-action/compare/v2.57.8...v2.58.0

[2.57.8]:
https://redirect.github.com/taiki-e/install-action/compare/v2.57.7...v2.57.8

[2.57.7]:
https://redirect.github.com/taiki-e/install-action/compare/v2.57.6...v2.57.7

[2.57.6]:
https://redirect.github.com/taiki-e/install-action/compare/v2.57.5...v2.57.6

[2.57.5]:
https://redirect.github.com/taiki-e/install-action/compare/v2.57.4...v2.57.5

[2.57.4]:
https://redirect.github.com/taiki-e/install-action/compare/v2.57.3...v2.57.4

[2.57.3]:
https://redirect.github.com/taiki-e/install-action/compare/v2.57.2...v2.57.3

[2.57.2]:
https://redirect.github.com/taiki-e/install-action/compare/v2.57.1...v2.57.2

[2.57.1]:
https://redirect.github.com/taiki-e/install-action/compare/v2.57.0...v2.57.1

[2.57.0]:
https://redirect.github.com/taiki-e/install-action/compare/v2.56.24...v2.57.0

[2.56.24]:
https://redirect.github.com/taiki-e/install-action/compare/v2.56.23...v2.56.24

[2.56.23]:
https://redirect.github.com/taiki-e/install-action/compare/v2.56.22...v2.56.23

[2.56.22]:
https://redirect.github.com/taiki-e/install-action/compare/v2.56.21...v2.56.22

[2.56.21]:
https://redirect.github.com/taiki-e/install-action/compare/v2.56.20...v2.56.21

[2.56.20]:
https://redirect.github.com/taiki-e/install-action/compare/v2.56.19...v2.56.20

[2.56.19]:
https://redirect.github.com/taiki-e/install-action/compare/v2.56.18...v2.56.19

[2.56.18]:
https://redirect.github.com/taiki-e/install-action/compare/v2.56.17...v2.56.18

[2.56.17]:
https://redirect.github.com/taiki-e/install-action/compare/v2.56.16...v2.56.17

[2.56.16]:
https://redirect.github.com/taiki-e/install-action/compare/v2.56.15...v2.56.16

[2.56.15]:
https://redirect.github.com/taiki-e/install-action/compare/v2.56.14...v2.56.15

[2.56.14]:
https://redirect.github.com/taiki-e/install-action/compare/v2.56.13...v2.56.14

[2.56.13]:
https://redirect.github.com/taiki-e/install-action/compare/v2.56.12...v2.56.13

[2.56.12]:
https://redirect.github.com/taiki-e/install-action/compare/v2.56.11...v2.56.12

[2.56.11]:
https://redirect.github.com/taiki-e/install-action/compare/v2.56.10...v2.56.11

[2.56.10]:
https://redirect.github.com/taiki-e/install-action/compare/v2.56.9...v2.56.10

[2.56.9]:
https://redirect.github.com/taiki-e/install-action/compare/v2.56.8...v2.56.9

[2.56.8]:
https://redirect.github.com/taiki-e/install-action/compare/v2.56.7...v2.56.8

[2.56.7]:
https://redirect.github.com/taiki-e/install-action/compare/v2.56.6...v2.56.7

[2.56.6]:
https://redirect.github.com/taiki-e/install-action/compare/v2.56.5...v2.56.6

[2.56.5]:
https://redirect.github.com/taiki-e/install-action/compare/v2.56.4...v2.56.5

[2.56.4]:
https://redirect.github.com/taiki-e/install-action/compare/v2.56.3...v2.56.4

[2.56.3]:
https://redirect.github.com/taiki-e/install-action/compare/v2.56.2...v2.56.3

[2.56.2]:
https://redirect.github.com/taiki-e/install-action/compare/v2.56.1...v2.56.2

[2.56.1]:
https://redirect.github.com/taiki-e/install-action/compare/v2.56.0...v2.56.1

[2.56.0]:
https://redirect.github.com/taiki-e/install-action/compare/v2.55.4...v2.56.0

[2.55.4]:
https://redirect.github.com/taiki-e/install-action/compare/v2.55.3...v2.55.4

[2.55.3]:
https://redirect.github.com/taiki-e/install-action/compare/v2.55.2...v2.55.3

[2.55.2]:
https://redirect.github.com/taiki-e/install-action/compare/v2.55.1...v2.55.2

[2.55.1]:
https://redirect.github.com/taiki-e/install-action/compare/v2.55.0...v2.55.1

[2.55.0]:
https://redirect.github.com/taiki-e/install-action/compare/v2.54.3...v2.55.0

[2.54.3]:
https://redirect.github.com/taiki-e/install-action/compare/v2.54.2...v2.54.3

[2.54.2]:
https://redirect.github.com/taiki-e/install-action/compare/v2.54.1...v2.54.2

[2.54.1]:
https://redirect.github.com/taiki-e/install-action/compare/v2.54.0...v2.54.1

[2.54.0]:
https://redirect.github.com/taiki-e/install-action/compare/v2.53.2...v2.54.0

[2.53.2]:
https://redirect.github.com/taiki-e/install-action/compare/v2.53.1...v2.53.2

[2.53.1]:
https://redirect.github.com/taiki-e/install-action/compare/v2.53.0...v2.53.1

[2.53.0]:
https://redirect.github.com/taiki-e/install-action/compare/v2.52.8...v2.53.0

[2.52.8]:
https://redirect.github.com/taiki-e/install-action/compare/v2.52.7...v2.52.8

[2.52.7]:
https://redirect.github.com/taiki-e/install-action/compare/v2.52.6...v2.52.7

[2.52.6]:
https://redirect.github.com/taiki-e/install-action/compare/v2.52.5...v2.52.6

[2.52.5]:
https://redirect.github.com/taiki-e/install-action/compare/v2.52.4...v2.52.5

[2.52.4]:
https://redirect.github.com/taiki-e/install-action/compare/v2.52.3...v2.52.4

[2.52.3]:
https://redirect.github.com/taiki-e/install-action/compare/v2.52.2...v2.52.3

[2.52.2]:
https://redirect.github.com/taiki-e/install-action/compare/v2.52.1...v2.52.2

[2.52.1]:
https://redirect.github.com/taiki-e/install-action/compare/v2.52.0...v2.52.1

[2.52.0]:
https://redirect.github.com/taiki-e/install-action/compare/v2.51.3...v2.52.0

[2.51.3]:
https://redirect.github.com/taiki-e/install-action/compare/v2.51.2...v2.51.3

[2.51.2]:
https://redirect.github.com/taiki-e/install-action/compare/v2.51.1...v2.51.2

[2.51.1]:
https://redirect.github.com/taiki-e/install-action/compare/v2.51.0...v2.51.1

[2.51.0]:
https://redirect.github.com/taiki-e/install-action/compare/v2.50.10...v2.51.0

[2.50.10]:
https://redirect.github.com/taiki-e/install-action/compare/v2.50.9...v2.50.10

[2.50.9]:
https://redirect.github.com/taiki-e/install-action/compare/v2.50.8...v2.50.9

[2.50.8]:
https://redirect.github.com/taiki-e/install-action/compare/v2.50.7...v2.50.8

[2.50.7]:
https://redirect.github.com/taiki-e/install-action/compare/v2.50.6...v2.50.7

[2.50.6]:
https://redirect.github.com/taiki-e/install-action/compare/v2.50.5...v2.50.6

[2.50.5]:
https://redirect.github.com/taiki-e/install-action/compare/v2.50.4...v2.50.5

[2.50.4]:
https://redirect.github.com/taiki-e/install-action/compare/v2.50.3...v2.50.4

[2.50.3]:
https://redirect.github.com/taiki-e/install-action/compare/v2.50.2...v2.50.3

[2.50.2]:
https://redirect.github.com/taiki-e/install-action/compare/v2.50.1...v2.50.2

[2.50.1]:
https://redirect.github.com/taiki-e/install-action/compare/v2.50.0...v2.50.1

[2.50.0]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.50...v2.50.0

[2.49.50]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.49...v2.49.50

[2.49.49]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.48...v2.49.49

[2.49.48]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.47...v2.49.48

[2.49.47]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.46...v2.49.47

[2.49.46]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.45...v2.49.46

[2.49.45]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.44...v2.49.45

[2.49.44]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.43...v2.49.44

[2.49.43]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.42...v2.49.43

[2.49.42]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.41...v2.49.42

[2.49.41]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.40...v2.49.41

[2.49.40]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.39...v2.49.40

[2.49.39]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.38...v2.49.39

[2.49.38]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.37...v2.49.38

[2.49.37]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.36...v2.49.37

[2.49.36]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.35...v2.49.36

[2.49.35]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.34...v2.49.35

[2.49.34]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.33...v2.49.34

[2.49.33]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.32...v2.49.33

[2.49.32]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.31...v2.49.32

[2.49.31]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.30...v2.49.31

[2.49.30]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.29...v2.49.30

[2.49.29]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.28...v2.49.29

[2.49.28]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.27...v2.49.28

[2.49.27]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.26...v2.49.27

[2.49.26]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.25...v2.49.26

[2.49.25]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.24...v2.49.25

[2.49.24]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.23...v2.49.24

[2.49.23]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.22...v2.49.23

[2.49.22]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.21...v2.49.22

[2.49.21]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.20...v2.49.21

[2.49.20]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.19...v2.49.20

[2.49.19]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.18...v2.49.19

[2.49.18]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.17...v2.49.18

[2.49.17]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.16...v2.49.17

[2.49.16]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.15...v2.49.16

[2.49.15]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.14...v2.49.15

[2.49.14]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.13...v2.49.14

[2.49.13]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.12...v2.49.13

[2.49.12]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.11...v2.49.12

[2.49.11]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.10...v2.49.11

[2.49.10]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.9...v2.49.10

[2.49.9]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.8...v2.49.9

[2.49.8]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.7...v2.49.8

[2.49.7]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.6...v2.49.7

[2.49.6]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.5...v2.49.6

[2.49.5]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.4...v2.49.5

[2.49.4]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.3...v2.49.4

[2.49.3]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.2...v2.49.3

[2.49.2]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.1...v2.49.2

[2.49.1]:
https://redirect.github.com/taiki-e/install-action/compare/v2.49.0...v2.49.1

[2.49.0]:
https://redirect.github.com/taiki-e/install-action/compare/v2.48.22...v2.49.0

[2.48.22]:
https://redirect.github.com/taiki-e/install-action/compare/v2.48.21...v2.48.22

[2.48.21]:
https://redirect.github.com/taiki-e/install-action/compare/v2.48.20...v2.48.21

[2.48.20]:
https://redirect.github.com/taiki-e/install-action/compare/v2.48.19...v2.48.20

[2.48.19]:
https://redirect.github.com/taiki-e/install-action/compare/v2.48.18...v2.48.19

[2.48.18]:
https://redirect.github.com/taiki-e/install-action/compare/v2.48.17...v2.48.18

[2.48.17]:
https://redirect.github.com/taiki-e/install-action/compare/v2.48.16...v2.48.17

[2.48.16]:
https://redirect.github.com/taiki-e/install-action/compare/v2.48.15...v2.48.16

[2.48.15]:
https://redirect.github.com/taiki-e/install-action/compare/v2.48.14...v2.48.15

[2.48.14]:
https://redirect.github.com/taiki-e/install-action/compare/v2.48.13...v2.48.14

[2.48.13]:
https://redirect.github.com/taiki-e/install-action/compare/v2.48.12...v2.48.13

[2.48.12]:
https://redirect.github.com/taiki-e/install-action/compare/v2.48.11...v2.48.12

[2.48.11]:
https://redirect.github.com/taiki-e/install-action/compare/v2.48.10...v2.48.11

[2.48.10]:
https://redirect.github.com/taiki-e/install-action/compare/v2.48.9...v2.48.10

[2.48.9]:
https://redirect.github.com/taiki-e/install-action/compare/v2.48.8...v2.48.9

[2.48.8]:
https://redirect.github.com/taiki-e/install-action/compare/v2.48.7...v2.48.8

[2.48.7]:
https://redirect.github.com/taiki-e/install-action/compare/v2.48.6...v2.48.7

[2.48.6]:
https://redirect.github.com/taiki-e/install-action/compare/v2.48.5...v2.48.6

[2.48.5]:
https://redirect.github.com/taiki-e/install-action/compare/v2.48.4...v2.48.5

[2.48.4]:
https://redirect.github.com/taiki-e/install-action/compare/v2.48.3...v2.48.4

[2.48.3]:
https://redirect.github.com/taiki-e/install-action/compare/v2.48.2...v2.48.3

[2.48.2]:
https://redirect.github.com/taiki-e/install-action/compare/v2.48.1...v2.48.2

[2.48.1]:
https://redirect.github.com/taiki-e/install-action/compare/v2.48.0...v2.48.1

[2.48.0]:
https://redirect.github.com/taiki-e/install-action/compare/v2.47.32...v2.48.0

[2.47.32]:
https://redirect.github.com/taiki-e/install-action/compare/v2.47.31...v2.47.32

[2.47.31]:
https://redirect.github.com/taiki-e/install-action/compare/v2.47.30...v2.47.31

[2.47.30]:
https://redirect.github.com/taiki-e/install-action/compare/v2.47.29...v2.47.30

[2.47.29]:
https://redirect.github.com/taiki-e/install-action/compare/v2.47.28...v2.47.29

[2.47.28]:
https://redirect.github.com/taiki-e/install-action/compare/v2.47.27...v2.47.28

[2.47.27]:
https://redirect.github.com/taiki-e/install-action/compare/v2.47.26...v2.47.27

[2.47.26]:
https://redirect.github.com/taiki-e/install-action/compare/v2.47.25...v2.47.26

[2.47.25]:
https://redirect.github.com/taiki-e/install-action/compare/v2.47.24...v2.47.25

[2.47.24]:
https://redirect.github.com/taiki-e/install-action/compare/v2.47.23...v2.47.24

[2.47.23]:
https://redirect.github.com/taiki-e/install-action/compare/v2.47.22...v2.47.23

[2.47.22]:
https://redirect.github.com/taiki-e/install-action/compare/v2.47.21...v2.47.22

[2.47.21]:
https://redirect.github.com/taiki-e/install-action/compare/v2.47.20...v2.47.21

[2.47.20]:
https://redirect.github.com/taiki-e/install-action/compare/v2.47.19...v2.47.20

[2.47.19]:
https://redirect.github.com/taiki-e/install-action/compare/v2.47.18...v2.47.19

[2.47.18]:
https://redirect.github.com/taiki-e/install-action/compare/v2.47.17...v2.47.18

[2.47.17]:
https://redirect.github.com/taiki-e/install-action/compare/v2.47.16...v2.47.17

[2.47.16]:
https://redirect.github.com/taiki-e/install-action/compare/v2.47.15...v2.47.16

[2.47.15]:
https://redirect.github.com/taiki-e/install-action/compare/v2.47.14...v2.47.15

[2.47.14]:
https://redirect.github.com/taiki-e/install-action/compare/v2.47.13...v2.47.14

[2.47.13]:
https://redirect.github.com/taiki-e/install-action/compare/v2.47.12...v2.47.13

[2.47.12]:
https://redirect.github.com/taiki-e/install-action/compare/v2.47.11...v2.47.12

[2.47.11]:
https://redirect.github.com/taiki-e/install-action/compare/v2.47.10...v2.47.11

[2.47.10]:
https://redirect.github.com/taiki-e/install-action/compare/v2.47.9...v2.47.10

[2.47.9]:
https://redirect.github.com/taiki-e/install-action/compare/v2.47.8...v2.47.9

[2.47.8]:
https://redirect.github.com/taiki-e/install-action/compare/v2.47.7...v2.47.8

[2.47.7]:
https://redirect.github.com/taiki-e/install-action/compare/v2.47.6...v2.47.7

[2.47.6]:
https://redirect.github.com/taiki-e/install-action/compare/v2.47.5...v2.47.6

[2.47.5]:
https://redirect.github.com/taiki-e/install-action/compare/v2.47.4...v2.47.5

[2.47.4]:
https://redirect.github.com/taiki-e/install-action/compare/v2.47.3...v2.47.4

[2.47.3]:
https://redirect.github.com/taiki-e/install-action/compare/v2.47.2...v2.47.3

[2.47.2]:
https://redirect.github.com/taiki-e/install-action/compare/v2.47.1...v2.47.2

[2.47.1]:
https://redirect.github.com/taiki-e/install-action/compare/v2.47.0...v2.47.1

[2.47.0]:
https://redirect.github.com/taiki-e/install-action/compare/v2.46.20...v2.47.0

[2.46.20]:
https://redirect.github.com/taiki-e/install-action/compare/v2.46.19...v2.46.20

[2.46.19]:
https://redirect.github.com/taiki-e/install-action/compare/v2.46.18...v2.46.19

[2.46.18]:
https://redirect.github.com/taiki-e/install-action/compare/v2.46.17...v2.46.18

[2.46.17]:
https://redirect.github.com/taiki-e/install-action/compare/v2.46.16...v2.46.17

[2.46.16]:
https://redirect.github.com/taiki-e/install-action/compare/v2.46.15...v2.46.16

[2.46.15]:
https://redirect.github.com/taiki-e/install-action/compare/v2.46.14...v2.46.15

[2.46.14]:
https://redirect.github.com/taiki-e/install-action/compare/v2.46.13...v2.46.14

[2.46.13]:
https://redirect.github.com/taiki-e/install-action/compare/v2.46.12...v2.46.13

[2.46.12]:
https://redirect.github.com/taiki-e/install-action/compare/v2.46.11...v2.46.12

[2.46.11]:
https://redirect.github.com/taiki-e/install-action/compare/v2.46.10...v2.46.11

[2.46.10]:
https://redirect.github.com/taiki-e/install-action/compare/v2.46.9...v2.46.10

[2.46.9]:
https://redirect.github.com/taiki-e/install-action/compare/v2.46.8...v2.46.9

[2.46.8]:
https://redirect.github.com/taiki-e/install-action/compare/v2.46.7...v2.46.8

[2.46.7]:
https://redirect.github.com/taiki-e/install-action/compare/v2.46.6...v2.46.7

[2.46.6]:
https://redirect.github.com/taiki-e/install-action/compare/v2.46.5...v2.46.6

[2.46.5]:
https://redirect.github.com/taiki-e/install-action/compare/v2.46.4...v2.46.5

[2.46.4]:
https://redirect.github.com/taiki-e/install-action/compare/v2.46.3...v2.46.4

[2.46.3]:
https://redirect.github.com/taiki-e/install-action/compare/v2.46.2...v2.46.3

[2.46.2]:
https://redirect.github.com/taiki-e/install-action/compare/v2.46.1...v2.46.2

[2.46.1]:
https://redirect.github.com/taiki-e/install-action/compare/v2.46.0...v2.46.1

[2.46.0]:
https://redirect.github.com/taiki-e/install-action/compare/v2.45.15...v2.46.0

[2.45.15]:
https://redirect.github.com/taiki-e/install-action/compare/v2.45.14...v2.45.15

[2.45.14]:
https://redirect.github.com/taiki-e/install-action/compare/v2.45.13...v2.45.14

[2.45.13]:
https://redirect.github.com/taiki-e/install-action/compare/v2.45.12...v2.45.13

[2.45.12]:
https://redirect.github.com/taiki-e/install-action/compare/v2.45.11...v2.45.12

[2.45.11]:
https://redirect.github.com/taiki-e/install-action/compare/v2.45.10...v2.45.11

[2.45.10]:
https://redirect.github.com/taiki-e/install-action/compare/v2.45.9...v2.45.10

[2.45.9]:
https://redirect.github.com/taiki-e/install-action/compare/v2.45.8...v2.45.9

[2.45.8]:
https://redirect.github.com/taiki-e/install-action/compare/v2.45.7...v2.45.8

[2.45.7]:
https://redirect.github.com/taiki-e/install-action/compare/v2.45.6...v2.45.7

[2.45.6]:
https://redirect.github.com/taiki-e/install-action/compare/v2.45.5...v2.45.6

[2.45.5]:
https://redirect.github.com/taiki-e/install-action/compare/v2.45.4...v2.45.5

[2.45.4]:
https://redirect.github.com/taiki-e/install-action/compare/v2.45.3...v2.45.4

[2.45.3]:
https://redirect.github.com/taiki-e/install-action/compare/v2.45.2...v2.45.3

[2.45.2]:
https://redirect.github.com/taiki-e/install-action/compare/v2.45.1...v2.45.2

[2.45.1]:
https://redirect.github.com/taiki-e/install-action/compare/v2.45.0...v2.45.1

[2.45.0]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.72...v2.45.0

[2.44.72]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.71...v2.44.72

[2.44.71]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.70...v2.44.71

[2.44.70]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.69...v2.44.70

[2.44.69]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.68...v2.44.69

[2.44.68]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.67...v2.44.68

[2.44.67]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.66...v2.44.67

[2.44.66]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.65...v2.44.66

[2.44.65]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.64...v2.44.65

[2.44.64]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.63...v2.44.64

[2.44.63]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.62...v2.44.63

[2.44.62]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.61...v2.44.62

[2.44.61]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.60...v2.44.61

[2.44.60]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.59...v2.44.60

[2.44.59]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.58...v2.44.59

[2.44.58]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.57...v2.44.58

[2.44.57]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.56...v2.44.57

[2.44.56]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.55...v2.44.56

[2.44.55]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.54...v2.44.55

[2.44.54]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.53...v2.44.54

[2.44.53]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.52...v2.44.53

[2.44.52]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.51...v2.44.52

[2.44.51]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.50...v2.44.51

[2.44.50]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.49...v2.44.50

[2.44.49]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.48...v2.44.49

[2.44.48]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.47...v2.44.48

[2.44.47]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.46...v2.44.47

[2.44.46]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.45...v2.44.46

[2.44.45]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.44...v2.44.45

[2.44.44]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.43...v2.44.44

[2.44.43]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.42...v2.44.43

[2.44.42]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.41...v2.44.42

[2.44.41]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.40...v2.44.41

[2.44.40]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.39...v2.44.40

[2.44.39]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.38...v2.44.39

[2.44.38]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.37...v2.44.38

[2.44.37]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.36...v2.44.37

[2.44.36]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.35...v2.44.36

[2.44.35]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.34...v2.44.35

[2.44.34]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.33...v2.44.34

[2.44.33]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.32...v2.44.33

[2.44.32]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.31...v2.44.32

[2.44.31]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.30...v2.44.31

[2.44.30]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.29...v2.44.30

[2.44.29]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.28...v2.44.29

[2.44.28]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.27...v2.44.28

[2.44.27]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.26...v2.44.27

[2.44.26]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.25...v2.44.26

[2.44.25]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.24...v2.44.25

[2.44.24]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.23...v2.44.24

[2.44.23]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.22...v2.44.23

[2.44.22]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.21...v2.44.22

[2.44.21]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.20...v2.44.21

[2.44.20]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.19...v2.44.20

[2.44.19]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.18...v2.44.19

[2.44.18]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.17...v2.44.18

[2.44.17]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.16...v2.44.17

[2.44.16]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.15...v2.44.16

[2.44.15]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.14...v2.44.15

[2.44.14]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.13...v2.44.14

[2.44.13]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.12...v2.44.13

[2.44.12]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.11...v2.44.12

[2.44.11]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.10...v2.44.11

[2.44.10]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.9...v2.44.10

[2.44.9]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.8...v2.44.9

[2.44.8]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.7...v2.44.8

[2.44.7]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.6...v2.44.7

[2.44.6]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.5...v2.44.6

[2.44.5]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.4...v2.44.5

[2.44.4]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.3...v2.44.4

[2.44.3]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.2...v2.44.3

[2.44.2]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.1...v2.44.2

[2.44.1]:
https://redirect.github.com/taiki-e/install-action/compare/v2.44.0...v2.44.1

[2.44.0]:
https://redirect.github.com/taiki-e/install-action/compare/v2.43.7...v2.44.0

[2.43.7]:
https://redirect.github.com/taiki-e/install-action/compare/v2.43.6...v2.43.7

[2.43.6]:
https://redirect.github.com/taiki-e/install-action/compare/v2.43.5...v2.43.6

[2.43.5]:
https://redirect.github.com/taiki-e/install-action/compare/v2.43.4...v2.43.5

[2.43.4]:
https://redirect.github.com/taiki-e/install-action/compare/v2.43.3...v2.43.4

[2.43.3]:
https://redirect.github.com/taiki-e/install-action/compare/v2.43.2...v2.43.3

[2.43.2]:
https://redirect.github.com/taiki-e/install-action/compare/v2.43.1...v2.43.2

[2.43.1]:
https://redirect.github.com/taiki-e/install-action/compare/v2.43.0...v2.43.1

[2.43.0]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.42...v2.43.0

[2.42.42]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.41...v2.42.42

[2.42.41]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.40...v2.42.41

[2.42.40]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.39...v2.42.40

[2.42.39]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.38...v2.42.39

[2.42.38]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.37...v2.42.38

[2.42.37]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.36...v2.42.37

[2.42.36]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.35...v2.42.36

[2.42.35]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.34...v2.42.35

[2.42.34]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.33...v2.42.34

[2.42.33]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.32...v2.42.33

[2.42.32]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.31...v2.42.32

[2.42.31]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.30...v2.42.31

[2.42.30]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.29...v2.42.30

[2.42.29]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.28...v2.42.29

[2.42.28]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.27...v2.42.28

[2.42.27]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.26...v2.42.27

[2.42.26]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.25...v2.42.26

[2.42.25]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.24...v2.42.25

[2.42.24]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.23...v2.42.24

[2.42.23]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.22...v2.42.23

[2.42.22]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.21...v2.42.22

[2.42.21]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.20...v2.42.21

[2.42.20]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.19...v2.42.20

[2.42.19]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.18...v2.42.19

[2.42.18]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.17...v2.42.18

[2.42.17]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.16...v2.42.17

[2.42.16]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.15...v2.42.16

[2.42.15]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.14...v2.42.15

[2.42.14]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.13...v2.42.14

[2.42.13]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.12...v2.42.13

[2.42.12]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.11...v2.42.12

[2.42.11]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.10...v2.42.11

[2.42.10]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.9...v2.42.10

[2.42.9]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.8...v2.42.9

[2.42.8]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.7...v2.42.8

[2.42.7]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.6...v2.42.7

[2.42.6]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.5...v2.42.6

[2.42.5]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.4...v2.42.5

[2.42.4]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.3...v2.42.4

[2.42.3]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.2...v2.42.3

[2.42.2]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.1...v2.42.2

[2.42.1]:
https://redirect.github.com/taiki-e/install-action/compare/v2.42.0...v2.42.1

[2.42.0]:
https://redirect.github.com/taiki-e/install-action/compare/v2.41.18...v2.42.0

[2.41.18]:
https://redirect.github.com/taiki-e/install-action/compare/v2.41.17...v2.41.18

[2.41.17]:
https://redirect.github.com/taiki-e/install-action/compare/v2.41.16...v2.41.17

[2.41.16]:
https://redirect.github.com/taiki-e/install-action/compare/v2.41.15...v2.41.16

[2.41.15]:
https://redirect.github.com/taiki-e/install-action/compare/v2.41.14...v2.41.15

[2.41.14]:
https://redirect.github.com/taiki-e/install-action/compare/v2.41.13...v2.41.14

[2.41.13]:
https://redirect.github.com/taiki-e/install-action/compare/v2.41.12...v2.41.13

[2.41.12]:
https://redirect.github.com/taiki-e/install-action/compare/v2.41.11...v2.41.12

[2.41.11]:
https://redirect.github.com/taiki-e/install-action/compare/v2.41.10...v2.41.11

[2.41.10]:
https://redirect.github.com/taiki-e/install-action/compare/v2.41.9...v2.41.10

[2.41.9]:
https://redirect.github.com/taiki-e/install-action/compare/v2.41.8...v2.41.9

[2.41.8]:
https://redirect.github.com/taiki-e/install-action/compare/v2.41.7...v2.41.8

[2.41.7]:
https://redirect.github.com/taiki-e/install-action/compare/v2.41.6...v2.41.7

[2.41.6]:
https://redirect.github.com/taiki-e/install-action/compare/v2.41.5...v2.41.6

[2.41.5]:
https://redirect.github.com/taiki-e/install-action/compare/v2.41.4...v2.41.5

[2.41.4]:
https://redirect.github.com/taiki-e/install-action/compare/v2.41.3...v2.41.4

[2.41.3]:
https://redirect.github.com/taiki-e/install-action/compare/v2.41.2...v2.41.3

[2.41.2]:
https://redirect.github.com/taiki-e/install-action/compare/v2.41.1...v2.41.2

[2.41.1]:
https://redirect.github.com/taiki-e/install-action/compare/v2.41.0...v2.41.1

[2.41.0]:
https://redirect.github.com/taiki-e/install-action/compare/v2.40.2...v2.41.0

[2.40.2]:
https://redirect.github.com/taiki-e/install-action/compare/v2.40.1...v2.40.2

[2.40.1]:
https://redirect.github.com/taiki-e/install-action/compare/v2.40.0...v2.40.1

[2.40.0]:
https://redirect.github.com/taiki-e/install-action/compare/v2.39.2...v2.40.0

[2.39.2]:
https://redirect.github.com/taiki-e/install-action/compare/v2.39.1...v2.39.2

[2.39.1]:
https://redirect.github.com/taiki-e/install-action/compare/v2.39.0...v2.39.1

[2.39.0]:
https://redirect.github.com/taiki-e/install-action/compare/v2.38.7...v2.39.0

[2.38.7]:
https://redirect.github.com/taiki-e/install-action/compare/v2.38.6...v2.38.7

[2.38.6]:
https://redirect.github.com/taiki-e/install-action/compare/v2.38.5...v2.38.6

[2.38.5]:
https://redirect.github.com/taiki-e/install-action/compare/v2.38.4...v2.38.5

[2.38.4]:
https://redirect.github.com/taiki-e/install-action/compare/v2.38.3...v2.38.4

[2.38.3]:
https://redirect.github.com/taiki-e/install-action/compare/v2.38.2...v2.38.3

[2.38.2]:
https://redirect.github.com/taiki-e/install-action/compare/v2.38.1...v2.38.2

[2.38.1]:
https://redirect.github.com/taiki-e/install-action/compare/v2.38.0...v2.38.1

[2.38.0]:
https://redirect.github.com/taiki-e/install-action/compare/v2.37.0...v2.38.0

[2.37.0]:
https://redirect.github.com/taiki-e/install-action/compare/v2.36.0...v2.37.0

[2.36.0]:
https://redirect.github.com/taiki-e/install-action/compare/v2.35.0...v2.36.0

[2.35.0]:
https://redirect.github.com/taiki-e/install-action/compare/v2.34.3...v2.35.0

[2.34.3]:
https://redirect.github.com/taiki-e/install-action/compare/v2.34.2...v2.34.3

[2.34.2]:
https://redirect.github.com/taiki-e/install-action/compare/v2.34.1...v2.34.2

[2.34.1]:
https://redirect.github.com/taiki-e/install-action/compare/v2.34.0...v2.34.1

[2.34.0]:
https://redirect.github.com/taiki-e/install-action/compare/v2.33.36...v2.34.0

[2.33.36]:
https://redirect.github.com/taiki-e/install-action/compare/v2.33.35...v2.33.36

[2.33.35]:
https://redirect.github.com/taiki-e/install-action/compare/v2.33.34...v2.33.35

[2.33.34]:
https://redirect.github.com/taiki-e/install-action/compare/v2.33.33...v2.33.34

[2.33.33]:
https://redirect.github.com/taiki-e/install-action/compare/v2.33.32...v2.33.33

[2.33.32]:
https://redirect.github.com/taiki-e/install-action/compare/v2.33.31...v2.33.32

[2.33.31]:
https://redirect.github.com/taiki-e/install-action/compare/v2.33.30...v2.33.31

[2.33.30]:
https://redirect.github.com/taiki-e/install-action/compare/v2.33.29...v2.33.30

[2.33.29]:
https://redirect.github.com/taiki-e/install-action/compare/v2.33.28...v2.33.29

[2.33.28]:
https://redirect.github.com/taiki-e/install-action/compare/v2.33.27...v2.33.28

[2.33.27]:
https://redirect.github.com/taiki-e/install-action/compare/v2.33.26...v2.33.27

[2.33.26]:
https://redirect.github.com/taiki-e/install-action/compare/v2.33.25...v2.33.26

[2.33.25]:
https://redirect.github.com/taiki-e/install-action/compare/v2.33.24...v2.33.25

[2.33.24]:
https://redirect.github.com/taiki-e/install-action/compare/v2.33.23...v2.33.24

[2.33.23]:
https://redirect.github.com/taiki-e/install-action/compare/v2.33.22...v2.33.23

[2.33.22]:
https://redirect.github.com/taiki-e/install-action/compare/v2.33.21...v2.33.22

[2.33.21]:
https://redirect.github.com/taiki-e/install-action/compare/v2.33.20...v2.33.21

[2.33.20]:
https://redirect.github.com/taiki-e/install-action/compare/v2.33.19...v2.33.20

[2.33.19]:
https://redirect.github.com/taiki-e/install-action/compare/v2.33.18...v2.33.19

[2.33.18]:
https://redirect.github.com/taiki-e/install-action/compare/v2.33.17...v2.33.18

[2.33.17]:
https://redirect.github.com/taiki-e/install-action/compare/v2.33.16...v2.33.17

[2.33.16]:
https://redirect.github.com/taiki-e/install-action/compare/v2.33.15...v2.33.16

[2.33.15]:
https://redirect.github.com/taiki-e/install-action/compare/v2.33.14...v2.33.15

[2.33.14]:
https://redirect.github.com/taiki-e/install-action/compare/v2.33.13...v2.33.14

[2.33.13]:
https://redirect.github.com/taiki-e/install-action/compare/v2.33.12...v2.33.13

[2.33.12]:
https://redirect.github.com/taiki-e/install-action/compare/v2.33.11...v2.33.12

[2.33.11]:
https://redirect.github.com/taiki-e/install-action/compare/v2.33.10...v2.33.11

[2.33.10]:
https://redirect.github.com/taiki-e/install-action/compare/v2.33.9...v2.33.10

[2.33.9]:
https://redirect.github.com/taiki-e/install-action/compare/v2.33.8...v2.33.9

[2.33.8]:
https://redirect.github.com/taiki-e/install-action/compare/v2.33.7...v2.33.8

[2.33.7]:
https://redirect.github.com/taiki-e/install-action/compare/v2.33.6...v2.33.7

[2.33.6]:
https://redirect.github.com/taiki-e/install-action/compare/v2.33.5...v2.33.6

[2.33.5]:
https://redirect.github.com/taiki-e/install-action/compare/v2.33.4...v2.33.5

[2.33.4]:
https://redirect.github.com/taiki-e/install-action/compare/v2.33.3...v2.33.4

[2.33.3]:
https://redirect.github.com/taiki-e/install-action/compare/v2.33.2...v2.33.3

[2.33.2]:
https://redirect.github.com/taiki-e/install-action/compare/v2.33.1...v2.33.2

[2.33.1]:
https://redirect.github.com/taiki-e/install-action/compare/v2.33.0...v2.33.1

[2.33.0]:
https://redirect.github.com/taiki-e/install-action/compare/v2.32.20...v2.33.0

[2.32.20]:
https://redirect.github.com/taiki-e/install-action/compare/v2.32.19...v2.32.20

[2.32.19]:
https://redirect.github.com/taiki-e/install-action/compare/v2.32.18...v2.32.19

[2.32.18]:
https://redirect.github.com/taiki-e/install-action/compare/v2.32.17...v2.32.18

[2.32.17]:
https://redirect.github.com/taiki-e/install-action/compare/v2.32.16...v2.32.17

[2.32.16]:
https://redirect.github.com/taiki-e/install-action/compare/v2.32.15...v2.32.16

[2.32.15]:
https://redirect.github.com/taiki-e/install-action/compare/v2.32.14...v2.32.15

[2.32.14]:
https://redirect.github.com/taiki-e/install-action/compare/v2.32.13...v2.32.14

[2.32.13]:
https://redirect.github.com/taiki-e/install-action/compare/v2.32.12...v2.32.13

[2.32.12]:
https://redirect.github.com/taiki-e/install-action/compare/v2.32.11...v2.32.12

[2.32.11]:
https://redirect.github.com/taiki-e/install-action/compare/v2.32.10...v2.32.11

[2.32.10]:
https://redirect.github.com/taiki-e/install-action/compare/v2.32.9...v2.32.10

[2.32.9]:
https://redirect.github.com/taiki-e/install-action/compare/v2.32.8...v2.32.9

[2.32.8]:
https://redirect.github.com/taiki-e/install-action/compare/v2.32.7...v2.32.8

[2.32.7]:
https://redirect.github.com/taiki-e/install-action/compare/v2.32.6...v2.32.7

[2.32.6]:
https://redirect.github.com/taiki-e/install-action/compare/v2.32.5...v2.32.6

[2.32.5]:
https://redirect.github.com/taiki-e/install-action/compare/v2.32.4...v2.32.5

[2.32.4]:
https://redirect.github.com/taiki-e/install-action/compare/v2.32.3...v2.32.4

[2.32.3]:
https://redirect.github.com/taiki-e/install-action/compare/v2.32.2...v2.32.3

[2.32.2]:
https://redirect.github.com/taiki-e/install-action/compare/v2.32.1...v2.32.2

[2.32.1]:
https://redirect.github.com/taiki-e/install-action/compare/v2.32.0...v2.32.1

[2.32.0]:
https://redirect.github.com/taiki-e/install-action/compare/v2.31.3...v2.32.0

[2.31.3]:
https://redirect.github.com/taiki-e/install-action/compare/v2.31.2...v2.31.3

[2.31.2]:
https://redirect.github.com/taiki-e/install-action/compare/v2.31.1...v2.31.2

[2.31.1]:
https://redirect.github.com/taiki-e/install-action/compare/v2.31.0...v2.31.1

[2.31.0]:
https://redirect.github.com/taiki-e/install-action/compare/v2.30.0...v2.31.0

[2.30.0]:
https://redirect.github.com/taiki-e/install-action/compare/v2.29.8...v2.30.0

[2.29.8]:
https://redirect.github.com/taiki-e/install-action/compare/v2.29.7...v2.29.8

[2.29.7]:
https://redirect.github.com/taiki-e/install-action/compare/v2.29.6...v2.29.7

[2.29.6]:
https://redirect.github.com/taiki-e/install-action/compare/v2.29.5...v2.29.6

[2.29.5]:
https://redirect.github.com/taiki-e/install-action/compare/v2.29.4...v2.29.5

[2.29.4]:
https://redirect.github.com/taiki-e/install-action/compare/v2.29.3...v2.29.4

[2.29.3]:
https://redirect.github.com/taiki-e/install-action/compare/v2.29.2...v2.29.3

[2.29.2]:
https://redirect.github.com/taiki-e/install-action/compare/v2.29.1...v2.29.2

[2.29.1]:
https://redirect.github.com/taiki-e/install-action/compare/v2.29.0...v2.29.1

[2.29.0]:
https://redirect.github.com/taiki-e/install-action/compare/v2.28.16...v2.29.0

[2.28.16]:
https://redirect.github.com/taiki-e/install-action/compare/v2.28.15...v2.28.16

[2.28.15]:
https://redirect.github.com/taiki-e/install-action/compare/v2.28.14...v2.28.15

[2.28.14]:
https://redirect.github.com/taiki-e/install-action/compare/v2.28.13...v2.28.14

[2.28.13]:
https://redirect.github.com/taiki-e/install-action/compare/v2.28.12...v2.28.13

[2.28.12]:
https://redirect.github.com/taiki-e/install-action/compare/v2.28.11...v2.28.12

[2.28.11]:
https://redirect.github.com/taiki-e/install-action/compare/v2.28.10...v2.28.11

[2.28.10]:
https://redirect.github.com/taiki-e/install-action/compare/v2.28.9...v2.28.10

[2.28.9]:
https://redirect.github.com/taiki-e/install-action/compare/v2.28.8...v2.28.9

[2.28.8]:
https://redirect.github.com/taiki-e/install-action/compare/v2.28.7...v2.28.8

[2.28.7]:
https://redirect.github.com/taiki-e/install-action/compare/v2.28.6...v2.28.7

[2.28.6]:
https://redirect.github.com/taiki-e/install-action/compare/v2.28.5...v2.28.6

[2.28.5]:
https://redirect.github.com/taiki-e/install-action/compare/v2.28.4...v2.28.5

[2.28.4]:
https://redirect.github.com/taiki-e/install-action/compare/v2.28.3...v2.28.4

[2.28.3]:
https://redirect.github.com/taiki-e/install-action/compare/v2.28.2...v2.28.3

[2.28.2]:
https://redirect.github.com/taiki-e/install-action/compare/v2.28.1...v2.28.2

[2.28.1]:
https://redirect.github.com/taiki-e/install-action/compare/v2.28.0...v2.28.1

[2.28.0]:
https://redirect.github.com/taiki-e/install-action/compare/v2.27.15...v2.28.0

[2.27.15]:
https://redirect.github.com/taiki-e/install-action/compare/v2.27.14...v2.27.15

[2.27.14]:
https://redirect.github.com/taiki-e/install-action/compare/v2.27.13...v2.27.14

[2.27.13]:
https://redirect.github.com/taiki-e/install-action/compare/v2.27.12...v2.27.13

[2.27.12]:
https://redirect.github.com/taiki-e/install-action/compare/v2.27.11...v2.27.12

[2.27.11]:
https://redirect.github.com/taiki-e/install-action/compare/v2.27.10...v2.27.11

[2.27.10]:
https://redirect.github.com/taiki-e/install-action/compare/v2.27.9...v2.27.10

[2.27.9]:
https://redirect.github.com/taiki-e/install-action/compare/v2.27.8...v2.27.9

[2.27.8]:
https://redirect.github.com/taiki-e/install-action/compare/v2.27.7...v2.27.8

[2.27.7]:
https://redirect.github.com/taiki-e/install-action/compare/v2.27.6...v2.27.7

[2.27.6]:
https://redirect.github.com/taiki-e/install-action/compare/v2.27.5...v2.27.6

[2.27.5]:
https://redirect.github.com/taiki-e/install-action/compare/v2.27.4...v2.27.5

[2.27.4]:
https://redirect.github.com/taiki-e/install-action/compare/v2.27.3...v2.27.4

[2.27.3]:
https://redirect.github.com/taiki-e/install-action/compare/v2.27.2...v2.27.3

[2.27.2]:
https://redirect.github.com/taiki-e/install-action/compare/v2.27.1...v2.27.2

[2.27.1]:
https://redirect.github.com/taiki-e/install-action/compare/v2.27.0...v2.27.1

[2.27.0]:
https://redirect.github.com/taiki-e/install-action/compare/v2.26.20...v2.27.0

[2.26.20]:
https://redirect.github.com/taiki-e/install-action/compare/v2.26.19...v2.26.20

[2.26.19]:
https://redirect.github.com/taiki-e/install-action/compare/v2.26.18...v2.26.19

[2.26.18]:
https://redirect.github.com/taiki-e/install-action/compare/v2.26.17...v2.26.18

[2.26.17]:
https://redirect.github.com/taiki-e/install-action/compare/v2.26.16...v2.26.17

[2.26.16]:
https://redirect.github.com/taiki-e/install-action/compare/v2.26.15...v2.26.16

[2.26.15]:
https://redirect.github.com/taiki-e/install-action/compare/v2.26.14...v2.26.15

[2.26.14]:
https://redirect.github.com/taiki-e/install-action/compare/v2.26.13...v2.26.14

[2.26.13]:
https://redirect.github.com/taiki-e/install-action/compare/v2.26.12...v2.26.13

[2.26.12]:
https://redirect.github.com/taiki-e/install-action/compare/v2.26.11...v2.26.12

[2.26.11]:
https://redirect.github.com/taiki-e/install-action/compare/v2.26.10...v2.26.11

[2.26.10]:
https://redirect.github.com/taiki-e/install-action/compare/v2.26.9...v2.26.10

[2.26.9]:
https://redirect.github.com/taiki-e/install-action/compare/v2.26.8...v2.26.9

[2.26.8]:
https://redirect.github.com/taiki-e/install-action/compare/v2.26.7...v2.26.8

[2.26.7]:
https://redirect.github.com/taiki-e/install-action/compare/v2.26.6...v2.26.7

[2.26.6]:
https://redirect.github.com/taiki-e/install-action/compare/v2.26.5...v2.26.6

[2.26.5]:
https://redirect.github.com/taiki-e/install-action/compare/v2.26.4...v2.26.5

[2.26.4]:
https://redirect.github.com/taiki-e/install-action/compare/v2.26.3...v2.26.4

[2.26.3]:
https://redirect.github.com/taiki-e/install-action/compare/v2.26.2...v2.26.3

[2.26.2]:
https://redirect.github.com/taiki-e/install-action/compare/v2.26.1...v2.26.2

[2.26.1]:
https://redirect.github.com/taiki-e/install-action/compare/v2.26.0...v2.26.1

[2.26.0]:
https://redirect.github.com/taiki-e/install-action/compare/v2.25.11...v2.26.0

[2.25.11]:
https://redirect.github.com/taiki-e/install-action/compare/v2.25.10...v2.25.11

[2.25.10]:
https://redirect.github.com/taiki-e/install-action/compare/v2.25.9...v2.25.10

[2.25.9]:
https://redirect.github.com/taiki-e/install-action/compare/v2.25.8...v2.25.9

[2.25.8]:
https://redirect.github.com/taiki-e/install-action/compare/v2.25.7...v2.25.8

[2.25.7]:
https://redirect.github.com/taiki-e/install-action/compare/v2.25.6...v2.25.7

[2.25.6]:
https://redirect.github.com/taiki-e/install-action/compare/v2.25.5...v2.25.6

[2.25.5]:
https://redirect.github.com/taiki-e/install-action/compare/v2.25.4...v2.25.5

[2.25.4]:
https://redirect.github.com/taiki-e/install-action/compare/v2.25.3...v2.25.4

[2.25.3]:
https://redirect.github.com/taiki-e/install-action/compare/v2.25.2...v2.25.3

[2.25.2]:
https://redirect.github.com/taiki-e/install-action/compare/v2.25.1...v2.25.2

[2.25.1]:
https://redirect.github.com/taiki-e/install-action/compare/v2.25.0...v2.25.1

[2.25.0]:
https://redirect.github.com/taiki-e/install-action/compare/v2.24.4...v2.25.0

[2.24.4]:
https://redirect.github.com/taiki-e/install-action/compare/v2.24.3...v2.24.4

[2.24.3]:
https://redirect.github.com/taiki-e/install-action/compare/v2.24.2...v2.24.3

[2.24.2]:
https://redirect.github.com/taiki-e/install-action/compare/v2.24.1...v2.24.2

[2.24.1]:
https://redirect.github.com/taiki-e/install-action/compare/v2.24.0...v2.24.1

[2.24.0]:
https://redirect.github.com/taiki-e/install-action/compare/v2.23.9...v2.24.0

[2.23.9]:
https://redirect.github.com/taiki-e/install-action/compare/v2.23.8...v2.23.9

[2.23.8]:
https://redirect.github.com/taiki-e/install-action/compare/v2.23.7...v2.23.8

[2.23.7]:
https://redirect.github.com/taiki-e/install-action/compare/v2.23.6...v2.23.7

[2.23.6]:
https://redirect.github.com/taiki-e/install-action/compare/v2.23.5...v2.23.6

[2.23.5]:
https://redirect.github.com/taiki-e/install-action/compare/v2.23.4...v2.23.5

[2.23.4]:
https://redirect.github.com/taiki-e/install-action/compare/v2.23.3...v2.23.4

[2.23.3]:
https://redirect.github.com/taiki-e/install-action/compare/v2.23.2...v2.23.3

[2.23.2]: https://redirect.github.com/taiki-e/in

</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:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNTkuNCIsInVwZGF0ZWRJblZlciI6IjQxLjE1OS40IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJpbnRlcm5hbCJdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-10 09:15:35 +01:00
renovate[bot]
2a1d412f72 Update Rust crate syn to v2.0.110 (#21357)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [syn](https://redirect.github.com/dtolnay/syn) |
workspace.dependencies | patch | `2.0.108` -> `2.0.110` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>dtolnay/syn (syn)</summary>

###
[`v2.0.110`](https://redirect.github.com/dtolnay/syn/releases/tag/2.0.110)

[Compare
Source](https://redirect.github.com/dtolnay/syn/compare/2.0.109...2.0.110)

- Tweaks to improve build speed
([#&#8203;1939](https://redirect.github.com/dtolnay/syn/issues/1939),
thanks [@&#8203;dishmaker](https://redirect.github.com/dishmaker))
- Make `syn::ext::IdentExt::unraw` available without "parsing" feature
([#&#8203;1940](https://redirect.github.com/dtolnay/syn/issues/1940))
- Support parsing `syn::Meta` followed by `=>`
([#&#8203;1944](https://redirect.github.com/dtolnay/syn/issues/1944))

###
[`v2.0.109`](https://redirect.github.com/dtolnay/syn/releases/tag/2.0.109)

[Compare
Source](https://redirect.github.com/dtolnay/syn/compare/2.0.108...2.0.109)

- Tweaks to improve build speed
([#&#8203;1927](https://redirect.github.com/dtolnay/syn/issues/1927),
[#&#8203;1928](https://redirect.github.com/dtolnay/syn/issues/1928),
[#&#8203;1930](https://redirect.github.com/dtolnay/syn/issues/1930),
[#&#8203;1932](https://redirect.github.com/dtolnay/syn/issues/1932),
[#&#8203;1934](https://redirect.github.com/dtolnay/syn/issues/1934),
thanks [@&#8203;dishmaker](https://redirect.github.com/dishmaker))

</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:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNTkuNCIsInVwZGF0ZWRJblZlciI6IjQxLjE1OS40IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJpbnRlcm5hbCJdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-10 09:14:48 +01:00
renovate[bot]
dd751e8d07 Update dependency ruff to v0.14.4 (#21353)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-10 01:35:42 +00:00
Alex Waygood
020ff1723b [ty] Add narrowing for isinstance() and issubclass() checks that use PEP-604 unions (#21334) 2025-11-08 18:20:46 +00:00
Hugo van Kemenade
09e6af16c8 [ruff+ty] Add colour to --help (#21337) 2025-11-08 17:17:14 +01:00
Micha Reiser
76efc8061d [ty] Make variance_of logging trace only (#21339) 2025-11-08 14:10:24 +00:00
Dan Parizher
16de4aa3cc [refurb] Auto-fix annotated assignments (FURB101) (#21278)
## Summary

Fixed FURB101 (`read-whole-file`) to handle annotated assignments.
Previously, the rule would detect violations in code like `contents: str
= f.read()` but fail to generate a fix. Now it correctly generates fixes
that preserve type annotations (e.g., `contents: str =
Path("file.txt").read_text(encoding="utf-8")`).

Fixes #21274

## Problem Analysis

The FURB101 rule was only checking for `Stmt::Assign` statements when
determining whether a fix could be applied. When encountering annotated
assignments (`Stmt::AnnAssign`) like `contents: str = f.read()`, the
rule would:

1. Correctly detect the violation (the diagnostic was reported)
2. Fail to generate a fix because:
- The `visit_expr` method only matched `Stmt::Assign`, not
`Stmt::AnnAssign`
- The `generate_fix` function only accepted `Stmt::Assign` in its body
validation
   - The replacement code generation didn't account for type annotations

This occurred because Python's AST represents annotated assignments as a
different node type (`StmtAnnAssign`) with separate fields for the
target, annotation, and value, unlike regular assignments which use a
list of targets.

## Approach

The fix extends the rule to handle both assignment types:

1. **Updated `visit_expr` method**: Now matches both `Stmt::Assign` and
`Stmt::AnnAssign`, extracting:
   - Variable name from the target expression
   - Type annotation code (when present) using the code generator

2. **Updated `generate_fix` function**:
- Added `annotation: Option<String>` parameter to accept annotation code
- Updated body validation to accept both `Stmt::Assign` and
`Stmt::AnnAssign`
- Modified replacement code generation to preserve annotations: `{var}:
{annotation} = {binding}({filename_code}).{suggestion}`

3. **Added test case**: Added an annotated assignment test case to
verify the fix works correctly.

The implementation maintains backward compatibility with regular
assignments while adding support for annotated assignments, ensuring
type annotations are preserved in the generated fixes.

---------

Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2025-11-07 19:04:45 -05:00
Brent Westbrook
e06e108095 [flake8-annotations] Add link to allow-star-arg-any option (ANN401) (#21326)
Summary
--

Addresses
https://github.com/astral-sh/ruff/issues/19152#issuecomment-3501373508
by adding a link to the configuration option to the rule page.

Test Plan
--

Built the docs locally and made sure the link was present and working
2025-11-07 18:45:53 -05:00
William Woodruff
b6add3ee6d chore: bump dist, remove old commenting workflows (#21302) 2025-11-07 17:09:29 -05:00
Alex Waygood
39c21d7c6c [ty] Generalize some infrastructure around type visitors (#21323)
We have lots of `TypeVisitor`s that end up having very similar
`visit_type` implementations. This PR consolidates some of the code for
these so that there's less repetition and duplication.
2025-11-07 16:26:30 -05:00
Micha Reiser
1617292e9f Update CodSpeedHQ/action action to v4.3.3 (#21254)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-07 19:39:52 +00:00
Douglas Creager
faae72b836 [ty] Clarify behavior of constraint sets for gradual upper bounds and constraints (#21287)
When checking whether a constraint set is satisfied, if a typevar has a
non-fully-static upper bound or constraint, we are free to choose any
materialization that makes the check succeed.

In non-inferable positions, we have to show that the constraint set is
satisfied for all valid specializations, so it's best to choose the most
restrictive materialization, since that minimizes the set of valid
specializations that have to pass.

In inferable positions, we only have to show that the constraint set is
satisfied for _some_ valid specializations, so it's best to choose the
most permissive materialization, since that maximizes our chances of
finding a specialization that passes.
2025-11-07 14:01:39 -05:00
Brent Westbrook
276f1d0d88 Remove duplicate preview tests for FURB101 and FURB103 (#21303)
Summary
--

These rules are themselves in preview, so we don't need the additional
preview checks on the fixes or the separate preview tests. This has
confused me in a couple of reviews of changes to the fixes.

Test Plan
--

Existing tests, with the fixes previously only shown in the preview
tests now in the "non-preview" tests.
2025-11-07 12:47:21 -05:00
David Peter
ed18112cfa [ty] Add support for Literals in implicit type aliases (#21296)
## Summary

Add support for `Literal` types in implicit type aliases.

part of https://github.com/astral-sh/ty/issues/221

## Ecosystem analysis

This looks good to me, true positives and known problems.

## Test Plan

New Markdown tests.
2025-11-07 17:46:55 +01:00
Micha Reiser
8ba1cfebed [ty] Add missing heap_size to variance_of queries (#21318) 2025-11-07 16:18:28 +00:00
Dan Parizher
6185a2af9e [pyupgrade] Fix false positive on relative imports from local .builtins module (UP029) (#21309) 2025-11-07 16:01:52 +00:00
Micha Reiser
6cc3393ccd [ty] Make range/position conversions fallible (#21297)
Co-authored-by: Aria Desires <aria.desires@gmail.com>
2025-11-07 14:44:23 +00:00
Dylan
c7ff9826d6 Bump 0.14.4 (#21306) 2025-11-06 15:47:29 -06:00
Dhruv Manilawala
35640dd853 Fix main by using infer_expression (#21299) 2025-11-06 20:10:43 +00:00
Dhruv Manilawala
cb2e277482 [ty] Understand legacy and PEP 695 ParamSpec (#21139)
## Summary

This PR adds support for understanding the legacy definition and PEP 695
definition for `ParamSpec`.

This is still very initial and doesn't really implement any of the
semantics.

Part of https://github.com/astral-sh/ty/issues/157

## Test Plan

Add mdtest cases.

## Ecosystem analysis

Most of the diagnostics in `starlette` are due to the fact that ty now
understands `ParamSpec` is not a `Todo` type, so the assignability check
fails. The code looks something like:

```py
class _MiddlewareFactory(Protocol[P]):
    def __call__(self, app: ASGIApp, /, *args: P.args, **kwargs: P.kwargs) -> ASGIApp: ...  # pragma: no cover

class Middleware:
    def __init__(
        self,
        cls: _MiddlewareFactory[P],
        *args: P.args,
        **kwargs: P.kwargs,
    ) -> None:
        self.cls = cls
        self.args = args
        self.kwargs = kwargs

# ty complains that `ServerErrorMiddleware` is not assignable to `_MiddlewareFactory[P]`
Middleware(ServerErrorMiddleware, handler=error_handler, debug=debug)
```

There are multiple diagnostics where there's an attribute access on the
`Wrapped` object of `functools` which Pyright also raises:
```py
from functools import wraps

def my_decorator(f):
    @wraps(f)
    def wrapper(*args, **kwds):
        return f(*args, **kwds)

	# Pyright: Cannot access attribute "__signature__" for class "_Wrapped[..., Unknown, ..., Unknown]"
      Attribute "__signature__" is unknown [reportAttributeAccessIssue]
	# ty: Object of type `_Wrapped[Unknown, Unknown, Unknown, Unknown]` has no attribute `__signature__` [unresolved-attribute]
    wrapper.__signature__
    return wrapper
```

There are additional diagnostics that is due to the assignability checks
failing because ty now infers the `ParamSpec` instead of using the
`Todo` type which would always succeed. This results in a few
`no-matching-overload` diagnostics because the assignability checks
fail.

There are a few diagnostics related to
https://github.com/astral-sh/ty/issues/491 where there's a variable
which is either a bound method or a variable that's annotated with
`Callable` that doesn't contain the instance as the first parameter.

Another set of (valid) diagnostics are where the code hasn't provided
all the type variables. ty is now raising diagnostics for these because
we include `ParamSpec` type variable in the signature. For example,
`staticmethod[Any]` which contains two type variables.
2025-11-06 11:14:40 -05:00
Zanie Blue
132d10fb6f [ty] Discover site-packages from the environment that ty is installed in (#21286)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

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

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

There are various situations where users expect the Python packages
installed in the same environment as ty itself to be considered during
type checking. A minimal example would look like:

```
uv venv my-env
uv pip install my-env ty httpx
echo "import httpx" > foo.py
./my-env/bin/ty check foo.py
```

or

```
uv tool install ty --with httpx
echo "import httpx" > foo.py
ty check foo.py
```

While these are a bit contrived, there are real-world situations where a
user would expect a similar behavior to work. Notably, all of the other
type checkers consider their own environment when determining search
paths (though I'll admit that I have not verified when they choose not
to do this).

One common situation where users are encountering this today is with
`uvx --with-requirements script.py ty check script.py` — which is
currently our "best" recommendation for type checking a PEP 723 script,
but it doesn't work.

Of the options discussed in
https://github.com/astral-sh/ty/issues/989#issuecomment-3307417985, I've
chosen (2) as our criteria for including ty's environment in the search
paths.

- If no virtual environment is discovered, we will always include ty's
environment.
- If a `.venv` is discovered in the working directory, we will _prepend_
ty's environment to the search paths. The dependencies in ty's
environment (e.g., from `uvx --with`) will take precedence.
- If a virtual environment is active, e.g., `VIRTUAL_ENV` (i.e.,
including conda prefixes) is set, we will not include ty's environment.

The reason we need to special case the `.venv` case is that we both

1.  Recommend `uvx ty` today as a way to check your project
2. Want to enable `uvx --with <...> ty`

And I don't want (2) to break when you _happen_ to be in a project
(i.e., if we only included ty's environment when _no_ environment is
found) and don't want to remove support for (1).

I think long-term, I want to make `uvx <cmd>` layer the environment on
_top_ of the project environment (in uv), which would obviate the need
for this change when you're using uv. However, that change is breaking
and I think users will expect this behavior in contexts where they're
not using uv, so I think we should handle it in ty regardless.

I've opted not to include the environment if it's non-virtual (i.e., a
system environment) for now. It seems better to start by being more
restrictive. I left a comment in the code.

## Test Plan

I did some manual testing with the initial commit, then subsequently
added some unit tests.

```
❯ echo "import httpx" > example.py
❯ uvx --with httpx ty check example.py
Installed 8 packages in 19ms
error[unresolved-import]: Cannot resolve imported module `httpx`
 --> foo/example.py:1:8
  |
1 | import httpx
  |        ^^^^^
  |
info: Searched in the following paths during module resolution:
info:   1. /Users/zb/workspace/ty/python (first-party code)
info:   2. /Users/zb/workspace/ty (first-party code)
info:   3. vendored://stdlib (stdlib typeshed stubs vendored by ty)
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
info: rule `unresolved-import` is enabled by default

Found 1 diagnostic
❯ uvx --from . --with httpx ty check example.py
All checks passed!
```

```
❯ uv init --script foo.py
Initialized script at `foo.py`
❯ uv add --script foo.py httpx
warning: The Python request from `.python-version` resolved to Python 3.13.8, which is incompatible with the script's Python requirement: `>=3.14`
Updated `foo.py`
❯ echo "import httpx" >> foo.py
❯ uvx --with-requirements foo.py ty check foo.py
error[unresolved-import]: Cannot resolve imported module `httpx`
  --> foo.py:15:8
   |
13 | if __name__ == "__main__":
14 |     main()
15 | import httpx
   |        ^^^^^
   |
info: Searched in the following paths during module resolution:
info:   1. /Users/zb/workspace/ty/python (first-party code)
info:   2. /Users/zb/workspace/ty (first-party code)
info:   3. vendored://stdlib (stdlib typeshed stubs vendored by ty)
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
info: rule `unresolved-import` is enabled by default

Found 1 diagnostic
❯ uvx --from . --with-requirements foo.py ty check foo.py
All checks passed!
```

Notice we do not include ty's environment if `VIRTUAL_ENV` is set

```
❯ VIRTUAL_ENV=.venv uvx --with httpx ty check foo/example.py
error[unresolved-import]: Cannot resolve imported module `httpx`
 --> foo/example.py:1:8
  |
1 | import httpx
  |        ^^^^^
  |
info: Searched in the following paths during module resolution:
info:   1. /Users/zb/workspace/ty/python (first-party code)
info:   2. /Users/zb/workspace/ty (first-party code)
info:   3. vendored://stdlib (stdlib typeshed stubs vendored by ty)
info:   4. /Users/zb/workspace/ty/.venv/lib/python3.13/site-packages (site-packages)
info: make sure your Python environment is properly configured: https://docs.astral.sh/ty/modules/#python-environment
info: rule `unresolved-import` is enabled by default

Found 1 diagnostic
```
2025-11-06 09:27:49 -05:00
Alex Waygood
f189aad6d2 [ty] Make special cases for UnionType slightly narrower (#21276)
Fixes https://github.com/astral-sh/ty/issues/1478
2025-11-06 09:00:43 -05:00
Ben Beasley
5517c9943a Require ignore 0.4.24 in Cargo.toml (#21292)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

<!-- What's the purpose of the change? What does it do, and why? -->
Since 4c4ddc8c29, ruff uses the `WalkBuilder::current_dir` API
[introduced in `ignore` version
0.4.24](https://diff.rs/ignore/0.4.23/0.4.24/src%2Fwalk.rs), so it
should explicitly depend on this minimum version.

See also https://github.com/astral-sh/ruff/pull/20979.

## Test Plan

<!-- How was it tested? -->
Source inspection verifies this version is necessary; no additional
testing is required since `Cargo.lock` already has (at least) this
version.
2025-11-06 07:43:32 -05:00
Matthew Mckee
b5ff96595d [ty] Favour imported symbols over builtin symbols (#21285)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

Raised by @AlexWaygood.

We previously did not favour imported symbols, when we probably
should've

## Test Plan

Add test showing that we favour imported symbol even if it is
alphabetically after other symbols that are builtin.
2025-11-06 06:46:08 -05:00
Loïc Riegel
c6573b16ac docs: revise Ruff setup instructions for Zed editor (#20935)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-11-06 01:11:29 +00:00
Micha Reiser
76127e5fb5 [ty] Update salsa (#21281) 2025-11-05 22:15:01 +00:00
Bhuminjay Soni
cddc0fedc2 [syntax-error]: no binding for nonlocal PLE0117 as a semantic syntax error (#21032)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

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

This PR ports PLE0117 as a semantic syntax error.

## Test Plan

<!-- How was it tested? -->
Tests previously written

---------

Signed-off-by: 11happy <soni5happy@gmail.com>
Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-11-05 19:13:28 +00:00
Douglas Creager
eda85f3c64 [ty] Constraining a typevar with itself (possibly via union or intersection) (#21273)
This PR carries over some of the `has_relation_to` logic for comparing a
typevar with itself. A typevar will specialize to the same type if it's
mentioned multiple times, so it is always assignable to and a subtype of
itself. (Note that typevars can only specialize to fully static types.)
This is also true when the typevar appears in a union on the right-hand
side, or in an intersection on the left-hand side. Similarly, a typevar
is always disjoint from its negation, so when a negated typevar appears
on the left-hand side, the constraint set is never satisfiable.

(Eventually this will allow us to remove the corresponding clauses from
`has_relation_to`, but that can't happen until more of #20093 lands.)
2025-11-05 12:31:53 -05:00
chiri
cef6600cf3 [ruff] Fix false positives on starred arguments (RUF057) (#21256)
## Summary

Fixes https://github.com/astral-sh/ruff/issues/21209

## Test Plan

`cargo nextest run ruf057`
2025-11-05 12:07:33 -05:00
Ibraheem Ahmed
5c69e00d1c [ty] Simplify unions containing multiple type variables during inference (#21275)
## Summary

Splitting this one out from https://github.com/astral-sh/ruff/pull/21210. This is also something that should be made obselete by the new constraint solver, but is easy enough to fix now.
2025-11-05 15:03:19 +00:00
Micha Reiser
7569b09bdd [ty] Add ty_server::Db trait (#21241) 2025-11-05 13:40:07 +00:00
Micha Reiser
7009d60260 [ty] Refactor Range to/from TextRange conversion as prep for notebook support (#21230) 2025-11-05 13:24:03 +00:00
David Peter
f79044478c [ty] Fix playground crash when file name includes path separator (#21151) 2025-11-04 18:36:36 +01:00
Dan Parizher
47e41ac6b6 [refurb] Fix false negative for underscores before sign in Decimal constructor (FURB157) (#21190)
## Summary

Fixes FURB157 false negative where `Decimal("_-1")` was not flagged as
verbose when underscores precede the sign character. This fixes #21186.

## Problem Analysis

The `verbose-decimal-constructor` (FURB157) rule failed to detect
verbose `Decimal` constructors when the sign character (`+` or `-`) was
preceded by underscores. For example, `Decimal("_-1")` was not flagged,
even though it can be simplified to `Decimal(-1)`.

The bug occurred because the rule checked for the sign character at the
start of the string before stripping leading underscores. According to
Python's `Decimal` parser behavior (as documented in CPython's
`_pydecimal.py`), underscores are removed before parsing the sign. The
rule's logic didn't match this behavior, causing a false negative for
cases like `"_-1"` where the underscore came before the sign.

This was a regression introduced in version 0.14.3, as these cases were
correctly flagged in version 0.14.2.

## Approach

The fix updates the sign extraction logic to:
1. Strip leading underscores first (matching Python's Decimal parser
behavior)
2. Extract the sign from the underscore-stripped string
3. Preserve the string after the sign for normalization purposes

This ensures that cases like `Decimal("_-1")`, `Decimal("_+1")`, and
`Decimal("_-1_000")` are correctly detected and flagged. The
normalization logic was also updated to use the string after the sign
(without underscores) to avoid double signs in the replacement output.
2025-11-04 11:02:50 -05:00
David Peter
2e7ab00d51 [ty] Allow values of type None in type expressions (#21263)
## Summary

Allow values of type `None` in type expressions. The [typing
spec](https://typing.python.org/en/latest/spec/annotations.html#type-and-annotation-expressions)
could be more explicit on whether this is actually allowed or not, but
it seems relatively harmless and does help in some use cases like:

```py
try:
    from module import MyClass
except ImportError:
    MyClass = None  # ty: ignore


def f(m: MyClass):
    pass
``` 

## Test Plan

Updated tests, ecosystem check.
2025-11-04 16:29:55 +01:00
Ibraheem Ahmed
d8106d38a0 Run codspeed benchmarks with profiling profile (#21261)
## Summary

This reduces the walltime benchmarks from 15m to 10m, and we should see an even bigger improvement once build caching kicks in, so I think it's worth the downsides.
2025-11-04 09:59:40 -05:00
Micha Reiser
4fd8d4b0ee [ty] Update expected diagnostic count in benchmarks (#21269) 2025-11-04 03:18:12 +00:00
Brent Westbrook
63b1c1ea8b Avoid extra parentheses for long match patterns with as captures (#21176)
Summary
--

This PR fixes #17796 by taking the approach mentioned in
https://github.com/astral-sh/ruff/issues/17796#issuecomment-2847943862
of simply recursing into the `MatchAs` patterns when checking if we need
parentheses. This allows us to reuse the parentheses in the inner
pattern before also breaking the `MatchAs` pattern itself:

```diff
 match class_pattern:
     case Class(xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx) as capture:
         pass
-    case (
-        Class(xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx) as capture
-    ):
+    case Class(
+        xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+    ) as capture:
         pass
-    case (
-        Class(
-            xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
-        ) as capture
-    ):
+    case Class(
+        xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+    ) as capture:
         pass
     case (
         Class(
@@ -685,13 +683,11 @@
 match sequence_pattern_brackets:
     case [xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx] as capture:
         pass
-    case (
-        [xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx] as capture
-    ):
+    case [
+        xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+    ] as capture:
         pass
-    case (
-        [
-            xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
-        ] as capture
-    ):
+    case [
+        xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+    ] as capture:
         pass
```

I haven't really resolved the question of whether or not it's okay
always to recurse, but I'm hoping the ecosystem check on this PR might
shed some light on that.

Test Plan
--

New tests based on the issue and then reviewing the ecosystem check here
2025-11-03 17:06:52 -05:00
Micha Reiser
3c5e4e1477 [ty] Update salsa (#21265) 2025-11-03 22:00:30 +00:00
Ibraheem Ahmed
3c8fb68765 [ty] dict is not assignable to TypedDict (#21238)
## Summary

A lot of the bidirectional inference work relies on `dict` not being
assignable to `TypedDict`, so I think it makes sense to add this before
fully implementing https://github.com/astral-sh/ty/issues/1387.
2025-11-03 16:57:49 -05:00
Alex Waygood
42adfd40ea Run py-fuzzer with --profile=profiling locally and in CI (#21266) 2025-11-03 16:53:42 -05:00
chiri
79a02711c1 [refurb] Expand fix safety for keyword arguments and Decimals (FURB164) (#21259)
## Summary

Fixes https://github.com/astral-sh/ruff/issues/21257

## Test Plan

`cargo nextest run furb164`
2025-11-03 16:09:02 -05:00
David Peter
d2fe6347fb [ty] Rename UnionType to types.UnionType (#21262) 2025-11-03 22:06:56 +01:00
David Peter
1fe958c694 [ty] Implicit type aliases: Support for PEP 604 unions (#21195)
## Summary

Add support for implicit type aliases that use PEP 604 unions:
```py
IntOrStr = int | str

reveal_type(IntOrStr)  # UnionType

def _(int_or_str: IntOrStr):
    reveal_type(int_or_str)  # int | str
```

## Typing conformance

The changes are either removed false positives, or new diagnostics due
to known limitations unrelated to this PR.

## Ecosystem impact

Spot checked, a mix of true positives and known limitations.

## Test Plan

New Markdown tests.
2025-11-03 21:50:25 +01:00
Carl Meyer
fe4ee81b97 [ty] prefer submodule over module __getattr__ in from-imports (#21260)
Fixes https://github.com/astral-sh/ty/issues/1053

## Summary

Other type checkers prioritize a submodule over a package `__getattr__`
in `from mod import sub`, even though the runtime precedence is the
other direction. In effect, this is making an implicit assumption that a
module `__getattr__` will not handle (that is, will raise
`AttributeError`) for names that are also actual submodules, rather than
shadowing them. In practice this seems like a realistic assumption in
the ecosystem? Or at least the ecosystem has adapted to it, and we need
to adapt this precedence also, for ecosystem compatibility.

The implementation is a bit ugly, precisely because it departs from the
runtime semantics, and our implementation is oriented toward modeling
runtime semantics accurately. That is, `__getattr__` is modeled within
the member-lookup code, so it's hard to split "member lookup result from
module `__getattr__`" apart from other member lookup results. I did this
via a synthetic `TypeQualifier::FROM_MODULE_GETATTR` that we attach to a
type resulting from a member lookup, which isn't beautiful but it works
well and doesn't introduce inefficiency (e.g. redundant member lookups).

## Test Plan

Updated mdtests.

Also added a related mdtest formalizing our support for a module
`__getattr__` that is explicitly annotated to accept a limited set of
names. In principle this could be an alternative (more explicit) way to
handle the precedence problem without departing from runtime semantics,
if the ecosystem would adopt it.

### Ecosystem analysis

Lots of removed diagnostics which are an improvement because we now
infer the expected submodule.

Added diagnostics are mostly unrelated issues surfaced now because we
previously had an earlier attribute error resulting in `Unknown`; now we
correctly resolve the module so that earlier attribute error goes away,
we get an actual type instead of `Unknown`, and that triggers a new
error.

In scipy and sklearn, the module `__getattr__` which we were respecting
previously is un-annotated so returned a forgiving `Unknown`; now we
correctly see the actual module, which reveals some cases of
https://github.com/astral-sh/ty/issues/133 that were previously hidden
(`scipy/optimize/__init__.py` [imports `from
._tnc`](eff82ca575/scipy/optimize/__init__.py (L429)).)

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-11-03 15:24:01 -05:00
Wei Lee
0433526897 [airflow] extend deprecated argument concurrency in airflow..DAG (AIR301) (#21220)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

<!-- What's the purpose of the change? What does it do, and why? -->
* extend AIR301 to include deprecated argument `concurrency` in
`airflow....DAG`

## Test Plan

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

update the existing test fixture in the first commit and then reorganize
in the second one
2025-11-03 15:20:20 -05:00
Tom Kuson
78ee7ae925 [flake8-comprehensions] Fix typo in C416 documentation (#21184)
## Summary

Adds missing curly brace to the C416 documentation.

## Test Plan

Build the docs
2025-11-03 14:04:59 -05:00
renovate[bot]
21ec8aa7d4 Update Rust crate unicode-ident to v1.0.22 (#21228)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 16:10:49 +00:00
renovate[bot]
64a255df49 Update to Unicode 17 for line-width calculations (#21231)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 15:55:50 +00:00
Shunsuke Shibayama
b5305b5f32 [ty] Fix panic due to simplifying Divergent types out of intersections types (#21253) 2025-11-03 15:41:11 +00:00
Alex Waygood
39f105bc4a [ty] Use "cannot" consistently over "can not" (#21255) 2025-11-03 10:38:20 -05:00
Micha Reiser
e8c35b9704 [ty] Simplify semantic token tests (#21206) 2025-11-03 15:35:42 +00:00
Micha Reiser
1b2ed6a503 [ty] Fix caching of imported modules in playground (#21251) 2025-11-03 15:00:30 +00:00
Micha Reiser
de9df1b326 Revert "Update CodSpeedHQ/action action to v4.3.1" (#21252) 2025-11-03 14:53:04 +00:00
Matthew Mckee
e2e83acd2f [ty] Remove mentions of VS Code from server logs (#21155)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-11-03 14:49:58 +00:00
Dan Parizher
6ddfb51d71 [flake8-bugbear] Mark fix as unsafe for non-NFKC attribute names (B009, B010) (#21131) 2025-11-03 14:45:23 +00:00
Matthew Mckee
e017b039df [ty] Favor in scope completions (#21194)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

Resolves https://github.com/astral-sh/ty/issues/1464

We sort the completions before we add the unimported ones, meaning that
imported completions show up before unimported ones.

This is also spoken about in
https://github.com/astral-sh/ty/issues/1274, and this is probably a
duplicate of that.

@AlexWaygood mentions this
[here](https://github.com/astral-sh/ty/issues/1274#issuecomment-3345942698)
too.

## Test Plan

Add a test showing even if an unimported completion "should"
(alphabetically before) come first, we favor the imported one.
2025-11-03 09:33:05 -05:00
Brent Westbrook
0dfd55babf Delete unused AsciiCharSet in FURB156 (#21181)
Summary
--

This code has been unused since #14233 but not detected by clippy I
guess. This should help to remove the temptation to use the set
comparison again like I suggested in #21144. And we shouldn't do the set
comparison because of #13802, which #14233 fixed.

Test Plan
--

Existing tests
2025-11-03 08:38:34 -05:00
renovate[bot]
f947c23cd7 Update Rust crate tempfile to v3.23.0 (#21248)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 03:41:20 +00:00
renovate[bot]
02879fa377 Update actions/setup-node action to v6 (#21249)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 03:34:13 +00:00
renovate[bot]
7dbfb56c3d Update astral-sh/setup-uv action to v7 (#21250)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 03:33:16 +00:00
renovate[bot]
f97c38dd88 Update Rust crate matchit to 0.9.0 (#21245)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 03:32:49 +00:00
renovate[bot]
14fce1f788 Update Rust crate regex to v1.12.2 (#21246)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 03:32:04 +00:00
renovate[bot]
31194d048d Update Rust crate serde_with to v3.15.1 (#21247)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 03:31:24 +00:00
renovate[bot]
fe95ff6b06 Update dependency pyodide to ^0.29.0 (#21236)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 03:23:24 +00:00
renovate[bot]
61c1007137 Update Rust crate bitflags to v2.10.0 (#21242)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 03:21:41 +00:00
renovate[bot]
884c3b178e Update Rust crate indexmap to v2.12.0 (#21244)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 03:21:03 +00:00
renovate[bot]
ade727ce66 Update Rust crate csv to v1.4.0 (#21243)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 03:20:42 +00:00
renovate[bot]
3493c9b67a Update dependency tomli to v2.3.0 (#21240)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 03:17:38 +00:00
renovate[bot]
666dd5fef1 Update dependency ruff to v0.14.3 (#21239)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 03:13:38 +00:00
renovate[bot]
bb05527350 Update dependency monaco-editor to ^0.54.0 (#21235)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 03:06:16 +00:00
renovate[bot]
dc373e639e Update CodSpeedHQ/action action to v4.3.1 (#21234)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 03:04:20 +00:00
renovate[bot]
fc71c90de6 Update taiki-e/install-action action to v2.62.45 (#21233)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 03:02:16 +00:00
renovate[bot]
c11b00bea0 Update Rust crate wasm-bindgen-test to v0.3.55 (#21232)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 02:57:13 +00:00
renovate[bot]
770b4d12ab Update Rust crate tikv-jemallocator to v0.6.1 (#21226)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 02:35:50 +00:00
renovate[bot]
c596a78c08 Update Rust crate schemars to v1.0.5 (#21222)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 02:27:40 +00:00
renovate[bot]
cb98175a36 Update Rust crate syn to v2.0.108 (#21224)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 02:22:04 +00:00
renovate[bot]
3bef60f69a Update Rust crate toml to v0.9.8 (#21227)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 02:21:47 +00:00
renovate[bot]
f477e11d26 Update Rust crate thiserror to v2.0.17 (#21225)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 02:21:21 +00:00
renovate[bot]
c0bd092fa9 Update Rust crate snapbox to v0.6.23 (#21223)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 02:20:19 +00:00
renovate[bot]
73b9b8eb6b Update Rust crate proc-macro2 to v1.0.103 (#21221)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 02:15:56 +00:00
renovate[bot]
80eeb1d64f Update Rust crate clap to v4.5.51 (#21214)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 01:50:12 +00:00
renovate[bot]
50b75cfcc6 Update cargo-bins/cargo-binstall action to v1.15.10 (#21212)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 01:49:44 +00:00
renovate[bot]
1c4a9d6a06 Update Rust crate globset to v0.4.18 (#21216)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[globset](https://redirect.github.com/BurntSushi/ripgrep/tree/master/crates/globset)
([source](https://redirect.github.com/BurntSushi/ripgrep/tree/HEAD/crates/globset))
| workspace.dependencies | patch | `0.4.17` -> `0.4.18` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>BurntSushi/ripgrep (globset)</summary>

###
[`v0.4.18`](https://redirect.github.com/BurntSushi/ripgrep/compare/globset-0.4.17...globset-0.4.18)

[Compare
Source](https://redirect.github.com/BurntSushi/ripgrep/compare/globset-0.4.17...globset-0.4.18)

</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:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNTkuNCIsInVwZGF0ZWRJblZlciI6IjQxLjE1OS40IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJpbnRlcm5hbCJdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 02:48:22 +01:00
renovate[bot]
222c6fd496 Update Rust crate ctrlc to v3.5.1 (#21215)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 01:48:02 +00:00
renovate[bot]
b754abff1b Update Rust crate aho-corasick to v1.1.4 (#21213)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [aho-corasick](https://redirect.github.com/BurntSushi/aho-corasick) |
workspace.dependencies | patch | `1.1.3` -> `1.1.4` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>BurntSushi/aho-corasick (aho-corasick)</summary>

###
[`v1.1.4`](https://redirect.github.com/BurntSushi/aho-corasick/compare/1.1.3...1.1.4)

[Compare
Source](https://redirect.github.com/BurntSushi/aho-corasick/compare/1.1.3...1.1.4)

</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:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNTkuNCIsInVwZGF0ZWRJblZlciI6IjQxLjE1OS40IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJpbnRlcm5hbCJdfQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 02:47:30 +01:00
renovate[bot]
41fe4d7f8c Update Rust crate ignore to v0.4.25 (#21217)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 01:47:12 +00:00
renovate[bot]
f14631e1cc Update Rust crate indicatif to v0.18.2 (#21218)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 01:46:05 +00:00
renovate[bot]
02f2dba28e Update Rust crate indoc to v2.0.7 (#21219)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-03 01:45:20 +00:00
Carl Meyer
0454a72674 [ty] don't union in default type for annotated parameters (#21208) 2025-11-02 18:21:54 -05:00
Carl Meyer
c32234cf0d [ty] support subscripting typing.Literal with a type alias (#21207)
Fixes https://github.com/astral-sh/ty/issues/1368

## Summary

Add support for patterns like this, where a type alias to a literal type
(or union of literal types) is used to subscript `typing.Literal`:

```py
type MyAlias = Literal[1]
def _(x: Literal[MyAlias]): ...
```

This shows up in the ecosystem report for PEP 613 type alias support.

One interesting case is an alias to `bool` or an enum type. `bool` is an
equivalent type to `Literal[True, False]`, which is a union of literal
types. Similarly an enum type `E` is also equivalent to a union of its
member literal types. Since (for explicit type aliases) we infer the RHS
directly as a type expression, this makes it difficult for us to
distinguish between `bool` and `Literal[True, False]`, so we allow
either one to (or an alias to either one) to appear inside `Literal`,
where other type checkers allow only the latter.

I think for implicit type aliases it may be simpler to support only
types derived from actually subscripting `typing.Literal`, though, so I
didn't make a TODO-comment commitment here.

## Test Plan

Added mdtests, including TODO-filled tests for PEP 613 and implicit type
aliases.

### Conformance suite

All changes here are positive -- we now emit errors on lines that should
be errors. This is a side effect of the new implementation, not the
primary purpose of this PR, but it's still a positive change.

### Ecosystem

Eliminates one ecosystem false positive, where a PEP 695 type alias for
a union of literal types is used to subscript `typing.Literal`.
2025-11-02 12:39:55 -05:00
David Peter
566d1d6497 [ty] Update to the latest version of the conformance suite (#21205)
## Summary

There have been some larger-scale updates to the conformance suite since
we introduced our CI job, so it seems sensible to bump the version of
the conformance suite to the latest state.

## Test plan

This is a bit awkward to test. Here is the diff of running ty on the
conformance suite before and after this bump. I filtered out line/column
information (`sed -re 's/\.py:[0-9]+:[0-9]+:/.py/'`) to avoid spurious
changes from content that has simply been moved around.

```diff
1,2c1
< fatal[panic] Panicked at /home/shark/.cargo/git/checkouts/salsa-e6f3bb7c2a062968/cdd0b85/src/function/execute.rs:419:17 when checking `/home/shark/typing/conformance/tests/aliases_typealiastype.py`: `infer_definition_types(Id(1a99c)): execute: too many cycle iterations`
< src/type_checker.py error[unresolved-import] Cannot resolve imported module `tqdm`
---
> fatal[panic] Panicked at /home/shark/.cargo/git/checkouts/salsa-e6f3bb7c2a062968/cdd0b85/src/function/execute.rs:419:17 when checking `/home/shark/typing/conformance/tests/aliases_typealiastype.py`: `infer_definition_types(Id(6e4c)): execute: too many cycle iterations`
205,206d203
< tests/constructors_call_metaclass.py error[type-assertion-failure] Argument does not have asserted type `Never`
< tests/constructors_call_metaclass.py error[missing-argument] No argument provided for required parameter `x` of function `__new__`
268a266,273
> tests/dataclasses_match_args.py error[type-assertion-failure] Argument does not have asserted type `tuple[Literal["x"]]`
> tests/dataclasses_match_args.py error[unresolved-attribute] Class `DC1` has no attribute `__match_args__`
> tests/dataclasses_match_args.py error[type-assertion-failure] Argument does not have asserted type `tuple[Literal["x"]]`
> tests/dataclasses_match_args.py error[unresolved-attribute] Class `DC2` has no attribute `__match_args__`
> tests/dataclasses_match_args.py error[type-assertion-failure] Argument does not have asserted type `tuple[Literal["x"]]`
> tests/dataclasses_match_args.py error[unresolved-attribute] Class `DC3` has no attribute `__match_args__`
> tests/dataclasses_match_args.py error[unresolved-attribute] Class `DC4` has no attribute `__match_args__`
> tests/dataclasses_match_args.py error[type-assertion-failure] Argument does not have asserted type `tuple[()]`
339a345
> tests/directives_assert_type.py error[type-assertion-failure] Argument does not have asserted type `Any`
424a431
> tests/generics_defaults.py error[type-assertion-failure] Argument does not have asserted type `Any`
520a528,529
> tests/generics_syntax_infer_variance.py error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `T@ShouldBeCovariant2 | Sequence[T@ShouldBeCovariant2]`
> tests/generics_syntax_infer_variance.py error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `int`
711a721
> tests/namedtuples_define_class.py error[too-many-positional-arguments] Too many positional arguments: expected 3, got 4
795d804
< tests/protocols_explicit.py error[invalid-attribute-access] Cannot assign to ClassVar `cm1` from an instance of type `Self@__init__`
822,823d830
< tests/qualifiers_annotated.py error[invalid-syntax] named expression cannot be used within a type annotation
< tests/qualifiers_annotated.py error[invalid-syntax] await expression cannot be used within a type annotation
922a930,953
> tests/typeddicts_extra_items.py error[invalid-key] Invalid key for TypedDict `Movie`: Unknown key "novel_adaptation"
> tests/typeddicts_extra_items.py error[invalid-key] Invalid key for TypedDict `Movie`: Unknown key "year"
> tests/typeddicts_extra_items.py error[type-assertion-failure] Argument does not have asserted type `bool`
> tests/typeddicts_extra_items.py error[invalid-key] Invalid key for TypedDict `Movie`: Unknown key "novel_adaptation"
> tests/typeddicts_extra_items.py error[invalid-argument-type] Invalid argument to key "year" with declared type `int` on TypedDict `InheritedMovie`: value of type `None`
> tests/typeddicts_extra_items.py error[invalid-key] Invalid key for TypedDict `InheritedMovie`: Unknown key "other_extra_key"
> tests/typeddicts_extra_items.py error[invalid-key] Invalid key for TypedDict `MovieEI`: Unknown key "year"
> tests/typeddicts_extra_items.py error[invalid-key] Invalid key for TypedDict `MovieExtraInt`: Unknown key "year"
> tests/typeddicts_extra_items.py error[invalid-key] Invalid key for TypedDict `MovieExtraStr`: Unknown key "description"
> tests/typeddicts_extra_items.py error[invalid-key] Invalid key for TypedDict `MovieExtraInt`: Unknown key "year"
> tests/typeddicts_extra_items.py error[invalid-key] Invalid key for TypedDict `NonClosedMovie`: Unknown key "year"
> tests/typeddicts_extra_items.py error[invalid-key] Invalid key for TypedDict `ExtraMovie`: Unknown key "year"
> tests/typeddicts_extra_items.py error[invalid-key] Invalid key for TypedDict `ExtraMovie`: Unknown key "language"
> tests/typeddicts_extra_items.py error[invalid-key] Invalid key for TypedDict `ClosedMovie`: Unknown key "year"
> tests/typeddicts_extra_items.py error[invalid-key] Invalid key for TypedDict `MovieExtraStr`: Unknown key "summary"
> tests/typeddicts_extra_items.py error[invalid-key] Invalid key for TypedDict `MovieExtraInt`: Unknown key "year"
> tests/typeddicts_extra_items.py error[invalid-assignment] Object of type `dict[Unknown | str, Unknown | str | int]` is not assignable to `Mapping[str, int]`
> tests/typeddicts_extra_items.py error[type-assertion-failure] Argument does not have asserted type `list[tuple[str, int | str]]`
> tests/typeddicts_extra_items.py error[type-assertion-failure] Argument does not have asserted type `list[int | str]`
> tests/typeddicts_extra_items.py error[unresolved-attribute] Object of type `IntDict` has no attribute `clear`
> tests/typeddicts_extra_items.py error[invalid-key] Invalid key for TypedDict `IntDictWithNum`: Unknown key "bar" - did you mean "num"?
> tests/typeddicts_extra_items.py error[type-assertion-failure] Argument does not have asserted type `tuple[str, int]`
> tests/typeddicts_extra_items.py error[invalid-key] Cannot access `IntDictWithNum` with a key of type `str`. Only string literals are allowed as keys on TypedDicts.
> tests/typeddicts_extra_items.py error[invalid-key] Invalid key for TypedDict `IntDictWithNum` of type `str`
950c981
< Found 949 diagnostics
---
> Found 980 diagnostics
```
2025-11-02 17:33:31 +01:00
Micha Reiser
6c3d6124c8 [ty] Fix range filtering for tokens starting at the end of the requested range (#21193)
Co-authored-by: David Peter <sharkdp@users.noreply.github.com>
2025-11-02 14:58:36 +00:00
David Peter
73107a083c [ty] Type inference for comprehensions (#20962)
## Summary

Adds type inference for list/dict/set comprehensions, including
bidirectional inference:

```py
reveal_type({k: v for k, v in [("a", 1), ("b", 2)]})  # dict[Unknown | str, Unknown | int]

squares: list[int | None] = [x for x in range(10)]
reveal_type(squares)  # list[int | None]
```

## Ecosystem impact

I did spot check the changes and most of them seem like known
limitations or true positives. Without proper bidirectional inference,
we saw a lot of false positives.

## Test Plan

New Markdown tests
2025-11-02 14:35:33 +01:00
Matthew Mckee
de1a6fb8ad Clean up definition completions docs and tests (#21183)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

@BurntSushi provided some feedback in #21146 so i address it here.
2025-11-02 08:01:06 -05:00
Alex Waygood
bff32a41dc [ty] Increase timeout-minutes to 10 for py-fuzzer job (#21196) 2025-11-01 22:06:03 -04:00
Micha Reiser
17c7b3cde1 Bump MSRV to Rust 1.89 (#21180) 2025-11-01 02:26:38 +00:00
Micha Reiser
921f409ee8 Update Rust toolchain to 1.91 (#21179) 2025-11-01 01:50:58 +00:00
github-actions[bot]
a151f9746d [ty] Sync vendored typeshed stubs (#21178)
Close and reopen this PR to trigger CI

---------

Co-authored-by: typeshedbot <>
2025-10-31 21:03:40 -04:00
Gautham Venkataraman
521217bb90 [ruff]: Make ruff analyze graph work with jupyter notebooks (#21161)
Co-authored-by: Gautham Venkataraman <gautham@dexterenergy.ai>
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-10-31 21:47:01 +00:00
Alex Waygood
a32d5b8dc4 [ty] Improve exhaustiveness analysis for type variables with bounds or constraints (#21172) 2025-10-31 16:51:11 -04:00
Micha Reiser
6337e22f0c [ty] Smaller refactors to server API in prep for notebook support (#21095) 2025-10-31 20:00:04 +00:00
Brent Westbrook
827d8ae5d4 Allow newlines after function headers without docstrings (#21110)
Summary
--

This is a first step toward fixing #9745. After reviewing our open
issues and several Black issues and PRs, I personally found the function
case the most compelling, especially with very long argument lists:

```py
def func(
	self,
	arg1: int,
	arg2: bool,
	arg3: bool,
	arg4: float,
	arg5: bool,
) -> tuple[...]:
	if arg2 and arg3:
		raise ValueError
```

or many annotations:

```py
def function(
    self, data: torch.Tensor | tuple[torch.Tensor, ...], other_argument: int
) -> torch.Tensor | tuple[torch.Tensor, ...]:
    do_something(data)
    return something
```

I think docstrings help the situation substantially both because syntax
highlighting will usually give a very clear separation between the
annotations and the docstring and because we already allow a blank line
_after_ the docstring:

```py
def function(
    self, data: torch.Tensor | tuple[torch.Tensor, ...], other_argument: int
) -> torch.Tensor | tuple[torch.Tensor, ...]:
    """
	A function doing something.

	And a longer description of the things it does.
	"""

    do_something(data)
    return something
```

There are still other comments on #9745, such as [this one] with 9
upvotes, where users specifically request blank lines in all block
types, or at least including conditionals and loops. I'm sympathetic to
that case as well, even if personally I don't find an [example] like
this:

```py
if blah:

    # Do some stuff that is logically related
    data = get_data()

    # Do some different stuff that is logically related
    results = calculate_results()

    return results
```

to be much more readable than:

```py
if blah:
    # Do some stuff that is logically related
    data = get_data()

    # Do some different stuff that is logically related
    results = calculate_results()

    return results
```

I'm probably just used to the latter from the formatters I've used, but
I do prefer it. I also think that functions are the least susceptible to
the accidental introduction of a newline after refactoring described in
Micha's [comment] on #8893.

I actually considered further restricting this change to functions with
multiline headers. I don't think very short functions like:

```py
def foo():

    return 1
```

benefit nearly as much from the allowed newline, but I just went with
any function without a docstring for now. I guess a marginal case like:

```py
def foo(a_long_parameter: ALongType, b_long_parameter: BLongType) -> CLongType:

    return 1
```

might be a good argument for not restricting it.

I caused a couple of syntax errors before adding special handling for
the ellipsis-only case, so I suspect that there are some other
interesting edge cases that may need to be handled better.

Test Plan
--

Existing tests, plus a few simple new ones. As noted above, I suspect
that we may need a few more for edge cases I haven't considered.

[this one]:
https://github.com/astral-sh/ruff/issues/9745#issuecomment-2876771400
[example]:
https://github.com/psf/black/issues/902#issuecomment-1562154809
[comment]:
https://github.com/astral-sh/ruff/issues/8893#issuecomment-1867259744
2025-10-31 14:53:40 -04:00
David Peter
1734ddfb3e [ty] Do not promote literals in contravariant positions of generic specializations (#21171)
## Summary

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

supersedes https://github.com/astral-sh/ruff/pull/20950 by @ibraheemdev 

## Test Plan

New regression test
2025-10-31 17:48:34 +01:00
Ibraheem Ahmed
ff3a6a8fbd [ty] Support type context of union attribute assignments (#21170)
## Summary

Turns out this is easy to implement. Resolves
https://github.com/astral-sh/ty/issues/1375.
2025-10-31 12:41:14 -04:00
Carl Meyer
9664474c51 [ty] rollback preferring declared type on invalid TypedDict creation (#21169)
## Summary

Discussion with @ibraheemdev clarified that
https://github.com/astral-sh/ruff/pull/21168 was incorrect. In a case of
failed inference of a dict literal as a `TypedDict`, we should store the
context-less inferred type of the dict literal as the type of the dict
literal expression itself; the fallback to declared type should happen
at the level of the overall assignment definition.

The reason the latter isn't working yet is because currently we
(wrongly) consider a homogeneous dict type as assignable to a
`TypedDict`, so we don't actually consider the assignment itself as
failed. So the "bug" I observed (and tried to fix) will naturally be
fixed by implementing TypedDict assignability rules.

Rollback https://github.com/astral-sh/ruff/pull/21168 except for the
tests, and modify the tests to include TODOs as needed.

## Test Plan

Updated mdtests.
2025-10-31 12:06:47 -04:00
Luca Chiodini
69b4c29924 Consistently wrap tokens in parser diagnostics in backticks instead of 'quotes' (#21163)
The parser currently uses single quotes to wrap tokens. This is
inconsistent with the rest of ruff/ty, which use backticks.

For example, see the inconsistent diagnostics produced in this simple
example: https://play.ty.dev/0a9d6eab-6599-4a1d-8e40-032091f7f50f

Consistently wrapping tokens in backticks produces uniform diagnostics.
Following the style decision of #723, in #2889 some quotes were already
switched into backticks.

This is also in line with Rust's guide on diagnostics
(https://rustc-dev-guide.rust-lang.org/diagnostics.html#diagnostic-structure):

> When code or an identifier must appear in a message or label, it
should be surrounded with backticks
2025-10-31 11:59:11 -04:00
Ibraheem Ahmed
bb40c34361 [ty] Use declared attribute types as type context (#21143)
## Summary

For example:
```py
class X:
    x: list[int | str]

def _(x: X):
    x.x = [1]
```

Resolves https://github.com/astral-sh/ty/issues/1375.
2025-10-31 15:48:28 +00:00
chiri
b93d8f2b9f [refurb] Preserve argument ordering in autofix (FURB103) (#20790)
Fixes https://github.com/astral-sh/ruff/issues/20785
2025-10-31 11:16:09 -04:00
Carl Meyer
1d111c8780 [ty] prefer declared type on invalid TypedDict creation (#21168)
## Summary

In general, when we have an invalid assignment (inferred assigned type
is not assignable to declared type), we fall back to inferring the
declared type, since the declared type is a more explicit declaration of
the programmer's intent. This also maintains the invariant that our
inferred type for a name is always assignable to the declared type for
that same name. For example:

```py
x: str = 1
reveal_type(x)  # revealed: str
```

We weren't following this pattern for dictionary literals inferred (via
type context) as a typed dictionary; if the literal was not valid for
the annotated TypedDict type, we would just fall back to the normal
inferred type of the dict literal, effectively ignoring the annotation,
and resulting in inferred type not assignable to declared type.

## Test Plan

Added mdtest assertions.
2025-10-31 11:12:06 -04:00
chiri
9d7da914b9 Improve extend docs (#21135)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-10-31 15:10:14 +00:00
David Peter
0c2cf75869 [ty] Do not promote literals in contravariant position (#21164)
## Summary

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

## Test Plan

Regression tests
2025-10-31 16:00:30 +01:00
Ibraheem Ahmed
1d6ae8596a [ty] Prefer exact matches when solving constrained type variables (#21165)
## Summary

The solver is currently order-dependent, and will choose a supertype
over the exact type if it appears earlier in the list of constraints. We
could be smarter and try to choose the most precise subtype, but I
imagine this is something the new constraint solver will fix anyways,
and this fixes the issue showing up on
https://github.com/astral-sh/ruff/pull/21070.
2025-10-31 10:58:09 -04:00
Douglas Creager
cf4e82d4b0 [ty] Add and test when constraint sets are satisfied by their typevars (#21129)
This PR adds a new `satisfied_by_all_typevar` method, which implements
one of the final steps of actually using these dang constraint sets.
Constraint sets exist to help us check assignability and subtyping of
types in the presence of typevars. We construct a constraint set
describing the conditions under which assignability holds between the
two types. Then we check whether that constraint set is satisfied for
the valid specializations of the relevant typevars (which is this new
method).

We also add a new `ty_extensions.ConstraintSet` method so that we can
test this method's behavior in mdtests, before hooking it up to the rest
of the specialization inference machinery.
2025-10-31 10:53:37 -04:00
Ibraheem Ahmed
1baf98aab3 [ty] Fix is_disjoint_from with @final classes (#21167)
## Summary

We currently perform a subtyping check instead of the intended subclass
check (and the subtyping check is confusingly named `is_subclass_of`).
This showed up in https://github.com/astral-sh/ruff/pull/21070.
2025-10-31 14:50:54 +00:00
Carl Meyer
3179b05221 [ty] don't assume in diagnostic messages that a TypedDict key error is about subscript access (#21166)
## Summary

Before this PR, we would emit diagnostics like "Invalid key access" for
a TypedDict literal with invalid key, which doesn't make sense since
there's no "access" in that case. This PR just adjusts the wording to be
more general, and adjusts the documentation of the lint rule too.

I noticed this in the playground and thought it would be a quick fix. As
usual, it turned out to be a bit more subtle than I expected, but for
now I chose to punt on the complexity. We may ultimately want to have
different rules for invalid subscript vs invalid TypedDict literal,
because an invalid key in a TypedDict literal is low severity: it's a
typo detector, but not actually a type error. But then there's another
wrinkle there: if the TypedDict is `closed=True`, then it _is_ a type
error. So would we want to separate the open and closed cases into
separate rules, too? I decided to leave this as a question for future.

If we wanted to use separate rules, or use specific wording for each
case instead of the generalized wording I chose here, that would also
involve a bit of extra work to distinguish the cases, since we use a
generic set of functions for reporting these errors.

## Test Plan

Added and updated mdtests.
2025-10-31 10:49:59 -04:00
Aria Desires
172e8d4ae0 [ty] Support implicit imports of submodules in __init__.pyi (#20855)
This is a second take at the implicit imports approach, allowing `from .
import submodule` in an `__init__.pyi` to create the
`mypackage.submodule` attribute everyhere.

This implementation operates inside of the
available_submodule_attributes subsystem instead of as a re-export rule.

The upside of this is we are no longer purely syntactic, and absolute
from imports that happen to target submodules work (an intentional
discussed deviation from pyright which demands a relative from import).
Also we don't re-export functions or classes.

The downside(?) of this is star imports no longer see these attributes
(this may be either good or bad. I believe it's not a huge lift to make
it work with star imports but it's some non-trivial reworking).

I've also intentionally made `import mypackage.submodule` not trigger
this rule although it's trivial to change that.

I've tried to cover as many relevant cases as possible for discussion in
the new test file I've added (there are some random overlaps with
existing tests but trying to add them piecemeal felt confusing and
weird, so I just made a dedicated file for this extension to the rules).

Fixes https://github.com/astral-sh/ty/issues/133

<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

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

## Test Plan

<!-- How was it tested? -->
2025-10-31 14:29:24 +00:00
Mahmoud Saada
735ec0c1f9 [ty] Fix generic inference for non-dataclass inheriting from generic dataclass (#21159)
## Summary

Fixes https://github.com/astral-sh/ty/issues/1427

This PR fixes a regression introduced in alpha.24 where non-dataclass
children of generic dataclasses lost generic type parameter information
during `__init__` synthesis.

The issue occurred because when looking up inherited members in the MRO,
the child class's `inherited_generic_context` was correctly passed down,
but `own_synthesized_member()` (which synthesizes dataclass `__init__`
methods) didn't accept this parameter. It only used
`self.inherited_generic_context(db)`, which returned the parent's
context instead of the child's.

The fix threads the child's generic context through to the synthesis
logic, allowing proper generic type inference for inherited dataclass
constructors.

## Test Plan

- Added regression test for non-dataclass inheriting from generic
dataclass
- Verified the exact repro case from the issue now works
- All 277 mdtest tests passing
- Clippy clean
- Manually verified with Python runtime, mypy, and pyright - all accept
this code pattern

## Verification

Tested against multiple type checkers:
-  Python runtime: Code works correctly
-  mypy: No issues found
-  pyright: 0 errors, 0 warnings
-  ty alpha.23: Worked (before regression)
-  ty alpha.24: Regression
-  ty with this fix: Works correctly

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: David Peter <mail@david-peter.de>
2025-10-31 13:55:17 +01:00
Ben Beasley
3585c96ea5 Update etcetera to 0.11.0 (#21160) 2025-10-31 12:53:18 +00:00
Micha Reiser
4b026c2a55 Fix missing diagnostics for notebooks (#21156) 2025-10-31 01:16:43 +00:00
Matthew Mckee
4b758b3746 [ty] Fix tests for definition completions (#21153) 2025-10-31 00:43:50 +00:00
Amethyst Reese
8737a2d5f5 Bump v0.14.3 (#21152)
- **Upgrade to rooster==0.1.1**
- **Changelog for v0.14.3**
- **Bump v0.14.3**
2025-10-30 17:06:29 -07:00
Matthew Mckee
3be3a10a2f [ty] Don't provide completions when in class or function definition (#21146) 2025-10-30 23:19:59 +00:00
Alex Waygood
13375d0e42 [ty] Use the top materialization of classes for narrowing in class-patterns for match statements (#21150) 2025-10-30 20:44:51 +00:00
Douglas Creager
c0b04d4b7c [ty] Update "constraint implication" relation to work on constraints between two typevars (#21068)
It's possible for a constraint to mention two typevars. For instance, in
the body of

```py
def f[S: int, T: S](): ...
```

the baseline constraint set would be `(T ≤ S) ∧ (S ≤ int)`. That is, `S`
must specialize to some subtype of `int`, and `T` must specialize to a
subtype of the type that `S` specializes to.

This PR updates the new "constraint implication" relationship from
#21010 to work on these kinds of constraint sets. For instance, in the
example above, we should be able to see that `T ≤ int` must always hold:

```py
def f[S, T]():
    constraints = ConstraintSet.range(Never, S, int) & ConstraintSet.range(Never, T, S)
    static_assert(constraints.implies_subtype_of(T, int))  # now succeeds!
```

This did not require major changes to the implementation of
`implies_subtype_of`. That method already relies on how our `simplify`
and `domain` methods expand a constraint set to include the transitive
closure of the constraints that it mentions, and to mark certain
combinations of constraints as impossible. Previously, that transitive
closure logic only looked at pairs of constraints that constrain the
same typevar. (For instance, to notice that `(T ≤ bool) ∧ ¬(T ≤ int)` is
impossible.)

Now we also look at pairs of constraints that constraint different
typevars, if one of the constraints is bound by the other — that is,
pairs of the form `T ≤ S` and `S ≤ something`, or `S ≤ T` and `something
≤ S`. In those cases, transitivity lets us add a new derived constraint
that `T ≤ something` or `something ≤ T`, respectively. Having done that,
our existing `implies_subtype_of` logic finds and takes into account
that derived constraint.
2025-10-30 16:11:04 -04:00
Brent Westbrook
1c7ea690a8 [flake8-type-checking] Fix TC003 false positive with future-annotations (#21125)
Summary
--

Fixes #21121 by upgrading `RuntimeEvaluated` annotations like
`dataclasses.KW_ONLY` to `RuntimeRequired`. We already had special
handling for
`TypingOnly` annotations in this context but not `RuntimeEvaluated`.
Combining
that with the `future-annotations` setting, which allowed ignoring the
`RuntimeEvaluated` flag, led to the reported bug where we would try to
move
`KW_ONLY` into a `TYPE_CHECKING` block.

Test Plan
--

A new test based on the issue
2025-10-30 14:14:29 -04:00
Alex Waygood
9bacd19c5a [ty] Fix lookup of __new__ on instances (#21147)
## Summary

We weren't correctly modeling it as a `staticmethod` in all cases,
leading us to incorrectly infer that the `cls` argument would be bound
if it was accessed on an instance (rather than the class object).

## Test Plan

Added mdtests that fail on `main`. The primer output also looks good!
2025-10-30 13:42:46 -04:00
Brent Westbrook
f0fe6d62fb Fix syntax error false positive on nested alternative patterns (#21104)
## Summary

Fixes #21101 by storing the child visitor's names in the parent visitor.
This makes sure that `visitor.names` on line 1818 isn't empty after we
visit a nested OR pattern.

## Test Plan

New inline test cases derived from the issue,
[playground](https://play.ruff.rs/7b6439ac-ee8f-4593-9a3e-c2aa34a595d0)
2025-10-30 13:40:03 -04:00
Prakhar Pratyush
10bda3df00 [pyupgrade] Fix false positive for TypeVar with default on Python <3.13 (UP046,UP047) (#21045)
## Summary

Type default for Type parameter was added in Python 3.13 (PEP 696).

`typing_extensions.TypeVar` backports the default argument to earlier
versions.

`UP046` & `UP047` were getting triggered when
`typing_extensions.TypeVar` with `default` argument was used on python
version < 3.13

It shouldn't be triggered for python version < 3.13

This commit fixes the bug by adding a python version check before
triggering them.

Fixes #20929.

## Test Plan

### Manual testing 1

As the issue author pointed out in
https://github.com/astral-sh/ruff/issues/20929#issuecomment-3413194511,
ran the following on `main` branch:
> % cargo run -p ruff -- check ../efax/ --target-version py312
--no-cache

<details><summary>Output</summary>

```zsh
   Compiling ruff_linter v0.14.1 (/Users/prakhar/ruff/crates/ruff_linter)
   Compiling ruff v0.14.1 (/Users/prakhar/ruff/crates/ruff)
   Compiling ruff_graph v0.1.0 (/Users/prakhar/ruff/crates/ruff_graph)
   Compiling ruff_workspace v0.0.0 (/Users/prakhar/ruff/crates/ruff_workspace)
   Compiling ruff_server v0.2.2 (/Users/prakhar/ruff/crates/ruff_server)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 6.72s
     Running `target/debug/ruff check ../efax/ --target-version py312 --no-cache`
UP046 Generic class `ExpectationParametrization` uses `Generic` subclass instead of type parameters
  --> /Users/prakhar/efax/efax/_src/expectation_parametrization.py:17:48
   |
17 | class ExpectationParametrization(Distribution, Generic[NP]):
   |                                                ^^^^^^^^^^^
18 |     """The expectation parametrization of an exponential family distribution.
   |
help: Use type parameters

UP046 Generic class `ExpToNat` uses `Generic` subclass instead of type parameters
  --> /Users/prakhar/efax/efax/_src/mixins/exp_to_nat/exp_to_nat.py:27:68
   |
26 | @dataclass
27 | class ExpToNat(ExpectationParametrization[NP], SimpleDistribution, Generic[NP]):
   |                                                                    ^^^^^^^^^^^
28 |     """This mixin implements the conversion from expectation to natural parameters.
   |
help: Use type parameters

UP046 Generic class `HasEntropyEP` uses `Generic` subclass instead of type parameters
  --> /Users/prakhar/efax/efax/_src/mixins/has_entropy.py:25:20
   |
23 |                    HasEntropy,
24 |                    JaxAbstractClass,
25 |                    Generic[NP]):
   |                    ^^^^^^^^^^^
26 |     @abstract_jit
27 |     @abstractmethod
   |
help: Use type parameters

UP046 Generic class `HasEntropyNP` uses `Generic` subclass instead of type parameters
  --> /Users/prakhar/efax/efax/_src/mixins/has_entropy.py:64:20
   |
62 | class HasEntropyNP(NaturalParametrization[EP],
63 |                    HasEntropy,
64 |                    Generic[EP]):
   |                    ^^^^^^^^^^^
65 |     @jit
66 |     @final
   |
help: Use type parameters

UP046 Generic class `NaturalParametrization` uses `Generic` subclass instead of type parameters
  --> /Users/prakhar/efax/efax/_src/natural_parametrization.py:43:30
   |
41 | class NaturalParametrization(Distribution,
42 |                              JaxAbstractClass,
43 |                              Generic[EP, Domain]):
   |                              ^^^^^^^^^^^^^^^^^^^
44 |     """The natural parametrization of an exponential family distribution.
   |
help: Use type parameters

UP046 Generic class `Structure` uses `Generic` subclass instead of type parameters
  --> /Users/prakhar/efax/efax/_src/structure/structure.py:31:17
   |
30 | @dataclass
31 | class Structure(Generic[P]):
   |                 ^^^^^^^^^^
32 |     """This class generalizes the notion of type for Distribution objects.
   |
help: Use type parameters

UP046 Generic class `DistributionInfo` uses `Generic` subclass instead of type parameters
  --> /Users/prakhar/efax/tests/distribution_info.py:20:24
   |
20 | class DistributionInfo(Generic[NP, EP, Domain]):
   |                        ^^^^^^^^^^^^^^^^^^^^^^^
21 |     def __init__(self, dimensions: int = 1, safety: float = 0.0) -> None:
22 |         super().__init__()
   |
help: Use type parameters

Found 7 errors.
No fixes available (7 hidden fixes can be enabled with the `--unsafe-fixes` option).
```
</details> 

Running it after the changes:
```zsh
ruff % cargo run -p ruff -- check ../efax/ --target-version py312 --no-cache
   Compiling ruff_linter v0.14.1 (/Users/prakhar/ruff/crates/ruff_linter)
   Compiling ruff v0.14.1 (/Users/prakhar/ruff/crates/ruff)
   Compiling ruff_graph v0.1.0 (/Users/prakhar/ruff/crates/ruff_graph)
   Compiling ruff_workspace v0.0.0 (/Users/prakhar/ruff/crates/ruff_workspace)
   Compiling ruff_server v0.2.2 (/Users/prakhar/ruff/crates/ruff_server)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 7.86s
     Running `target/debug/ruff check ../efax/ --target-version py312 --no-cache`
All checks passed!
```

---

### Manual testing 2

Ran the check on the following script (mainly to verify `UP047`):
```py
from __future__ import annotations                                                                                                                                                    

from typing import Generic

from typing_extensions import TypeVar

T = TypeVar("T", default=int)


def generic_function(var: T) -> T:
    return var


Q = TypeVar("Q", default=str)


class GenericClass(Generic[Q]):
    var: Q
```

On `main` branch:
> ruff % cargo run -p ruff -- check ~/up046.py --target-version py312
--preview --no-cache

<details><summary>Output</summary>

```zsh
   Compiling ruff_linter v0.14.1 (/Users/prakhar/ruff/crates/ruff_linter)
   Compiling ruff v0.14.1 (/Users/prakhar/ruff/crates/ruff)
   Compiling ruff_graph v0.1.0 (/Users/prakhar/ruff/crates/ruff_graph)
   Compiling ruff_workspace v0.0.0 (/Users/prakhar/ruff/crates/ruff_workspace)
   Compiling ruff_server v0.2.2 (/Users/prakhar/ruff/crates/ruff_server)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 7.43s
     Running `target/debug/ruff check /Users/prakhar/up046.py --target-version py312 --preview --no-cache`
UP047 Generic function `generic_function` should use type parameters
  --> /Users/prakhar/up046.py:10:5
   |
10 | def generic_function(var: T) -> T:
   |     ^^^^^^^^^^^^^^^^^^^^^^^^
11 |     return var
   |
help: Use type parameters

UP046 Generic class `GenericClass` uses `Generic` subclass instead of type parameters
  --> /Users/prakhar/up046.py:17:20
   |
17 | class GenericClass(Generic[Q]):
   |                    ^^^^^^^^^^
18 |     var: Q
   |
help: Use type parameters

Found 2 errors.
No fixes available (2 hidden fixes can be enabled with the `--unsafe-fixes` option).
```

</details> 

After the fix (this branch):
```zsh
ruff % cargo run -p ruff -- check ~/up046.py --target-version py312 --preview --no-cache
   Compiling ruff_linter v0.14.1 (/Users/prakhar/ruff/crates/ruff_linter)
   Compiling ruff v0.14.1 (/Users/prakhar/ruff/crates/ruff)
   Compiling ruff_graph v0.1.0 (/Users/prakhar/ruff/crates/ruff_graph)
   Compiling ruff_workspace v0.0.0 (/Users/prakhar/ruff/crates/ruff_workspace)
   Compiling ruff_server v0.2.2 (/Users/prakhar/ruff/crates/ruff_server)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 7.40s
     Running `target/debug/ruff check /Users/prakhar/up046.py --target-version py312 --preview --no-cache`
All checks passed!
```

Signed-off-by: Prakhar Pratyush <prakhar1144@gmail.com>
2025-10-30 12:59:07 -04:00
David Peter
e55bc943e5 [ty] Reachability and narrowing for enum methods (#21130)
## Summary

Adds proper type narrowing and reachability analysis for matching on
non-inferable type variables bound to enums. For example:

```py
from enum import Enum

class Answer(Enum):
    NO = 0
    YES = 1

    def is_yes(self) -> bool:  # no error here!
        match self:
            case Answer.YES:
                return True
            case Answer.NO:
                return False
```

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

## Test Plan

Added regression tests
2025-10-30 15:38:57 +01:00
David Peter
1b0ee4677e [ty] Use range instead of custom IntIterable (#21138)
## Summary

We previously didn't understand `range` and wrote these custom
`IntIterable`/`IntIterator` classes for tests. We can now remove them
and make the tests shorter in some places.
2025-10-30 15:21:55 +01:00
Dan Parizher
1ebedf6df5 [ruff] Add support for additional eager conversion patterns (RUF065) (#20657)
## Summary

Fixes #20583
2025-10-29 21:45:08 +00:00
Dan Parizher
980b4c55b2 [ruff-ecosystem] Fix CLI crash on Python 3.14 (#21092) 2025-10-29 21:37:39 +00:00
David Peter
5139f76d1f [ty] Infer type of self for decorated methods and properties (#21123)
## Summary

Infer a type of unannotated `self` parameters in decorated methods /
properties.

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

## Test Plan

Existing tests, some new tests.
2025-10-29 21:22:38 +00:00
Jonas Vacek
aca8ba76a4 [flake8-bandit] Fix correct example for S308 (#21128)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary
Fixed the incorrect import example in the "correct exmaple"
<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan
🤷 
<!-- How was it tested? -->
2025-10-29 15:03:56 -04:00
Matthew Mckee
7045898ffa [ty] Dont provide goto definition for definitions which are not reexported in builtins (#21127) 2025-10-29 18:39:36 +00:00
Wei Lee
d38a5292d2 [airflow] warning airflow....DAG.create_dagrun has been removed (AIR301) (#21093) 2025-10-29 14:57:37 +00:00
Shunsuke Shibayama
83a00c0ac8 [ty] follow the breaking API changes made in salsa-rs/salsa#1015 (#21117) 2025-10-29 14:56:12 +00:00
Alex Waygood
8b22fd1a5f [ty] Rename Type::into_nominal_instance (#21124) 2025-10-29 10:18:33 -04:00
Andrew Gallant
765257bdce [ty] Filter out "unimported" from the current module
Note that this doesn't change the evaluation results unfortunately.
In particular, prior to this fix, the correct result was ranked above
the redundant result. Our MRR-based evaluation doesn't care about
anything below the rank of the correct answer, and so this change isn't
reflected in our evaluation.

Fixes astral-sh/ty#1445
2025-10-29 09:13:49 -04:00
Andrew Gallant
2d4e0edee4 [ty] Add evaluation test for auto-import including symbols in current module
This shouldn't happen. And indeed, currently, this results in a
sub-optimal ranking.
2025-10-29 09:13:49 -04:00
Andrew Gallant
9ce3fa3fe3 [ty] Refactor ty_ide completion tests
The status quo grew organically and didn't do well when one wanted to
mix and match different settings to generate a snapshot.

This does a small refactor to use more of a builder to generate
snapshots.
2025-10-29 09:13:49 -04:00
Andrew Gallant
196a68e4c8 [ty] Render import <...> in completions when "label details" isn't supported
This fixes a bug where the `import module` part of a completion for
unimported candidates would be missing. This makes it especially
confusing because the user can't tell where the symbol is coming from,
and there is no hint that an `import` statement will be inserted.

Previously, we were using [`CompletionItemLabelDetails`] to render the
`import module` part of the suggestion. But this is only supported in
clients that support version 3.17 (or newer) of the LSP specification.
It turns out that this support isn't widespread yet. In particular,
Heliex doesn't seem to support "label details."

To fix this, we take a [cue from rust-analyzer][rust-analyzer-details].
We detect if the client supports "label details," and if so, use it.
Otherwise, we push the `import module` text into the completion label
itself.

Fixes https://github.com/astral-sh/ruff/pull/20439#issuecomment-3313689568

[`CompletionItemLabelDetails`]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#completionItemLabelDetails
[rust-analyzer-details]: 5d905576d4/crates/rust-analyzer/src/lsp/to_proto.rs (L391-L404)
2025-10-29 08:50:41 -04:00
Dan Parizher
349061117c [refurb] Preserve digit separators in Decimal constructor (FURB157) (#20588)
## Summary

Fixes #20572

---------

Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2025-10-28 17:47:52 -04:00
Takayuki Maeda
d0aebaa253 [ISC001] fix panic when string literals are unclosed (#21034)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-10-28 19:14:58 +00:00
Douglas Creager
17850eee4b [ty] Reformat constraint set mdtests (#21111)
This PR updates the mdtests that test how our generics solver interacts
with our new constraint set implementation. Because the rendering of a
constraint set can get long, this standardizes on putting the `revealed`
assertion on a separate line. We also add a `static_assert` test for
each constraint set to verify that they are all coerced into simple
`bool`s correctly.

This is a pure reformatting (not even a refactoring!) that changes no
behavior. I've pulled it out of #20093 to reduce the amount of effort
that will be required to review that PR.
2025-10-28 14:59:49 -04:00
Douglas Creager
4d2ee41e24 [ty] Move constraint set mdtest functions into ConstraintSet class (#21108)
We have several functions in `ty_extensions` for testing our constraint
set implementation. This PR refactors those functions so that they are
all methods of the `ConstraintSet` class, rather than being standalone
top-level functions. 🎩 to @sharkdp for pointing out that
`KnownBoundMethod` gives us what we need to implement that!
2025-10-28 14:32:41 -04:00
Takayuki Maeda
7b959ef44b Avoid sending an unnecessary "clear diagnostics" message for clients supporting pull diagnostics (#21105) 2025-10-28 18:24:35 +00:00
renovate[bot]
4c4ddc8c29 Update Rust crate ignore to v0.4.24 (#20979)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-10-28 17:49:26 +00:00
Micha Reiser
ae0343f848 [ty] Rename inner query for better debugging experience (#21106) 2025-10-28 11:26:05 +00:00
Douglas Creager
29462ea1d4 [ty] Add new "constraint implication" typing relation (#21010)
This PR adds the new **_constraint implication_** relationship between
types, aka `is_subtype_of_given`, which tests whether one type is a
subtype of another _assuming that the constraints in a particular
constraint set hold_.

For concrete types, constraint implication is exactly the same as
subtyping. (A concrete type is any fully static type that is not a
typevar. It can _contain_ a typevar, though — `list[T]` is considered
concrete.)

The interesting case is typevars. The other typing relationships (TODO:
will) all "punt" on the question when considering a typevar, by
translating the desired relationship into a constraint set. At some
point, though, we need to resolve a constraint set; at that point, we
can no longer punt on the question. Unlike with concrete types, the
answer will depend on the constraint set that we are considering.
2025-10-27 22:01:08 -04:00
Bhuminjay Soni
7fee62b2de [semantic error tests]: refactor semantic error tests to separate files (#20926)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

<!-- What's the purpose of the change? What does it do, and why? -->
This PR refactors semantic error tests in each seperate file


## Test Plan

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

## CC
- @ntBre

---------

Signed-off-by: 11happy <soni5happy@gmail.com>
Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2025-10-27 21:18:11 +00:00
Brent Westbrook
96b60c11d9 Respect --output-format with --watch (#21097)
Summary
--

Fixes #19550

This PR copies our non-watch diagnostic rendering code into
`Printer::write_continuously` in preview mode, allowing it to use
whatever output format is passed in.

I initially marked this as also fixing #19552, but I guess that's not
true currently but will be true once this is stabilized and we can
remove the warning.

Test Plan
--

Existing tests, but I don't think we have any `watch` tests, so some
manual testing as well. The default with just `ruff check --watch` is
still `concise`, adding just `--preview` still gives the `full` output,
and then specifying any other output format works, with JSON as one
example:

<img width="695" height="719" alt="Screenshot 2025-10-27 at 9 21 41 AM"
src="https://github.com/user-attachments/assets/98957911-d216-4fc4-8b6c-22c56c963b3f"
/>
2025-10-27 12:04:55 -04:00
Dylan
fffbe5a879 [pyflakes] Revert to stable behavior if imports for module lie in alternate branches for F401 (#20878)
Closes #20839
2025-10-27 10:23:36 -05:00
Dylan
116611bd39 Fix finding keyword range for clause header after statement ending with semicolon (#21067)
When formatting clause headers for clauses that are not their own node,
like an `else` clause or `finally` clause, we begin searching for the
keyword at the end of the previous statement. However, if the previous
statement ended in a semicolon this caused a panic because we only
expected trivia between the end of the last statement and the keyword.

This PR adjusts the starting point of our search for the keyword to
begin after the optional semicolon in these cases.

Closes #21065
2025-10-27 09:52:17 -05:00
Alex Waygood
db0e921db1 [ty] Fix bug where ty would think all types had an __mro__ attribute (#20995) 2025-10-27 11:19:12 +00:00
Micha Reiser
3c7f56f582 Restore indent.py (#21094) 2025-10-27 10:34:29 +00:00
Dan Parizher
8a73519b25 [flake8-django] Apply DJ001 to annotated fields (#20907) 2025-10-27 09:19:15 +01:00
Shahar Naveh
fa12fd0184 Clearer error message when line-length goes beyond threshold (#21072)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-10-27 07:42:48 +00:00
renovate[bot]
fdb8ea487c Update upload and download artifacts github actions (#21083)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-10-27 08:29:32 +01:00
renovate[bot]
d846a0319a Update dependency mdformat-mkdocs to v4.4.2 (#21088)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-27 07:47:46 +01:00
renovate[bot]
bca5d33385 Update cargo-bins/cargo-binstall action to v1.15.9 (#21086)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-27 07:47:12 +01:00
renovate[bot]
c83c4d52a4 Update Rust crate clap to v4.5.50 (#21090)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-27 07:46:38 +01:00
renovate[bot]
e692b7f1ee Update Rust crate get-size2 to v0.7.1 (#21091)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-27 07:44:02 +01:00
renovate[bot]
8e51db3ecd Update Rust crate bstr to v1.12.1 (#21089)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-27 07:43:39 +01:00
Auguste Lalande
64ab79e572 Add missing docstring sections to the numpy list (#20931)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

Add docstring sections which were missing from the numpy list as pointed
out here #20923. For now these are only the official sections as
documented
[here](https://numpydoc.readthedocs.io/en/latest/format.html#sections).

## Test Plan

Added a test case for DOC102
2025-10-24 17:19:30 -04:00
Dan Parizher
1ade9a5943 [pydoclint] Fix false positive on explicit exception re-raising (DOC501, DOC502) (#21011)
## Summary
Fixes #20973 (`docstring-extraneous-exception`) false positive when
exceptions mentioned in docstrings are caught and explicitly re-raised
using `raise e` or `raise e from None`.

## Problem Analysis
The DOC502 rule was incorrectly flagging exceptions mentioned in
docstrings as "not explicitly raised" when they were actually being
explicitly re-raised through exception variables bound in `except`
clauses.

**Root Cause**: The `BodyVisitor` in `check_docstring.rs` only checked
for direct exception references (like `raise OSError()`) but didn't
recognize when a variable bound to an exception in an `except` clause
was being re-raised.

**Example of the bug**:
```python
def f():
    """Do nothing.

    Raises
    ------
    OSError
        If the OS errors.
    """
    try:
        pass
    except OSError as e:
        raise e  # This was incorrectly flagged as not explicitly raising OSError
```

The issue occurred because `resolve_qualified_name(e)` couldn't resolve
the variable `e` to a qualified exception name, since `e` is just a
variable binding, not a direct reference to an exception class.

## Approach
Modified the `BodyVisitor` in
`crates/ruff_linter/src/rules/pydoclint/rules/check_docstring.rs` to:

1. **Track exception variable bindings**: Added `exception_variables`
field to map exception variable names to their exception types within
`except` clauses
2. **Enhanced raise statement detection**: Updated `visit_stmt` to check
if a `raise` statement uses a variable name that's bound to an exception
in the current `except` clause
3. **Proper scope management**: Clear exception variable mappings when
leaving `except` handlers to prevent cross-contamination

**Key changes**:
- Added `exception_variables: FxHashMap<&'a str, QualifiedName<'a>>` to
track variable-to-exception mappings
- Enhanced `visit_except_handler` to store exception variable bindings
when entering `except` clauses
- Modified `visit_stmt` to check for variable-based re-raising: `raise
e` → lookup `e` in `exception_variables`
- Clear mappings when exiting `except` handlers to maintain proper scope

---------

Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2025-10-24 16:54:09 -04:00
Ibraheem Ahmed
304ac22e74 [ty] Use constructor parameter types as type context (#21054)
## Summary

Resolves https://github.com/astral-sh/ty/issues/1408.
2025-10-24 20:14:18 +00:00
Douglas Creager
c3de8847d5 [ty] Consider domain of BDD when checking whether always satisfiable (#21050)
That PR title might be a bit inscrutable.

Consider the two constraints `T ≤ bool` and `T ≤ int`. Since `bool ≤
int`, by transitivity `T ≤ bool` implies `T ≤ int`. (Every type that is
a subtype of `bool` is necessarily also a subtype of `int`.) That means
that `T ≤ bool ∧ T ≰ int` is an impossible combination of constraints,
and is therefore not a valid input to any BDD. We say that that
assignment is not in the _domain_ of the BDD.

The implication `T ≤ bool → T ≤ int` can be rewritten as `T ≰ bool ∨ T ≤
int`. (That's the definition of implication.) If we construct that
constraint set in an mdtest, we should get a constraint set that is
always satisfiable. Previously, that constraint set would correctly
_display_ as `always`, but a `static_assert` on it would fail.

The underlying cause is that our `is_always_satisfied` method would only
test if the BDD was the `AlwaysTrue` terminal node. `T ≰ bool ∨ T ≤ int`
does not simplify that far, because we purposefully keep around those
constraints in the BDD structure so that it's easier to compare against
other BDDs that reference those constraints.

To fix this, we need a more nuanced definition of "always satisfied".
Instead of evaluating to `true` for _every_ input, we only need it to
evaluate to `true` for every _valid_ input — that is, every input in its
domain.
2025-10-24 13:37:56 -04:00
Ibraheem Ahmed
f17ddd62ad [ty] Avoid duplicate diagnostics during multi-inference of standalone expressions (#21056)
## Summary

Resolves https://github.com/astral-sh/ty/issues/1428.
2025-10-24 13:21:39 -04:00
Micha Reiser
adbf05802a [ty] Fix rare panic with highly cyclic TypeVar definitions (#21059) 2025-10-24 18:30:54 +02:00
Micha Reiser
eb8c0ad87c [ty] Add --no-progress option (#21063) 2025-10-24 18:00:00 +02:00
Shahar Naveh
a2d0d39853 Configurable "unparse mode" for ruff_python_codegen::Generator (#21041)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-10-24 15:44:48 +00:00
Micha Reiser
f36fa7d6c1 [ty] Fix missing newline before first diagnostic (#21058) 2025-10-24 17:35:23 +02:00
William Woodruff
6f0982d2d6 chore: bump zizmor (#21064) 2025-10-24 10:58:23 -04:00
Dan Parizher
3e8685d2ec [pyflakes] Fix false positive for __class__ in lambda expressions within class definitions (F821) (#20564)
## Summary

Fixes #20562

---------

Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2025-10-24 10:07:19 -04:00
Dan Parizher
7576669297 [flake8-pyi] Fix PYI034 to not trigger on metaclasses (PYI034) (#20881)
## Summary

Fixes #20781

---------

Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
2025-10-24 13:40:26 +00:00
Alex Waygood
bf74c824eb [ty] Delegate truthiness inference of an enum Literal type to its enum-instance supertype (#21060) 2025-10-24 14:34:16 +01:00
Alex Waygood
e196c2ab37 [ty] Consider __len__ when determining the truthiness of an instance of a tuple class or a @final class (#21049) 2025-10-24 09:29:55 +00:00
Micha Reiser
4522f35ea7 [ty] Add comment explaining why HasTrackedScope is implemented for Identifier and why it works (#21057) 2025-10-24 09:48:57 +02:00
Micha Reiser
be5a62f7e5 [ty] Timeout based workspace diagnostic progress reports (#21019) 2025-10-24 09:06:19 +02:00
wangxiaolei
28aed61a22 [pylint] Implement stop-iteration-return (PLR1708) (#20733)
## Summary

implement pylint rule stop-iteration-return / R1708

## Test Plan

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

---------

Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2025-10-23 15:02:41 -07:00
Wei Lee
05cde8bd19 [airflow] Extend airflow.models..Param check (AIR311) (#21043)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

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

* Extend `airflow.models.Param` to include `airflow.models.param.Param`
case and include both `airflow.models.param.ParamDict` and
`airflow.models.param.DagParam` and their `airflow.models.` counter part

## Test Plan

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

update the text fixture accordingly and reorganize them in the third
commit
2025-10-23 17:12:52 -04:00
Brent Westbrook
83a3bc4ee9 Bump 0.14.2 (#21051) 2025-10-23 15:17:22 -04:00
Brent Westbrook
155fd603e8 Document when a rule was added (#21035)
Summary
--

Inspired by #20859, this PR adds the version a rule was added, and the
file and line where it was defined, to `ViolationMetadata`. The file and
line just use the standard `file!` and `line!` macros, while the more
interesting version field uses a new `violation_metadata` attribute
parsed by our `ViolationMetadata` derive macro.

I moved the commit modifying all of the rule files to the end, so it
should be a lot easier to review by omitting that one.

As a curiosity and a bit of a sanity check, I also plotted the rule
numbers over time:

<img width="640" height="480" alt="image"
src="https://github.com/user-attachments/assets/75b0b5cc-3521-4d40-a395-8807e6f4925f"
/>

I think this looks pretty reasonable and avoids some of the artifacts
the earlier versions of the script ran into, such as the `rule`
sub-command not being available or `--explain` requiring a file
argument.

<details><summary>Script and summary data</summary>

```shell
gawk --csv '
NR > 1 {
    split($2, a, ".")
    major = a[1]; minor = a[2]; micro = a[3]
    # sum the number of rules added per minor version
    versions[minor] += 1
}
END {
    tot = 0
    for (i = 0; i <= 14; i++) {
        tot += versions[i]
        print i, tot
    }
}
' ruff_rules_metadata.csv > summary.dat
```

```
0 696
1 768
2 778
3 803
4 822
5 848
6 855
7 865
8 893
9 915
10 916
11 924
12 929
13 932
14 933
```

</details>

Test Plan
--

I built and viewed the documentation locally, and it looks pretty good!

<img width="1466" height="676" alt="image"
src="https://github.com/user-attachments/assets/5e227df4-7294-4d12-bdaa-31cac4e9ad5c"
/>

The spacing seems a bit awkward following the `h1` at the top, so I'm
wondering if this might look nicer as a footer in Ruff. The links work
well too:
- [v0.0.271](https://github.com/astral-sh/ruff/releases/tag/v0.0.271)
- [Related
issues](https://github.com/astral-sh/ruff/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20airflow-variable-name-task-id-mismatch)
- [View
source](https://github.com/astral-sh/ruff/blob/main/crates%2Fruff_linter%2Fsrc%2Frules%2Fairflow%2Frules%2Ftask_variable_name.rs#L34)

The last one even works on `main` now since it points to the
`derive(ViolationMetadata)` line.

In terms of binary size, this branch is a bit bigger than main with
38,654,520 bytes compared to 38,635,728 (+20 KB). I guess that's not
_too_ much of an increase, but I wanted to check since we're generating
a lot more code with macros.

---------

Co-authored-by: GiGaGon <107241144+MeGaGiGaGon@users.noreply.github.com>
2025-10-23 14:48:41 -04:00
Shunsuke Shibayama
48f1771877 [ty] fix infinite recursion with generic type aliases (#20969)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-10-23 14:14:30 +00:00
decorator-factory
4ca74593dd [ty] Consider type_check_only when ranking completions (#20910) 2025-10-23 15:09:13 +01:00
Alex Waygood
dab3d4e917 [ty] Improve invalid-argument-type diagnostics where a union type was provided (#21044) 2025-10-23 13:16:21 +00:00
Micha Reiser
01695513ce Disable npm caching for playground (#21039) 2025-10-23 11:51:29 +02:00
Micha Reiser
e92fd51a2c [ty] Add cycle handling to lazy_default (#20967) 2025-10-23 10:05:08 +02:00
Eric Mark Martin
c3631c78bd [ty] Add docstrings for ty_extensions functions (#21036)
Co-authored-by: David Peter <sharkdp@users.noreply.github.com>
2025-10-23 09:50:21 +02:00
David Peter
589e8ac0d9 [ty] Infer type for implicit self parameters in method bodies (#20922)
## Summary

Infer a type of `Self` for unannotated `self` parameters in methods of
classes.

part of https://github.com/astral-sh/ty/issues/159

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

## Conformance tests changes

```diff
+enums_member_values.py:85:9: error[invalid-assignment] Object of type `int` is not assignable to attribute `_value_` of type `str`
```
A true positive ✔️ 

```diff
-generics_self_advanced.py:35:9: error[type-assertion-failure] Argument does not have asserted type `Self@method2`
-generics_self_basic.py:14:9: error[type-assertion-failure] Argument does not have asserted type `Self@set_scale
```

Two false positives going away ✔️ 

```diff
+generics_syntax_infer_variance.py:82:9: error[invalid-assignment] Cannot assign to final attribute `x` on type `Self@__init__`
```

This looks like a true positive to me, even if it's not marked with `#
E` ✔️

```diff
+protocols_explicit.py:56:9: error[invalid-assignment] Object of type `tuple[int, int, str]` is not assignable to attribute `rgb` of type `tuple[int, int, int]`
```

True positive ✔️ 

```
+protocols_explicit.py:85:9: error[invalid-attribute-access] Cannot assign to ClassVar `cm1` from an instance of type `Self@__init__`
```

This looks like a true positive to me, even if it's not marked with `#
E`. But this is consistent with our understanding of `ClassVar`, I
think. ✔️

```py
+qualifiers_final_annotation.py:52:9: error[invalid-assignment] Cannot assign to final attribute `ID4` on type `Self@__init__`
+qualifiers_final_annotation.py:65:9: error[invalid-assignment] Cannot assign to final attribute `ID7` on type `Self@method1`
```

New true positives ✔️ 

```py
+qualifiers_final_annotation.py:52:9: error[invalid-assignment] Cannot assign to final attribute `ID4` on type `Self@__init__`
+qualifiers_final_annotation.py:57:13: error[invalid-assignment] Cannot assign to final attribute `ID6` on type `Self@__init__`
+qualifiers_final_annotation.py:59:13: error[invalid-assignment] Cannot assign to final attribute `ID6` on type `Self@__init__`
```

This is a new false positive, but that's a pre-existing issue on main
(if you annotate with `Self`):
https://play.ty.dev/3ee1c56d-7e13-43bb-811a-7a81e236e6ab  => reported
as https://github.com/astral-sh/ty/issues/1409

## Ecosystem

* There are 5931 new `unresolved-attribute` and 3292 new
`possibly-missing-attribute` attribute errors, way too many to look at
all of them. I randomly sampled 15 of these errors and found:
* 13 instances where there was simply no such attribute that we could
plausibly see. Sometimes [I didn't find it
anywhere](8644d886c6/openlibrary/plugins/openlibrary/tests/test_listapi.py (L33)).
Sometimes it was set externally on the object. Sometimes there was some
[`setattr` dynamicness going
on](a49f6b927d/setuptools/wheel.py (L88-L94)).
I would consider all of them to be true positives.
* 1 instance where [attribute was set on `obj` in
`__new__`](9e87b44fd4/sympy/tensor/array/array_comprehension.py (L45C1-L45C36)),
which we don't support yet
  * 1 instance [where the attribute was defined via `__slots__`

](e250ec0fc8/lib/spack/spack/vendor/pyrsistent/_pdeque.py (L48C5-L48C14))
* I see 44 instances [of the false positive
above](https://github.com/astral-sh/ty/issues/1409) with `Final`
instance attributes being set in `__init__`. I don't think this should
block this PR.

## Test Plan

New Markdown tests.

---------

Co-authored-by: Shaygan Hooshyari <sh.hooshyari@gmail.com>
2025-10-23 09:34:39 +02:00
Micha Reiser
76a55314e4 Fix rare multithreaded related hang (#21038) 2025-10-23 09:25:16 +02:00
Takayuki Maeda
6c18f18450 [ruff] Fix UP032 conversion for decimal ints with underscores (#21022)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

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

Fixes #21017

Taught UP032’s parenthesize check to ignore underscores when inspecting
decimal integer literals so the converter emits `f"{(1_2).real}"`
instead of invalid syntax.

## Test Plan

Added test cases to UP032_2.py.

<!-- How was it tested? -->
2025-10-22 18:11:50 -04:00
William Woodruff
7ba176d395 ci: adjust zizmor config, bump dist (#20999)
## Summary

Also bumps `cargo dist` to 0.30, and moves us
back to the upstream copy of `dist` now that
the latest version has integrated our fork's
patches.

## Test Plan

See what happens in CI 🙂

---------

Signed-off-by: William Woodruff <william@astral.sh>
2025-10-22 17:48:17 -04:00
Douglas Creager
766ed5b5f3 [ty] Some more simplifications when rendering constraint sets (#21009)
This PR adds another useful simplification when rendering constraint
sets: `T = int` instead of `T = int ∧ T ≠ str`. (The "smaller"
constraint `T = int` implies the "larger" constraint `T ≠ str`.
Constraint set clauses are intersections, and if one constraint in a
clause implies another, we can throw away the "larger" constraint.)

While we're here, we also normalize the bounds of a constraint, so that
we equate e.g. `T ≤ int | str` with `T ≤ str | int`, and change the
ordering of BDD variables so that all constraints with the same typevar
are ordered adjacent to each other.

Lastly, we also add a new `display_graph` helper method that prints out
the full graph structure of a BDD.

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-10-22 13:38:44 -04:00
David Peter
81c1d36088 [ty] Make attributes.md mdtests faster (#21030)
## Summary

That example was too extreme for debug mode.
2025-10-22 16:40:58 +02:00
Alex Waygood
20510e1d71 [ty] Set INSTA_FORCE_PASS and INSTA_OUTPUT environment variables from mdtest.py (#21029) 2025-10-22 15:32:14 +01:00
David Peter
58a68f1bbd [ty] Fall back to Divergent for deeply nested specializations (#20988)
## Summary

Fall back to `C[Divergent]` if we are trying to specialize `C[T]` with a
type that itself already contains deeply nested specialized generic
classes. This is a way to prevent infinite recursion for cases like
`self.x = [self.x]` where type inference for the implicit instance
attribute would not converge.

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

## Test Plan

Regression tests.
2025-10-22 14:29:10 +02:00
Takayuki Maeda
2c9433796a [ruff] Autogenerate TypeParam nodes (#21028) 2025-10-22 14:06:24 +02:00
Alex Waygood
40148d7b11 [ty] Add assertions to ensure that we never call KnownClass::Tuple.to_instance() or similar (#21027) 2025-10-22 11:07:01 +00:00
Takayuki Maeda
6271fba1e1 [ruff] Auto generate ast Pattern nodes (#21024) 2025-10-22 08:24:34 +02:00
Takayuki Maeda
a51a0f16e4 [flake8-simplify] Skip SIM911 when unknown arguments are present (#20697)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

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

Fixes #18778

Prevent SIM911 from triggering when zip() is called on .keys()/.values()
that take any positional or keyword arguments, so Ruff
never suggests the lossy rewrite.

## Test Plan

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

Added a test case to SIM911.py.
2025-10-21 16:58:48 -04:00
Brent Westbrook
4b0fa5f270 Render a diagnostic for syntax errors introduced in formatter tests (#21021)
## Summary

I spun this out from #21005 because I thought it might be helpful
separately. It just renders a nice `Diagnostic` for syntax errors
pointing to the source of the error. This seemed a bit more helpful to
me than just the byte offset when working on #21005, and we had most of
the code around after #20443 anyway.

## Test Plan

This doesn't actually affect any passing tests, but here's an example of
the additional output I got when I broke the spacing after the `in`
token:

```
    error[internal-error]: Expected 'in', found name
      --> /home/brent/astral/ruff/crates/ruff_python_formatter/resources/test/fixtures/black/cases/cantfit.py:50:79
       |
    48 |     need_more_to_make_the_line_long_enough,
    49 | )
    50 | del ([], name_1, name_2), [(), [], name_4, name_3], name_1[[name_2 for name_1 inname_0]]
       |                                                                               ^^^^^^^^
    51 | del ()
       |
```

I just appended this to the other existing output for now.
2025-10-21 13:47:26 -04:00
Aria Desires
2e13b13012 [ty] Support goto-definition on vendored typeshed stubs (#21020)
This is an alternative to #21012 that more narrowly handles this logic
in the stub-mapping machinery rather than pervasively allowing us to
identify cached files as typeshed stubs. Much of the logic is the same
(pulling the logic out of ty_server so it can be reused).

I don't have a good sense for if one approach is "better" or "worse" in
terms of like, semantics and Weird Bugs that this can cause. This one is
just "less spooky in its broad consequences" and "less muddying of
separation of concerns" and puts the extra logic on a much colder path.
I won't be surprised if one day the previous implementation needs to be
revisited for its more sweeping effects but for now this is good.

Fixes https://github.com/astral-sh/ty/issues/1054
2025-10-21 13:38:40 -04:00
Micha Reiser
9d1ffd605c [ty] Implement go-to for binary and unary operators (#21001)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-10-21 19:25:41 +02:00
David Peter
2dbca6370b [ty] Avoid ever-growing default types (#20991)
## Summary

We currently panic in the seemingly rare case where the type of a
default value of a parameter depends on the callable itself:
```py
class C:
    def f(self: C):
        self.x = lambda a=self.x: a
```

Types of default values are only used for display reasons, and it's
unclear if we even want to track them (or if we should rather track the
actual value). So it didn't seem to me that we should spend a lot of
effort (and runtime) trying to achieve a theoretically correct type here
(which would be infinite).

Instead, we simply replace *nested* default types with `Unknown`, i.e.
only if the type of the default value is a callable itself.

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

## Test Plan

Regression tests
2025-10-21 19:13:36 +02:00
Bhuminjay Soni
3dd78e711e [syntax-errors] Name is parameter and global (#20426)
## Summary

<!-- What's the purpose of the change? What does it do, and why? -->
This PR implements a new semantic syntax error where name is parameter &
global.

## Test Plan

<!-- How was it tested? -->
I have written inline test as directed in #17412

---------

Signed-off-by: 11happy <soni5happy@gmail.com>
Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
2025-10-21 16:51:16 +00:00
David Peter
e1cada1ec3 [ty] Disable panicking mdtest (#21016)
## Summary

Only run the "pull types" test after performing the "actual" mdtest. We
observed that the order matters. There is currently one mdtest which
panics when checked in the CLI or the playground. With this change, it
also panics in the mdtest suite.

reopens https://github.com/astral-sh/ty/issues/837?
2025-10-21 14:40:23 +02:00
Micha Reiser
69ce064569 [ty] Fix completions at end of file (#20993) 2025-10-21 09:24:31 +00:00
Micha Reiser
523fc418ed [ty] Fix out-of-order semantic token for function with regular argument after kwargs (#21013) 2025-10-21 10:51:09 +02:00
Micha Reiser
24d0f65d62 [ty] Fix auto import for files with from __future__ import (#20987) 2025-10-21 08:14:39 +02:00
Hengky Kurniawan
a802d7a0ea [fastapi] Handle ellipsis defaults in FAST002 autofix (FAST002) (#20810)
## Summary

Implement handling of ellipsis (`...`) defaults in the `FAST002` autofix
to correctly differentiate between required and optional parameters in
FastAPI route definitions.

Previously, the autofix did not properly handle cases where parameters
used `...` as a default value (to indicate required parameters). This
could lead to incorrect transformations when applying the autofix.

This change updates the `FAST002` autofix logic to:
- Correctly recognize `...` as a valid FastAPI required default.
- Preserve the semantics of required parameters while still applying
other autofix improvements.
- Avoid incorrectly substituting or removing ellipsis defaults.

Fixes https://github.com/astral-sh/ruff/issues/20800

## Test Plan

Added a new test fixture at:
```crates/ruff_linter/resources/test/fixtures/fastapi/FAST002_2.py```
2025-10-21 02:47:13 +00:00
Takayuki Maeda
692b7d7f0c [ruff] Skip autofix for keyword and __debug__ path params (#20960)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

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

Fixes #20941

Skip autofix for keyword and __debug__ path params

## Test Plan

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

I added two test cases to
crates/ruff_linter/resources/test/fixtures/fastapi/FAST003.py.
2025-10-20 16:37:20 -07:00
Dylan
d363d49ab7 [flake8-bugbear] Skip B905 and B912 if <2 iterables and no starred arguments (#20998)
Closes #20997

This will _decrease_ the number of diagnostics emitted for
[zip-without-explicit-strict
(B905)](https://docs.astral.sh/ruff/rules/zip-without-explicit-strict/#zip-without-explicit-strict-b905),
since previously it triggered on any `zip` call no matter the number of
arguments. It may _increase_ the number of diagnostics for
[map-without-explicit-strict
(B912)](https://docs.astral.sh/ruff/rules/map-without-explicit-strict/#map-without-explicit-strict-b912)
since it will now trigger on a single starred argument where before it
would not. However, the latter rule is in `preview` so this is
acceptable.

Note - we do not need to make any changes to
[batched-without-explicit-strict
(B911)](https://docs.astral.sh/ruff/rules/batched-without-explicit-strict/#batched-without-explicit-strict-b911)
since that just takes a single iterable.

I am doing this in one PR rather than two because we should keep the
behavior of these rules consistent with one another.

For review: apologies for the unreadability of the snapshot for `B905`.
Unfortunately I saw no way of keeping a small diff and a correct fixture
(the fixture labeled a whole block as `# Error` whereas now several in
the block became `# Ok`).Probably simplest to just view the actual
snapshot - it's relatively small.
2025-10-20 18:35:32 -05:00
Dylan
281ae8eb34 [pyupgrade] Always parenthesize assignment expressions in fix for f-string (UP032) (#21003)
Closes #21000
2025-10-20 18:34:02 -05:00
renovate[bot]
d61902c89f Update dependency vite to v7.0.8 (#21007)
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
| [vite](https://vite.dev)
([source](https://redirect.github.com/vitejs/vite/tree/HEAD/packages/vite))
| [`7.0.7` ->
`7.0.8`](https://renovatebot.com/diffs/npm/vite/7.0.7/7.0.8) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/vite/7.0.8?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/vite/7.0.7/7.0.8?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

### GitHub Vulnerability Alerts

####
[CVE-2025-62522](https://redirect.github.com/vitejs/vite/security/advisories/GHSA-93m4-6634-74q7)

### Summary
Files denied by
[`server.fs.deny`](https://vitejs.dev/config/server-options.html#server-fs-deny)
were sent if the URL ended with `\` when the dev server is running on
Windows.

### Impact
Only apps that match the following conditions are affected:

- explicitly exposes the Vite dev server to the network (using --host or
[`server.host` config
option](https://vitejs.dev/config/server-options.html#server-host))
- running the dev server on Windows

### Details
`server.fs.deny` can contain patterns matching against files (by default
it includes `.env`, `.env.*`, `*.{crt,pem}` as such patterns). These
patterns were able to bypass by using a back slash(`\`). The root cause
is that `fs.readFile('/foo.png/')` loads `/foo.png`.

### PoC
```shell
npm create vite@latest
cd vite-project/
cat "secret" > .env
npm install
npm run dev
curl --request-target /.env\ http://localhost:5173
```
<img width="1593" height="616" alt="image"
src="https://github.com/user-attachments/assets/36212f4e-1d3c-4686-b16f-16b35ca9e175"
/>

---

### Release Notes

<details>
<summary>vitejs/vite (vite)</summary>

###
[`v7.0.8`](https://redirect.github.com/vitejs/vite/releases/tag/v7.0.8)

[Compare
Source](https://redirect.github.com/vitejs/vite/compare/v7.0.7...v7.0.8)

Please refer to
[CHANGELOG.md](https://redirect.github.com/vitejs/vite/blob/v7.0.8/packages/vite/CHANGELOG.md)
for details.

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "" (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:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNDMuMSIsInVwZGF0ZWRJblZlciI6IjQxLjE0My4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJpbnRlcm5hbCIsInNlY3VyaXR5Il19-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-20 16:22:20 -07:00
Robsdedude
511710e1ef [flake8-gettext] Resolve qualified names and built-in bindings (INT001, INT002, INT003) (#19045)
## Summary
Make rules `INT001`, `INT002`, and `INT003` also 
* trigger on qualified names when we're sure the calls are calls to the
`gettext` module. For example
  ```python
  from gettext import gettext as foo
  
foo(f"{'bar'}") # very certain that this is a call to a real `gettext`
function => worth linting
  ```
* trigger on `builtins` bindings
  ```python
  from builtins, gettext
  
  gettext.install("...")  # binds `gettext.gettext` to `builtins._`
  builtins.__dict__["_"] = ...  # also a common pattern

  _(f"{'bar'}")  # should therefore also be linted
  ```

Fixes: https://github.com/astral-sh/ruff/issues/19028

## Test Plan

Tests have been added to all three rules.

---------

Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
2025-10-20 18:24:55 -04:00
Alex Waygood
da04d92ff2 remove playground/issue.py (#21002) 2025-10-20 21:00:05 +02:00
ShalokShalom
0f21567405 Add source to testimonial (#20971)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-10-20 19:48:07 +02:00
Micha Reiser
be32f8acb1 [ty] Fix inconsistent highlighting of self (#20986) 2025-10-20 19:32:48 +02:00
Takayuki Maeda
1197035c02 [ty] Add capabilities check for clear_diagnostics (#20989) 2025-10-20 19:31:01 +02:00
Shunsuke Shibayama
a7c38eb122 [ty] display variance on hover over type variables (#20900) 2025-10-20 17:28:36 +00:00
Micha Reiser
0520d11a66 [ty] Fix None, True and False highlighting in playground (#20994) 2025-10-20 19:25:08 +02:00
David Peter
54cd9d889d [ty] Refactor: Use let-chains in a few places (#20985) 2025-10-20 18:01:37 +01:00
Alex Waygood
44e678a222 Add language: python to several pre-commit hook entries (#20992) 2025-10-20 17:32:02 +01:00
Brent Westbrook
38c074e67d Catch syntax errors in nested interpolations before Python 3.12 (#20949)
Summary
--

This PR fixes the issue I added in #20867 and noticed in #20930. Cases
like this
cause an error on any Python version:

```py
f"{1:""}"
```

which gave me a false sense of security before. Cases like this are
still
invalid only before 3.12 and weren't flagged after the changes in
#20867:

```py
f'{1: abcd "{'aa'}" }'
           # ^  reused quote
f'{1: abcd "{"\n"}" }'
            # ^  backslash
```

I didn't recognize these as nested interpolations that also need to be
checked
for invalid expressions, so filtering out the whole format spec wasn't
quite
right. And `elements.interpolations()` only iterates over the outermost 
interpolations, not the nested ones.

There's basically no code change in this PR, I just moved the existing
check
from `parse_interpolated_string`, which parses the entire string, to
`parse_interpolated_element`. This kind of seems more natural anyway and
avoids
having to try to recursively visit nested elements after the fact in
`parse_interpolated_string`. So viewing the diff with something like

```
git diff --color-moved --ignore-space-change --color-moved-ws=allow-indentation-change main
```

should make this more clear.

Test Plan
--

New tests
2025-10-20 09:03:13 -04:00
Loïc Riegel
c2ae9c7806 feat: 'ruff rule' provides more easily parsable JSON ouput (#20168) 2025-10-20 09:09:51 +02:00
renovate[bot]
5a1201b868 Update Rust crate getrandom to v0.3.4 (#20977)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-10-20 07:00:08 +00:00
Takayuki Maeda
48b50128eb [ruff] Update schemars to v1 (#20942) 2025-10-20 08:59:52 +02:00
renovate[bot]
991e8ed178 Update Rust crate serde to v1.0.228 (#20982)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-20 08:53:07 +02:00
renovate[bot]
0fc5520404 Update Rust crate regex-automata to v0.4.13 (#20981)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-20 08:45:17 +02:00
renovate[bot]
b82404f706 Update Rust crate globset to v0.4.17 (#20978)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-20 08:35:32 +02:00
renovate[bot]
4622b8f849 Update Rust crate quote to v1.0.41 (#20980)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-20 08:33:26 +02:00
renovate[bot]
bb3a88cdd5 Update Rust crate clap to v4.5.49 (#20976)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-20 08:33:09 +02:00
renovate[bot]
029a7aa200 Update Rust crate snapbox to v0.6.22 (#20983)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-20 08:32:48 +02:00
Alex Waygood
590ce9d9ed Use uv more in CI workflows (#20968)
## Summary

More dogfooding of our own tools.

I didn't touch the build-binaries workflow (it's scary) or the
publish-docs workflow (which doesn't run on PRs) or the ruff-lsp job in
the ci.yaml workflow (ruff-lsp is deprecated; it doesn't seem worth
making changes there).

## Test Plan

CI on this PR
2025-10-19 14:59:10 +01:00
Renkai Ge
0b8de723c6 Refactor format tests to use CliTest helper (#20953)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-10-19 10:25:21 +00:00
Shunsuke Shibayama
36d4b02fa9 [ty] fix non-deterministic overload function inference (#20966) 2025-10-19 12:13:10 +02:00
Alex Waygood
1f8297cfe6 [ty] Improve error messages for unresolved attribute diagnostics (#20963)
## Summary

- Type checkers (and type-checker authors) think in terms of types, but
I think most Python users think in terms of values. Rather than saying
that a _type_ `X` "has no attribute `foo`" (which I think sounds strange
to many users), say that "an object of type `X` has no attribute `foo`"
- Special-case certain types so that the diagnostic messages read more
like normal English: rather than saying "Type `<class 'Foo'>` has no
attribute `bar`" or "Object of type `<class 'Foo'>` has no attribute
`bar`", just say "Class `Foo` has no attribute `bar`"

## Test Plan

Mdtests and snapshots updated
2025-10-19 10:58:25 +01:00
Takayuki Maeda
b6b96d75eb [ruff] Use DiagnosticTag for more pyflakes and panda rules (#20801) 2025-10-19 11:07:16 +02:00
David Peter
3c229ae58a [ty] dataclass_transform: Support for fields with an alias (#20961)
## Summary

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

## Conformance tests

Two false positives removed, as expected.

## Test Plan

New Markdown tests
2025-10-18 18:20:39 +00:00
Alex Waygood
44d9063058 Reduce duplicated logic between the macOS and Windows CI jobs (#20958) 2025-10-18 18:00:55 +01:00
Alex Waygood
09306a1d69 Run py-fuzzer using Python 3.14 in CI (#20957) 2025-10-18 15:42:39 +01:00
Alex Waygood
68c1fa86c8 [ty] Fix panic when attempting to validate the members of a protocol that inherits from a protocol in another module (#20956) 2025-10-18 15:01:46 +01:00
Alex Waygood
16efe53a72 [ty] Fix panic on recursive class definitions in a stub that use constrained type variables (#20955) 2025-10-18 13:02:55 +00:00
Micha Reiser
7532155c9b [ty] Add suggestion to unknown rule diagnostics, rename unknown-rule lint to ignore-comment-unknown-rule (#20948) 2025-10-18 12:44:21 +02:00
Bhuminjay Soni
7198e53182 [syntax-errors] Alternative match patterns bind different names (#20682)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

<!-- What's the purpose of the change? What does it do, and why? -->
This PR implements semantic syntax error where alternative patterns bind
different names

## Test Plan

<!-- How was it tested? -->
I have written inline tests as directed in #17412

---------

Signed-off-by: 11happy <soni5happy@gmail.com>
Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2025-10-17 21:35:48 +00:00
Alex Waygood
8ca2b5555d Dogfood ty on py-fuzzer in CI (#20946) 2025-10-17 20:30:17 +01:00
David Peter
6d2cf3475f Only add the actual schema in schemastore PRs (#20947)
Same as https://github.com/astral-sh/ty/pull/1391:

> Last time I ran this script, due to what I assume was a `npm` version
mismatch, the `package-lock.json` file was updated while running `npm
install` in the `schemastore`. Due to the use of `git commit -a`, it was
accidentally included in the commit for the semi-automated schemastore
PR. The solution here is to only add the actual file that we want to
commit.
2025-10-17 21:14:04 +02:00
Shunsuke Shibayama
e4384fc212 [ty] impl VarianceInferable for KnownInstanceType (#20924)
## Summary

Derived from #20900

Implement `VarianceInferable` for `KnownInstanceType` (especially for
`KnownInstanceType::TypeAliasType`).

The variance of a type alias matches its value type. In normal usage,
type aliases are expanded to value types, so the variance of a type
alias can be obtained without implementing this. However, for example,
if we want to display the variance when hovering over a type alias, we
need to be able to obtain the variance of the type alias itself (cf.
#20900).

## Test Plan

I couldn't come up with a way to test this in mdtest, so I'm testing it
in a test submodule at the end of `types.rs`.
I also added a test to `mdtest/generics/pep695/variance.md`, but it
passes without the changes in this PR.
2025-10-17 21:12:19 +02:00
David Peter
6e7ff07065 [ty] Provide completions on TypeVars (#20943)
## Summary

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

## Test Plan

New snapshot tests
2025-10-17 20:05:20 +02:00
Alex Waygood
c7e2bfd759 [ty] continue and break statements outside loops are syntax errors (#20944)
Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2025-10-17 17:13:40 +00:00
Alex Waygood
c424007645 Update usage instructions and lockfile for py-fuzzer script (#20940) 2025-10-17 15:57:17 +01:00
Brent Westbrook
0115fd3757 Avoid reusing nested, interpolated quotes before Python 3.12 (#20930)
## Summary

Fixes #20774 by tracking whether an `InterpolatedStringState` element is
nested inside of another interpolated element. This feels like kind of a
naive fix, so I'm welcome to other ideas. But it resolves the problem in
the issue and clears up the syntax error in the black compatibility
test, without affecting many other cases.

The other affected case is actually interesting too because the
[input](96b156303b/crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/fstring.py (L707))
is invalid, but the previous quote selection fixed the invalid syntax:

```pycon
Python 3.11.13 (main, Sep  2 2025, 14:20:25) [Clang 20.1.4 ] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> f'{1: abcd "{'aa'}" }'  # input
  File "<stdin>", line 1
    f'{1: abcd "{'aa'}" }'
                  ^^
SyntaxError: f-string: expecting '}'
>>> f'{1: abcd "{"aa"}" }'  # old output
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: Invalid format specifier ' abcd "aa" ' for object of type 'int'
>>> f'{1: abcd "{'aa'}" }'  # new output
  File "<stdin>", line 1
    f'{1: abcd "{'aa'}" }'
                  ^^
SyntaxError: f-string: expecting '}'
```

We now preserve the invalid syntax in the input.

Unfortunately, this also seems to be another edge case I didn't consider
in https://github.com/astral-sh/ruff/pull/20867 because we don't flag
this as a syntax error after 0.14.1:

<details><summary>Shell output</summary>
<p>

```
> uvx ruff@0.14.0 check --ignore ALL --target-version py311 - <<EOF
f'{1: abcd "{'aa'}" }'
EOF
invalid-syntax: Cannot reuse outer quote character in f-strings on Python 3.11 (syntax was added in Python 3.12)
 --> -:1:14
  |
1 | f'{1: abcd "{'aa'}" }'
  |              ^
  |

Found 1 error.
> uvx ruff@0.14.1 check --ignore ALL --target-version py311 - <<EOF
f'{1: abcd "{'aa'}" }'
EOF
All checks passed!
> uvx python@3.11 -m ast <<EOF
f'{1: abcd "{'aa'}" }'
EOF
Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "/home/brent/.local/share/uv/python/cpython-3.11.13-linux-x86_64-gnu/lib/python3.11/ast.py", line 1752, in <module>
    main()
  File "/home/brent/.local/share/uv/python/cpython-3.11.13-linux-x86_64-gnu/lib/python3.11/ast.py", line 1748, in main
    tree = parse(source, args.infile.name, args.mode, type_comments=args.no_type_comments)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/brent/.local/share/uv/python/cpython-3.11.13-linux-x86_64-gnu/lib/python3.11/ast.py", line 50, in parse
    return compile(source, filename, mode, flags,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<stdin>", line 1
    f'{1: abcd "{'aa'}" }'
                  ^^
SyntaxError: f-string: expecting '}'
```

</p>
</details> 


I assumed that was the same `ParseError` as the one caused by
`f"{1:""}"`, but this is a nested interpolation inside of the format
spec.

## Test Plan

New test copied from the black compatibility test. I guess this is a
duplicate now, I started working on this branch before the new black
tests were imported, so I could delete the separate test in our fixtures
if that's preferable.
2025-10-17 08:49:16 -04:00
David Peter
cfbd42c22a [ty] Support dataclass_transform for base class models (#20783)
## Summary

Support `dataclass_transform` when used on a (base) class.

## Typing conformance

* The changes in `dataclasses_transform_class.py` look good, just a few
mistakes due to missing `alias` support.
* I didn't look closely at the changes in
`dataclasses_transform_converter.py` since we don't support `converter`
yet.

## Ecosystem impact

The impact looks huge, but it's concentrated on a single project (ibis).
Their setup looks more or less like this:

* the real `Annotatable`:
d7083c2c96/ibis/common/grounds.py (L100-L101)
* the real `DataType`:
d7083c2c96/ibis/expr/datatypes/core.py (L161-L179)
* the real `Array`:
d7083c2c96/ibis/expr/datatypes/core.py (L1003-L1006)


```py
from typing import dataclass_transform

@dataclass_transform()
class Annotatable:
    pass

class DataType(Annotatable):
    nullable: bool = True

class Array[T](DataType):
    value_type: T
```

They expect something like `Array([1, 2])` to work, but ty, pyright,
mypy, and pyrefly would all expect there to be a first argument for the
`nullable` field on `DataType`. I don't really understand on what
grounds they expect the `nullable` field to be excluded from the
signature, but this seems to be the main reason for the new diagnostics
here. Not sure if related, but it looks like their typing setup is not
really complete
(https://github.com/ibis-project/ibis/issues/6844#issuecomment-1868274770,
this thread also mentions `dataclass_transform`).

## Test Plan

Update pre-existing tests.
2025-10-17 14:04:31 +02:00
Mark Z. Ding
fc3b341529 [ty] Truncate Literal type display in some situations (#20928) 2025-10-17 11:50:58 +00:00
Alex Waygood
baaa8dad3a [ty] Re-enable fuzzer seeds that are no longer slow (#20937) 2025-10-17 12:29:13 +01:00
Micha Reiser
a21cde8a5a [ty] Fix playground crash for very large files (#20934) 2025-10-17 09:15:33 +02:00
Aria Desires
64edfb6ef6 [ty] add legacy namespace package support (#20897)
Detect legacy namespace packages and treat them like namespace packages
when looking them up as the *parent* of the module we're interested in.
In all other cases treat them like a regular package.

(This PR is coauthored by @MichaReiser in a shared coding session)

Fixes https://github.com/astral-sh/ty/issues/838

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
2025-10-17 03:16:37 +00:00
Ibraheem Ahmed
96b156303b [ty] Prefer declared type for invariant collection literals (#20927)
## Summary

Prefer the declared type for collection literals, e.g.,
```py
x: list[Any] = [1, "2", (3,)]
reveal_type(x)  # list[Any]
```

This solves a large part of https://github.com/astral-sh/ty/issues/136
for invariant generics, where respecting the declared type is a lot more
important. It also means that annotated dict literals with `dict[_,
Any]` is a way out of https://github.com/astral-sh/ty/issues/1248.
2025-10-16 16:11:28 -04:00
Douglas Creager
b0e10a9777 [ty] Don't track inferability via different Type variants (#20677)
We have to track whether a typevar appears in a position where it's
inferable or not. In a non-inferable position (in the body of the
generic class or function that binds it), assignability must hold for
every possible specialization of the typevar. In an inferable position,
it only needs to hold for _some_ specialization.
https://github.com/astral-sh/ruff/pull/20093 is working on using
constraint sets to model assignability of typevars, and the constraint
sets that we produce will be the same for inferable vs non-inferable
typevars; what changes is what we _compare_ that constraint set to. (For
a non-inferable typevar, the constraint set must equal the set of valid
specializations; for an inferable typevar, it must not be `never`.)

When I first added support for tracking inferable vs non-inferable
typevars, it seemed like it would be easiest to have separate `Type`
variants for each. The alternative (which lines up with the Δ set in
[POPL15](https://doi.org/10.1145/2676726.2676991)) would be to
explicitly plumb through a list of inferable typevars through our type
property methods. That seemed cumbersome.

In retrospect, that was the wrong decision. We've had to jump through
hoops to translate types between the inferable and non-inferable
variants, which has been quite brittle. Combined with the original point
above, that much of the assignability logic will become more identical
between inferable and non-inferable, there is less justification for the
two `Type` variants. And plumbing an extra `inferable` parameter through
all of these methods turns out to not be as bad as I anticipated.

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-10-16 15:59:46 -04:00
Ibraheem Ahmed
25023cc0ea [ty] Use declared variable types as bidirectional type context (#20796)
## Summary

Use the declared type of variables as type context for the RHS of assignment expressions, e.g.,
```py
x: list[int | str]
x = [1]
reveal_type(x)  # revealed: list[int | str]
```
2025-10-16 15:40:39 -04:00
Ibraheem Ahmed
1ade4f2081 [ty] Avoid unnecessarily widening generic specializations (#20875)
## Summary

Ignore the type context when specializing a generic call if it leads to
an unnecessarily wide return type. For example, [the example mentioned
here](https://github.com/astral-sh/ruff/pull/20796#issuecomment-3403319536)
works as expected after this change:
```py
def id[T](x: T) -> T:
    return x

def _(i: int):
    x: int | None = id(i)
    y: int | None = i
    reveal_type(x)  # revealed: int
    reveal_type(y)  # revealed: int
```

I also added extended our usage of `filter_disjoint_elements` to tuple
and typed-dict inference, which resolves
https://github.com/astral-sh/ty/issues/1266.
2025-10-16 19:17:37 +00:00
David Peter
8dad58de37 [ty] Support dataclass-transform field_specifiers (#20888)
## Summary

Add support for the `field_specifiers` parameter on
`dataclass_transform` decorator calls.

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

## Conformance test results

All true positives ✔️ 

## Ecosystem analysis

* `trio`: this is the kind of change that I would expect from this PR.
The code makes use of a dataclass `Outcome` with a `_unwrapped: bool =
attr.ib(default=False, eq=False, init=False)` field that is excluded
from the `__init__` signature, so we now see a bunch of
constructor-call-related errors going away.
* `home-assistant/core`: They have a `domain: str = attr.ib(init=False,
repr=False)` field and then use
  ```py
    @domain.default
    def _domain_default(self) -> str:
        # …
  ```
This accesses the `default` attribute on `dataclasses.Field[…]` with a
type of `default: _T | Literal[_MISSING_TYPE.MISSING]`, so we get those
"Object of type `_MISSING_TYPE` is not callable" errors. I don't really
understand how that is supposed to work. Even if `_MISSING_TYPE` would
be absent from that union, what does this try to call? pyright also
issues an error and it doesn't seem to work at runtime? So this looks
like a true positive?
* `attrs`: Similar here. There are some new diagnostics on code that
tries to access `.validator` on a field. This *does* work at runtime,
but I'm not sure how that is supposed to type-check (without a [custom
plugin](2c6c395935/mypy/plugins/attrs.py (L575-L602))).
pyright errors on this as well.
* A handful of new false positives because we don't support `alias` yet

## Test Plan

Updated tests.
2025-10-16 20:49:11 +02:00
1452 changed files with 39772 additions and 13751 deletions

View File

@@ -8,7 +8,3 @@ benchmark = "bench -p ruff_benchmark --bench linter --bench formatter --"
# See: https://github.com/astral-sh/ruff/issues/11503
[target.'cfg(all(target_env="msvc", target_os = "windows"))']
rustflags = ["-C", "target-feature=+crt-static"]
[target.'wasm32-unknown-unknown']
# See https://docs.rs/getrandom/latest/getrandom/#webassembly-support
rustflags = ["--cfg", 'getrandom_backend="wasm_js"']

View File

@@ -22,7 +22,7 @@ env:
CARGO_TERM_COLOR: always
RUSTUP_MAX_RETRIES: 10
PACKAGE_NAME: ruff
PYTHON_VERSION: "3.13"
PYTHON_VERSION: "3.14"
NEXTEST_PROFILE: ci
jobs:
@@ -231,6 +231,8 @@ jobs:
with:
persist-credentials: false
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Install Rust toolchain"
run: |
rustup component add clippy
@@ -251,20 +253,23 @@ jobs:
with:
persist-credentials: false
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
shared-key: ruff-linux-debug
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- name: "Install cargo nextest"
uses: taiki-e/install-action@522492a8c115f1b6d4d318581f09638e9442547b # v2.62.21
uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
with:
tool: cargo-nextest
- name: "Install cargo insta"
uses: taiki-e/install-action@522492a8c115f1b6d4d318581f09638e9442547b # v2.62.21
uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
with:
tool: cargo-insta
- name: "Install uv"
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
with:
enable-cache: "true"
- name: ty mdtests (GitHub annotations)
@@ -277,7 +282,8 @@ jobs:
run: cargo test -p ty_python_semantic --test mdtest || true
- name: "Run tests"
run: cargo insta test --all-features --unreferenced reject --test-runner nextest
- name: Dogfood ty on py-fuzzer
run: uv run --project=./python/py-fuzzer cargo run -p ty check --project=./python/py-fuzzer
# Check for broken links in the documentation.
- run: cargo doc --all --no-deps
env:
@@ -290,14 +296,6 @@ jobs:
env:
# Setting RUSTDOCFLAGS because `cargo doc --check` isn't yet implemented (https://github.com/rust-lang/cargo/issues/10025).
RUSTDOCFLAGS: "-D warnings"
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: ruff
path: target/debug/ruff
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: ty
path: target/debug/ty
cargo-test-linux-release:
name: "cargo test (linux, release)"
@@ -314,28 +312,35 @@ jobs:
with:
persist-credentials: false
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- name: "Install cargo nextest"
uses: taiki-e/install-action@522492a8c115f1b6d4d318581f09638e9442547b # v2.62.21
uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
with:
tool: cargo-nextest
- name: "Install cargo insta"
uses: taiki-e/install-action@522492a8c115f1b6d4d318581f09638e9442547b # v2.62.21
uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
with:
tool: cargo-insta
- name: "Install uv"
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
with:
enable-cache: "true"
- name: "Run tests"
run: cargo insta test --release --all-features --unreferenced reject --test-runner nextest
cargo-test-windows:
name: "cargo test (windows)"
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-windows-2022-16' || 'windows-latest' }}
cargo-test-other:
strategy:
matrix:
platform:
- ${{ github.repository == 'astral-sh/ruff' && 'depot-windows-2022-16' || 'windows-latest' }}
- macos-latest
name: "cargo test (${{ matrix.platform }})"
runs-on: ${{ matrix.platform }}
needs: determine_changes
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
timeout-minutes: 20
@@ -344,45 +349,16 @@ jobs:
with:
persist-credentials: false
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Install Rust toolchain"
run: rustup show
- name: "Install cargo nextest"
uses: taiki-e/install-action@522492a8c115f1b6d4d318581f09638e9442547b # v2.62.21
uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
with:
tool: cargo-nextest
- name: "Install uv"
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
with:
enable-cache: "true"
- name: "Run tests"
env:
# Workaround for <https://github.com/nextest-rs/nextest/issues/1493>.
RUSTUP_WINDOWS_PATH_ADD_BIN: 1
run: |
cargo nextest run --all-features --profile ci
cargo test --all-features --doc
cargo-test-macos:
name: "cargo test (macos)"
runs-on: macos-latest
needs: determine_changes
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
timeout-minutes: 20
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- name: "Install cargo nextest"
uses: taiki-e/install-action@522492a8c115f1b6d4d318581f09638e9442547b # v2.62.21
with:
tool: cargo-nextest
- name: "Install uv"
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
with:
enable-cache: "true"
- name: "Run tests"
@@ -401,9 +377,11 @@ jobs:
with:
persist-credentials: false
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Install Rust toolchain"
run: rustup target add wasm32-unknown-unknown
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: 22
cache: "npm"
@@ -436,6 +414,8 @@ jobs:
file: "Cargo.toml"
field: "workspace.package.rust-version"
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Install Rust toolchain"
env:
MSRV: ${{ steps.msrv.outputs.value }}
@@ -460,10 +440,13 @@ jobs:
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
workspaces: "fuzz -> target"
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- name: "Install cargo-binstall"
uses: cargo-bins/cargo-binstall@a66119fbb1c952daba62640c2609111fe0803621 # v1.15.7
uses: cargo-bins/cargo-binstall@ae04fb5e853ae6cd3ad7de4a1d554a8b646d12aa # v1.15.11
- 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
@@ -472,9 +455,7 @@ jobs:
fuzz-parser:
name: "fuzz parser"
runs-on: ubuntu-latest
needs:
- cargo-test-linux
- determine_changes
needs: determine_changes
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.parser == 'true' || needs.determine_changes.outputs.py-fuzzer == 'true') }}
timeout-minutes: 20
env:
@@ -483,26 +464,24 @@ jobs:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
name: Download Ruff binary to test
id: download-cached-binary
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
name: ruff
path: ruff-to-test
shared-key: ruff-linux-debug
save-if: false
- name: "Install Rust toolchain"
run: rustup show
- name: Build Ruff binary
run: cargo build --bin ruff
- name: Fuzz
env:
DOWNLOAD_PATH: ${{ steps.download-cached-binary.outputs.download-path }}
run: |
# Make executable, since artifact download doesn't preserve this
chmod +x "${DOWNLOAD_PATH}/ruff"
(
uvx \
uv run \
--python="${PYTHON_VERSION}" \
--from=./python/py-fuzzer \
--project=./python/py-fuzzer \
--locked \
fuzz \
--test-executable="${DOWNLOAD_PATH}/ruff" \
--test-executable=target/debug/ruff \
--bin=ruff \
0-500
)
@@ -518,6 +497,9 @@ jobs:
with:
persist-credentials: false
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
- name: "Install Rust toolchain"
run: rustup component add rustfmt
# Run all code generation scripts, and verify that the current output is
@@ -532,13 +514,16 @@ jobs:
./scripts/add_plugin.py test --url https://pypi.org/project/-test/0.1.0/ --prefix TST
./scripts/add_rule.py --name FirstRule --prefix TST --code 001 --linter test
- run: cargo check
# Lint/format/type-check py-fuzzer
# (dogfooding with ty is done in a separate job)
- run: uv run --directory=./python/py-fuzzer mypy
- run: uv run --directory=./python/py-fuzzer ruff format --check
- run: uv run --directory=./python/py-fuzzer ruff check
ecosystem:
name: "ecosystem"
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-latest-8' || 'ubuntu-latest' }}
needs:
- cargo-test-linux
- determine_changes
needs: determine_changes
# Only runs on pull requests, since that is the only we way we can find the base version for comparison.
# Ecosystem check needs linter and/or formatter changes.
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && github.event_name == 'pull_request' && needs.determine_changes.outputs.code == 'true' }}
@@ -546,42 +531,49 @@ jobs:
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
ref: ${{ github.event.pull_request.base.ref }}
persist-credentials: false
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
with:
python-version: ${{ env.PYTHON_VERSION }}
activate-environment: true
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
name: Download comparison Ruff binary
id: ruff-target
with:
name: ruff
path: target/debug
- name: "Install Rust toolchain"
run: rustup show
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
name: Download baseline Ruff binary
- name: "Install mold"
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
name: ruff
branch: ${{ github.event.pull_request.base.ref }}
workflow: "ci.yaml"
check_artifacts: true
shared-key: ruff-linux-debug
save-if: false
- name: Build baseline version
run: |
cargo build --bin ruff
mv target/debug/ruff target/debug/ruff-baseline
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
clean: false
- name: Build comparison version
run: cargo build --bin ruff
- name: Install ruff-ecosystem
run: |
pip install ./python/ruff-ecosystem
uv pip install ./python/ruff-ecosystem
- name: Run `ruff check` stable ecosystem check
if: ${{ needs.determine_changes.outputs.linter == 'true' }}
env:
DOWNLOAD_PATH: ${{ steps.ruff-target.outputs.download-path }}
run: |
# Make executable, since artifact download doesn't preserve this
chmod +x ./ruff "${DOWNLOAD_PATH}/ruff"
# Set pipefail to avoid hiding errors with tee
set -eo pipefail
ruff-ecosystem check ./ruff "${DOWNLOAD_PATH}/ruff" --cache ./checkouts --output-format markdown | tee ecosystem-result-check-stable
ruff-ecosystem check ./target/debug/ruff-baseline ./target/debug/ruff --cache ./checkouts --output-format markdown | tee ecosystem-result-check-stable
cat ecosystem-result-check-stable > "$GITHUB_STEP_SUMMARY"
echo "### Linter (stable)" > ecosystem-result
@@ -590,16 +582,11 @@ jobs:
- name: Run `ruff check` preview ecosystem check
if: ${{ needs.determine_changes.outputs.linter == 'true' }}
env:
DOWNLOAD_PATH: ${{ steps.ruff-target.outputs.download-path }}
run: |
# Make executable, since artifact download doesn't preserve this
chmod +x ./ruff "${DOWNLOAD_PATH}/ruff"
# Set pipefail to avoid hiding errors with tee
set -eo pipefail
ruff-ecosystem check ./ruff "${DOWNLOAD_PATH}/ruff" --cache ./checkouts --output-format markdown --force-preview | tee ecosystem-result-check-preview
ruff-ecosystem check ./target/debug/ruff-baseline ./target/debug/ruff --cache ./checkouts --output-format markdown --force-preview | tee ecosystem-result-check-preview
cat ecosystem-result-check-preview > "$GITHUB_STEP_SUMMARY"
echo "### Linter (preview)" >> ecosystem-result
@@ -608,16 +595,11 @@ jobs:
- name: Run `ruff format` stable ecosystem check
if: ${{ needs.determine_changes.outputs.formatter == 'true' }}
env:
DOWNLOAD_PATH: ${{ steps.ruff-target.outputs.download-path }}
run: |
# Make executable, since artifact download doesn't preserve this
chmod +x ./ruff "${DOWNLOAD_PATH}/ruff"
# Set pipefail to avoid hiding errors with tee
set -eo pipefail
ruff-ecosystem format ./ruff "${DOWNLOAD_PATH}/ruff" --cache ./checkouts --output-format markdown | tee ecosystem-result-format-stable
ruff-ecosystem format ./target/debug/ruff-baseline ./target/debug/ruff --cache ./checkouts --output-format markdown | tee ecosystem-result-format-stable
cat ecosystem-result-format-stable > "$GITHUB_STEP_SUMMARY"
echo "### Formatter (stable)" >> ecosystem-result
@@ -626,32 +608,19 @@ jobs:
- name: Run `ruff format` preview ecosystem check
if: ${{ needs.determine_changes.outputs.formatter == 'true' }}
env:
DOWNLOAD_PATH: ${{ steps.ruff-target.outputs.download-path }}
run: |
# Make executable, since artifact download doesn't preserve this
chmod +x ./ruff "${DOWNLOAD_PATH}/ruff"
# Set pipefail to avoid hiding errors with tee
set -eo pipefail
ruff-ecosystem format ./ruff "${DOWNLOAD_PATH}/ruff" --cache ./checkouts --output-format markdown --force-preview | tee ecosystem-result-format-preview
ruff-ecosystem format ./target/debug/ruff-baseline ./target/debug/ruff --cache ./checkouts --output-format markdown --force-preview | tee ecosystem-result-format-preview
cat ecosystem-result-format-preview > "$GITHUB_STEP_SUMMARY"
echo "### Formatter (preview)" >> ecosystem-result
cat ecosystem-result-format-preview >> ecosystem-result
echo "" >> ecosystem-result
- name: Export pull request number
run: |
echo ${{ github.event.number }} > pr-number
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
name: Upload PR Number
with:
name: pr-number
path: pr-number
# NOTE: astral-sh-bot uses this artifact to post comments on PRs.
# Make sure to update the bot if you rename the artifact.
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
name: Upload Results
with:
@@ -662,47 +631,50 @@ jobs:
name: "Fuzz for new ty panics"
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}
needs:
- cargo-test-linux
- determine_changes
# Only runs on pull requests, since that is the only we way we can find the base version for comparison.
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && github.event_name == 'pull_request' && (needs.determine_changes.outputs.ty == 'true' || needs.determine_changes.outputs.py-fuzzer == 'true') }}
timeout-minutes: 20
timeout-minutes: ${{ github.repository == 'astral-sh/ruff' && 10 || 20 }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
fetch-depth: 0
persist-credentials: false
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
name: Download new ty binary
id: ty-new
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
name: ty
path: target/debug
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
name: Download baseline ty binary
with:
name: ty
branch: ${{ github.event.pull_request.base.ref }}
workflow: "ci.yaml"
check_artifacts: true
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- name: Fuzz
env:
FORCE_COLOR: 1
NEW_TY: ${{ steps.ty-new.outputs.download-path }}
run: |
# Make executable, since artifact download doesn't preserve this
chmod +x "${PWD}/ty" "${NEW_TY}/ty"
echo "new commit"
git rev-list --format=%s --max-count=1 "$GITHUB_SHA"
cargo build --profile=profiling --bin=ty
mv target/profiling/ty ty-new
MERGE_BASE="$(git merge-base "$GITHUB_SHA" "origin/$GITHUB_BASE_REF")"
git checkout -b old_commit "$MERGE_BASE"
echo "old commit (merge base)"
git rev-list --format=%s --max-count=1 old_commit
cargo build --profile=profiling --bin=ty
mv target/profiling/ty ty-old
(
uvx \
uv run \
--python="${PYTHON_VERSION}" \
--from=./python/py-fuzzer \
--project=./python/py-fuzzer \
--locked \
fuzz \
--test-executable="${NEW_TY}/ty" \
--baseline-executable="${PWD}/ty" \
--test-executable=ty-new \
--baseline-executable=ty-old \
--only-new-bugs \
--bin=ty \
0-500
0-1000
)
cargo-shear:
@@ -714,7 +686,7 @@ jobs:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: cargo-bins/cargo-binstall@a66119fbb1c952daba62640c2609111fe0803621 # v1.15.7
- uses: cargo-bins/cargo-binstall@ae04fb5e853ae6cd3ad7de4a1d554a8b646d12aa # v1.15.11
- run: cargo binstall --no-confirm cargo-shear
- run: cargo shear
@@ -727,12 +699,16 @@ jobs:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- name: "Run ty completion evaluation"
run: cargo run --release --package ty_completion_eval -- all --threshold 0.4 --tasks /tmp/completion-evaluation-tasks.csv
run: cargo run --profile profiling --package ty_completion_eval -- all --threshold 0.4 --tasks /tmp/completion-evaluation-tasks.csv
- name: "Ensure there are no changes"
run: diff ./crates/ty_completion_eval/completion-evaluation-tasks.csv /tmp/completion-evaluation-tasks.csv
@@ -750,6 +726,8 @@ jobs:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
@@ -772,9 +750,11 @@ jobs:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: 22
- name: "Cache pre-commit"
@@ -803,10 +783,9 @@ jobs:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version: "3.13"
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Add SSH key"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1
@@ -815,13 +794,16 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: Install uv
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
with:
python-version: 3.13
activate-environment: true
- name: "Install Insiders dependencies"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
run: uv pip install -r docs/requirements-insiders.txt --system
run: uv pip install -r docs/requirements-insiders.txt
- name: "Install dependencies"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
run: uv pip install -r docs/requirements.txt --system
run: uv pip install -r docs/requirements.txt
- name: "Update README File"
run: python scripts/transform_readme.py --target mkdocs
- name: "Generate docs"
@@ -846,6 +828,8 @@ jobs:
with:
persist-credentials: false
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Install Rust toolchain"
run: rustup show
- name: "Run checks"
@@ -859,9 +843,7 @@ jobs:
name: "test ruff-lsp"
runs-on: ubuntu-latest
timeout-minutes: 5
needs:
- cargo-test-linux
- determine_changes
needs: determine_changes
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-test') && (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }}
steps:
- uses: extractions/setup-just@e33e0265a09d6d736e2ee1e0eb685ef1de4669ff # v3.0.0
@@ -869,37 +851,46 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
name: "Download ruff-lsp source"
name: "Checkout ruff source"
with:
persist-credentials: false
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
shared-key: ruff-linux-debug
save-if: false
- name: "Install Rust toolchain"
run: rustup show
- name: Build Ruff binary
run: cargo build -p ruff --bin ruff
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
name: "Checkout ruff-lsp source"
with:
persist-credentials: false
repository: "astral-sh/ruff-lsp"
path: ruff-lsp
- uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
# installation fails on 3.13 and newer
python-version: "3.12"
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
name: Download development ruff binary
id: ruff-target
with:
name: ruff
path: target/debug
- name: Install ruff-lsp dependencies
run: |
cd ruff-lsp
just install
- name: Run ruff-lsp tests
env:
DOWNLOAD_PATH: ${{ steps.ruff-target.outputs.download-path }}
run: |
# Setup development binary
pip uninstall --yes ruff
chmod +x "${DOWNLOAD_PATH}/ruff"
export PATH="${DOWNLOAD_PATH}:${PATH}"
export PATH="${PWD}/target/debug:${PATH}"
ruff version
cd ruff-lsp
just test
check-playground:
@@ -916,7 +907,9 @@ jobs:
- name: "Install Rust toolchain"
run: rustup target add wasm32-unknown-unknown
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: 22
cache: "npm"
@@ -954,21 +947,23 @@ jobs:
persist-credentials: false
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
- name: "Install Rust toolchain"
run: rustup show
- name: "Install codspeed"
uses: taiki-e/install-action@522492a8c115f1b6d4d318581f09638e9442547b # v2.62.21
uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
with:
tool: cargo-codspeed
- name: "Build benchmarks"
run: cargo codspeed build --features "codspeed,instrumented" --no-default-features -p ruff_benchmark --bench formatter --bench lexer --bench linter --bench parser
run: cargo codspeed build --features "codspeed,instrumented" --profile profiling --no-default-features -p ruff_benchmark --bench formatter --bench lexer --bench linter --bench parser
- name: "Run benchmarks"
uses: CodSpeedHQ/action@6b43a0cd438f6ca5ad26f9ed03ed159ed2df7da9 # v4.1.1
uses: CodSpeedHQ/action@bb005fe1c1eea036d3894f02c049cb6b154a1c27 # v4.3.3
with:
mode: instrumentation
run: cargo codspeed run
@@ -992,21 +987,23 @@ jobs:
persist-credentials: false
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
- name: "Install Rust toolchain"
run: rustup show
- name: "Install codspeed"
uses: taiki-e/install-action@522492a8c115f1b6d4d318581f09638e9442547b # v2.62.21
uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
with:
tool: cargo-codspeed
- name: "Build benchmarks"
run: cargo codspeed build --features "codspeed,instrumented" --no-default-features -p ruff_benchmark --bench ty
run: cargo codspeed build --features "codspeed,instrumented" --profile profiling --no-default-features -p ruff_benchmark --bench ty
- name: "Run benchmarks"
uses: CodSpeedHQ/action@6b43a0cd438f6ca5ad26f9ed03ed159ed2df7da9 # v4.1.1
uses: CodSpeedHQ/action@bb005fe1c1eea036d3894f02c049cb6b154a1c27 # v4.3.3
with:
mode: instrumentation
run: cargo codspeed run
@@ -1030,21 +1027,23 @@ jobs:
persist-credentials: false
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
- name: "Install Rust toolchain"
run: rustup show
- name: "Install codspeed"
uses: taiki-e/install-action@522492a8c115f1b6d4d318581f09638e9442547b # v2.62.21
uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
with:
tool: cargo-codspeed
- name: "Build benchmarks"
run: cargo codspeed build --features "codspeed,walltime" --no-default-features -p ruff_benchmark
run: cargo codspeed build --features "codspeed,walltime" --profile profiling --no-default-features -p ruff_benchmark
- name: "Run benchmarks"
uses: CodSpeedHQ/action@6b43a0cd438f6ca5ad26f9ed03ed159ed2df7da9 # v4.1.1
uses: CodSpeedHQ/action@bb005fe1c1eea036d3894f02c049cb6b154a1c27 # v4.3.3
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@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"
@@ -48,9 +48,10 @@ jobs:
run: |
# shellcheck disable=SC2046
(
uvx \
--python=3.12 \
--from=./python/py-fuzzer \
uv run \
--python=3.14 \
--project=./python/py-fuzzer \
--locked \
fuzz \
--test-executable=target/debug/ruff \
--bin=ruff \

View File

@@ -43,7 +43,7 @@ jobs:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
@@ -59,20 +59,15 @@ jobs:
run: |
cd ruff
scripts/mypy_primer.sh
echo ${{ github.event.number }} > ../pr-number
# NOTE: astral-sh-bot uses this artifact to post comments on PRs.
# Make sure to update the bot if you rename the artifact.
- name: Upload diff
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: mypy_primer_diff
path: mypy_primer.diff
- name: Upload pr-number
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: pr-number
path: pr-number
memory_usage:
name: Run memory statistics
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
@@ -85,7 +80,7 @@ jobs:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:

View File

@@ -1,122 +0,0 @@
name: PR comment (mypy_primer)
on: # zizmor: ignore[dangerous-triggers]
workflow_run:
workflows: [Run mypy_primer]
types: [completed]
workflow_dispatch:
inputs:
workflow_run_id:
description: The mypy_primer workflow that triggers the workflow run
required: true
jobs:
comment:
runs-on: ubuntu-24.04
permissions:
pull-requests: write
steps:
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
name: Download PR number
with:
name: pr-number
run_id: ${{ github.event.workflow_run.id || github.event.inputs.workflow_run_id }}
if_no_artifact_found: ignore
allow_forks: true
- name: Parse pull request number
id: pr-number
run: |
if [[ -f pr-number ]]
then
echo "pr-number=$(<pr-number)" >> "$GITHUB_OUTPUT"
fi
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
name: "Download mypy_primer results"
id: download-mypy_primer_diff
if: steps.pr-number.outputs.pr-number
with:
name: mypy_primer_diff
workflow: mypy_primer.yaml
pr: ${{ steps.pr-number.outputs.pr-number }}
path: pr/mypy_primer_diff
workflow_conclusion: completed
if_no_artifact_found: ignore
allow_forks: true
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
name: "Download mypy_primer memory results"
id: download-mypy_primer_memory_diff
if: steps.pr-number.outputs.pr-number
with:
name: mypy_primer_memory_diff
workflow: mypy_primer.yaml
pr: ${{ steps.pr-number.outputs.pr-number }}
path: pr/mypy_primer_memory_diff
workflow_conclusion: completed
if_no_artifact_found: ignore
allow_forks: true
- name: Generate comment content
id: generate-comment
if: ${{ steps.download-mypy_primer_diff.outputs.found_artifact == 'true' && steps.download-mypy_primer_memory_diff.outputs.found_artifact == 'true' }}
run: |
# Guard against malicious mypy_primer results that symlink to a secret
# file on this runner
if [[ -L pr/mypy_primer_diff/mypy_primer.diff ]] || [[ -L pr/mypy_primer_memory_diff/mypy_primer_memory.diff ]]
then
echo "Error: mypy_primer.diff and mypy_primer_memory.diff cannot be a symlink"
exit 1
fi
# Note this identifier is used to find the comment to update on
# subsequent runs
echo '<!-- generated-comment mypy_primer -->' >> comment.txt
echo '## `mypy_primer` results' >> comment.txt
if [ -s "pr/mypy_primer_diff/mypy_primer.diff" ]; then
echo '<details>' >> comment.txt
echo '<summary>Changes were detected when running on open source projects</summary>' >> comment.txt
echo '' >> comment.txt
echo '```diff' >> comment.txt
cat pr/mypy_primer_diff/mypy_primer.diff >> comment.txt
echo '```' >> comment.txt
echo '</details>' >> comment.txt
else
echo 'No ecosystem changes detected ✅' >> comment.txt
fi
if [ -s "pr/mypy_primer_memory_diff/mypy_primer_memory.diff" ]; then
echo '<details>' >> comment.txt
echo '<summary>Memory usage changes were detected when running on open source projects</summary>' >> comment.txt
echo '' >> comment.txt
echo '```diff' >> comment.txt
cat pr/mypy_primer_memory_diff/mypy_primer_memory.diff >> comment.txt
echo '```' >> comment.txt
echo '</details>' >> comment.txt
else
echo 'No memory usage changes detected ✅' >> comment.txt
fi
echo 'comment<<EOF' >> "$GITHUB_OUTPUT"
cat comment.txt >> "$GITHUB_OUTPUT"
echo 'EOF' >> "$GITHUB_OUTPUT"
- name: Find existing comment
uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3.1.0
if: steps.generate-comment.outcome == 'success'
id: find-comment
with:
issue-number: ${{ steps.pr-number.outputs.pr-number }}
comment-author: "github-actions[bot]"
body-includes: "<!-- generated-comment mypy_primer -->"
- name: Create or update comment
if: steps.find-comment.outcome == 'success'
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4
with:
comment-id: ${{ steps.find-comment.outputs.comment-id }}
issue-number: ${{ steps.pr-number.outputs.pr-number }}
body-path: comment.txt
edit-mode: replace

View File

@@ -1,88 +0,0 @@
name: Ecosystem check comment
on:
workflow_run:
workflows: [CI]
types: [completed]
workflow_dispatch:
inputs:
workflow_run_id:
description: The ecosystem workflow that triggers the workflow run
required: true
jobs:
comment:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
name: Download pull request number
with:
name: pr-number
run_id: ${{ github.event.workflow_run.id || github.event.inputs.workflow_run_id }}
if_no_artifact_found: ignore
allow_forks: true
- name: Parse pull request number
id: pr-number
run: |
if [[ -f pr-number ]]
then
echo "pr-number=$(<pr-number)" >> "$GITHUB_OUTPUT"
fi
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
name: "Download ecosystem results"
id: download-ecosystem-result
if: steps.pr-number.outputs.pr-number
with:
name: ecosystem-result
workflow: ci.yaml
pr: ${{ steps.pr-number.outputs.pr-number }}
path: pr/ecosystem
workflow_conclusion: completed
if_no_artifact_found: ignore
allow_forks: true
- name: Generate comment content
id: generate-comment
if: steps.download-ecosystem-result.outputs.found_artifact == 'true'
run: |
# Guard against malicious ecosystem results that symlink to a secret
# file on this runner
if [[ -L pr/ecosystem/ecosystem-result ]]
then
echo "Error: ecosystem-result cannot be a symlink"
exit 1
fi
# Note this identifier is used to find the comment to update on
# subsequent runs
echo '<!-- generated-comment ecosystem -->' >> comment.txt
echo '## `ruff-ecosystem` results' >> comment.txt
cat pr/ecosystem/ecosystem-result >> comment.txt
echo "" >> comment.txt
echo 'comment<<EOF' >> "$GITHUB_OUTPUT"
cat comment.txt >> "$GITHUB_OUTPUT"
echo 'EOF' >> "$GITHUB_OUTPUT"
- name: Find existing comment
uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3.1.0
if: steps.generate-comment.outcome == 'success'
id: find-comment
with:
issue-number: ${{ steps.pr-number.outputs.pr-number }}
comment-author: "github-actions[bot]"
body-includes: "<!-- generated-comment ecosystem -->"
- name: Create or update comment
if: steps.find-comment.outcome == 'success'
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4
with:
comment-id: ${{ steps.find-comment.outputs.comment-id }}
issue-number: ${{ steps.pr-number.outputs.pr-number }}
body-path: comment.txt
edit-mode: replace

View File

@@ -18,6 +18,8 @@ env:
CARGO_TERM_COLOR: always
RUSTUP_MAX_RETRIES: 10
permissions: {}
jobs:
publish:
runs-on: ubuntu-latest
@@ -29,11 +31,10 @@ jobs:
persist-credentials: false
- name: "Install Rust toolchain"
run: rustup target add wasm32-unknown-unknown
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: 22
cache: "npm"
cache-dependency-path: playground/package-lock.json
package-manager-cache: false
- uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
- name: "Install Node dependencies"
run: npm ci

View File

@@ -22,7 +22,7 @@ jobs:
id-token: write
steps:
- name: "Install uv"
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
with:
pattern: wheels-*

View File

@@ -35,9 +35,10 @@ jobs:
persist-credentials: false
- name: "Install Rust toolchain"
run: rustup target add wasm32-unknown-unknown
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: 22
package-manager-cache: false
- uses: jetli/wasm-bindgen-action@20b33e20595891ab1a0ed73145d8a21fc96e7c29 # v0.2.0
- name: "Install Node dependencies"
run: npm ci

View File

@@ -45,7 +45,7 @@ jobs:
jq '.name="@astral-sh/ruff-wasm-${{ matrix.target }}"' crates/ruff_wasm/pkg/package.json > /tmp/package.json
mv /tmp/package.json crates/ruff_wasm/pkg
- run: cp LICENSE crates/ruff_wasm/pkg # wasm-pack does not put the LICENSE file in the pkg
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: 22
registry-url: "https://registry.npmjs.org"

View File

@@ -1,7 +1,6 @@
# This file was autogenerated by dist: https://github.com/astral-sh/cargo-dist
# This file was autogenerated by dist: https://axodotdev.github.io/cargo-dist
#
# Copyright 2022-2024, axodotdev
# Copyright 2025 Astral Software Inc.
# SPDX-License-Identifier: MIT or Apache-2.0
#
# CI that:
@@ -69,9 +68,9 @@ jobs:
# we specify bash to get pipefail; it guards against the `curl` command
# failing. otherwise `sh` won't catch that `curl` returned non-0
shell: bash
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/cargo-dist/releases/download/v0.28.5-prerelease.1/cargo-dist-installer.sh | sh"
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.30.2/cargo-dist-installer.sh | sh"
- name: Cache dist
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: cargo-dist-cache
path: ~/.cargo/bin/dist
@@ -87,7 +86,7 @@ jobs:
cat plan-dist-manifest.json
echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
- name: "Upload dist-manifest.json"
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: artifacts-plan-dist-manifest
path: plan-dist-manifest.json
@@ -129,14 +128,14 @@ jobs:
persist-credentials: false
submodules: recursive
- name: Install cached dist
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53
with:
name: cargo-dist-cache
path: ~/.cargo/bin/
- run: chmod +x ~/.cargo/bin/dist
# Get all the local artifacts for the global tasks to use (for e.g. checksums)
- name: Fetch local artifacts
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53
with:
pattern: artifacts-*
path: target/distrib/
@@ -154,7 +153,7 @@ jobs:
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
- name: "Upload artifacts"
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
name: artifacts-build-global
path: |
@@ -167,8 +166,8 @@ jobs:
- custom-build-binaries
- custom-build-docker
- build-global-artifacts
# Only run if we're "publishing", and only if local and global didn't fail (skipped is fine)
if: ${{ always() && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.custom-build-binaries.result == 'skipped' || needs.custom-build-binaries.result == 'success') && (needs.custom-build-docker.result == 'skipped' || needs.custom-build-docker.result == 'success') }}
# Only run if we're "publishing", and only if plan, local and global didn't fail (skipped is fine)
if: ${{ always() && needs.plan.result == 'success' && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.custom-build-binaries.result == 'skipped' || needs.custom-build-binaries.result == 'success') && (needs.custom-build-docker.result == 'skipped' || needs.custom-build-docker.result == 'success') }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
runs-on: "depot-ubuntu-latest-4"
@@ -180,14 +179,14 @@ jobs:
persist-credentials: false
submodules: recursive
- name: Install cached dist
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53
with:
name: cargo-dist-cache
path: ~/.cargo/bin/
- run: chmod +x ~/.cargo/bin/dist
# Fetch artifacts from scratch-storage
- name: Fetch artifacts
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53
with:
pattern: artifacts-*
path: target/distrib/
@@ -201,7 +200,7 @@ jobs:
cat dist-manifest.json
echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
- name: "Upload dist-manifest.json"
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4
with:
# Overwrite the previous copy
name: artifacts-dist-manifest
@@ -257,7 +256,7 @@ jobs:
submodules: recursive
# Create a GitHub Release while uploading all files to it
- name: "Download GitHub Artifacts"
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53
with:
pattern: artifacts-*
path: artifacts

View File

@@ -77,7 +77,7 @@ jobs:
run: |
git config --global user.name typeshedbot
git config --global user.email '<>'
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
- name: Sync typeshed stubs
run: |
rm -rf "ruff/${VENDORED_TYPESHED}"
@@ -131,7 +131,7 @@ jobs:
with:
persist-credentials: true
ref: ${{ env.UPSTREAM_BRANCH}}
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
- name: Setup git
run: |
git config --global user.name typeshedbot
@@ -170,7 +170,7 @@ jobs:
with:
persist-credentials: true
ref: ${{ env.UPSTREAM_BRANCH}}
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
- uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
- name: Setup git
run: |
git config --global user.name typeshedbot
@@ -207,12 +207,12 @@ jobs:
uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
- name: "Install cargo nextest"
if: ${{ success() }}
uses: taiki-e/install-action@522492a8c115f1b6d4d318581f09638e9442547b # v2.62.21
uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
with:
tool: cargo-nextest
- name: "Install cargo insta"
if: ${{ success() }}
uses: taiki-e/install-action@522492a8c115f1b6d4d318581f09638e9442547b # v2.62.21
uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
with:
tool: cargo-insta
- name: Update snapshots

View File

@@ -33,11 +33,14 @@ jobs:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
with:
enable-cache: true # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
workspaces: "ruff"
lookup-only: false # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
- name: Install Rust toolchain
run: rustup show
@@ -109,8 +112,6 @@ jobs:
cat diff-statistics.md >> "$GITHUB_STEP_SUMMARY"
echo ${{ github.event.number }} > pr-number
- name: "Deploy to Cloudflare Pages"
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
id: deploy
@@ -128,18 +129,14 @@ jobs:
echo >> comment.md
echo "**[Full report with detailed diff]($DEPLOYMENT_URL/diff)** ([timing results]($DEPLOYMENT_URL/timing))" >> comment.md
# NOTE: astral-sh-bot uses this artifact to post comments on PRs.
# Make sure to update the bot if you rename the artifact.
- name: Upload comment
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: comment.md
path: comment.md
- name: Upload pr-number
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: pr-number
path: pr-number
- name: Upload diagnostics diff
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:

View File

@@ -1,85 +0,0 @@
name: PR comment (ty ecosystem-analyzer)
on: # zizmor: ignore[dangerous-triggers]
workflow_run:
workflows: [ty ecosystem-analyzer]
types: [completed]
workflow_dispatch:
inputs:
workflow_run_id:
description: The ty ecosystem-analyzer workflow that triggers the workflow run
required: true
jobs:
comment:
runs-on: ubuntu-24.04
permissions:
pull-requests: write
steps:
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
name: Download PR number
with:
name: pr-number
run_id: ${{ github.event.workflow_run.id || github.event.inputs.workflow_run_id }}
if_no_artifact_found: ignore
allow_forks: true
- name: Parse pull request number
id: pr-number
run: |
if [[ -f pr-number ]]
then
echo "pr-number=$(<pr-number)" >> "$GITHUB_OUTPUT"
fi
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
name: "Download comment.md"
id: download-comment
if: steps.pr-number.outputs.pr-number
with:
name: comment.md
workflow: ty-ecosystem-analyzer.yaml
pr: ${{ steps.pr-number.outputs.pr-number }}
path: pr/comment
workflow_conclusion: completed
if_no_artifact_found: ignore
allow_forks: true
- name: Generate comment content
id: generate-comment
if: ${{ steps.download-comment.outputs.found_artifact == 'true' }}
run: |
# Guard against malicious ty ecosystem-analyzer results that symlink to a secret
# file on this runner
if [[ -L pr/comment/comment.md ]]
then
echo "Error: comment.md cannot be a symlink"
exit 1
fi
# Note: this identifier is used to find the comment to update on subsequent runs
echo '<!-- generated-comment ty ecosystem-analyzer -->' > comment.md
echo >> comment.md
cat pr/comment/comment.md >> comment.md
echo 'comment<<EOF' >> "$GITHUB_OUTPUT"
cat comment.md >> "$GITHUB_OUTPUT"
echo 'EOF' >> "$GITHUB_OUTPUT"
- name: Find existing comment
uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3.1.0
if: steps.generate-comment.outcome == 'success'
id: find-comment
with:
issue-number: ${{ steps.pr-number.outputs.pr-number }}
comment-author: "github-actions[bot]"
body-includes: "<!-- generated-comment ty ecosystem-analyzer -->"
- name: Create or update comment
if: steps.find-comment.outcome == 'success'
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4
with:
comment-id: ${{ steps.find-comment.outputs.comment-id }}
issue-number: ${{ steps.pr-number.outputs.pr-number }}
body-path: comment.md
edit-mode: replace

View File

@@ -29,11 +29,14 @@ jobs:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
with:
enable-cache: true # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
- uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
with:
workspaces: "ruff"
lookup-only: false # zizmor: ignore[cache-poisoning] acceptable risk for CloudFlare pages artifact
- name: Install Rust toolchain
run: rustup show

View File

@@ -24,7 +24,7 @@ env:
CARGO_TERM_COLOR: always
RUSTUP_MAX_RETRIES: 10
RUST_BACKTRACE: 1
CONFORMANCE_SUITE_COMMIT: d4f39b27a4a47aac8b6d4019e1b0b5b3156fabdc
CONFORMANCE_SUITE_COMMIT: 9f6d8ced7cd1c8d92687a4e9c96d7716452e471e
jobs:
typing_conformance:
@@ -94,21 +94,18 @@ jobs:
touch typing_conformance_diagnostics.diff
fi
echo ${{ github.event.number }} > pr-number
echo "${CONFORMANCE_SUITE_COMMIT}" > conformance-suite-commit
# NOTE: astral-sh-bot uses this artifact to post comments on PRs.
# Make sure to update the bot if you rename the artifact.
- name: Upload diff
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: typing_conformance_diagnostics_diff
path: typing_conformance_diagnostics.diff
- name: Upload pr-number
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: pr-number
path: pr-number
# NOTE: astral-sh-bot uses this artifact to post comments on PRs.
# Make sure to update the bot if you rename the artifact.
- name: Upload conformance suite commit
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:

View File

@@ -1,112 +0,0 @@
name: PR comment (typing_conformance)
on: # zizmor: ignore[dangerous-triggers]
workflow_run:
workflows: [Run typing conformance]
types: [completed]
workflow_dispatch:
inputs:
workflow_run_id:
description: The typing_conformance workflow that triggers the workflow run
required: true
jobs:
comment:
runs-on: ubuntu-24.04
permissions:
pull-requests: write
steps:
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
name: Download PR number
with:
name: pr-number
run_id: ${{ github.event.workflow_run.id || github.event.inputs.workflow_run_id }}
if_no_artifact_found: ignore
allow_forks: true
- name: Parse pull request number
id: pr-number
run: |
if [[ -f pr-number ]]
then
echo "pr-number=$(<pr-number)" >> "$GITHUB_OUTPUT"
fi
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
name: Download typing conformance suite commit
with:
name: conformance-suite-commit
run_id: ${{ github.event.workflow_run.id || github.event.inputs.workflow_run_id }}
if_no_artifact_found: ignore
allow_forks: true
- uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc # v8
name: "Download typing_conformance results"
id: download-typing_conformance_diff
if: steps.pr-number.outputs.pr-number
with:
name: typing_conformance_diagnostics_diff
workflow: typing_conformance.yaml
pr: ${{ steps.pr-number.outputs.pr-number }}
path: pr/typing_conformance_diagnostics_diff
workflow_conclusion: completed
if_no_artifact_found: ignore
allow_forks: true
- name: Generate comment content
id: generate-comment
if: ${{ steps.download-typing_conformance_diff.outputs.found_artifact == 'true' }}
run: |
# Guard against malicious typing_conformance results that symlink to a secret
# file on this runner
if [[ -L pr/typing_conformance_diagnostics_diff/typing_conformance_diagnostics.diff ]]
then
echo "Error: typing_conformance_diagnostics.diff cannot be a symlink"
exit 1
fi
# Note this identifier is used to find the comment to update on
# subsequent runs
echo '<!-- generated-comment typing_conformance_diagnostics_diff -->' >> comment.txt
if [[ -f conformance-suite-commit ]]
then
echo "## Diagnostic diff on [typing conformance tests](https://github.com/python/typing/tree/$(<conformance-suite-commit)/conformance)" >> comment.txt
else
echo "conformance-suite-commit file not found"
echo "## Diagnostic diff on typing conformance tests" >> comment.txt
fi
if [ -s "pr/typing_conformance_diagnostics_diff/typing_conformance_diagnostics.diff" ]; then
echo '<details>' >> comment.txt
echo '<summary>Changes were detected when running ty on typing conformance tests</summary>' >> comment.txt
echo '' >> comment.txt
echo '```diff' >> comment.txt
cat pr/typing_conformance_diagnostics_diff/typing_conformance_diagnostics.diff >> comment.txt
echo '```' >> comment.txt
echo '</details>' >> comment.txt
else
echo 'No changes detected when running ty on typing conformance tests ✅' >> comment.txt
fi
echo 'comment<<EOF' >> "$GITHUB_OUTPUT"
cat comment.txt >> "$GITHUB_OUTPUT"
echo 'EOF' >> "$GITHUB_OUTPUT"
- name: Find existing comment
uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3.1.0
if: steps.generate-comment.outcome == 'success'
id: find-comment
with:
issue-number: ${{ steps.pr-number.outputs.pr-number }}
comment-author: "github-actions[bot]"
body-includes: "<!-- generated-comment typing_conformance_diagnostics_diff -->"
- name: Create or update comment
if: steps.find-comment.outcome == 'success'
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4
with:
comment-id: ${{ steps.find-comment.outputs.comment-id }}
issue-number: ${{ steps.pr-number.outputs.pr-number }}
body-path: comment.txt
edit-mode: replace

18
.github/zizmor.yml vendored
View File

@@ -1,21 +1,23 @@
# Configuration for the zizmor static analysis tool, run via pre-commit in CI
# https://woodruffw.github.io/zizmor/configuration/
# https://docs.zizmor.sh/configuration/
#
# TODO: can we remove the ignores here so that our workflows are more secure?
rules:
dangerous-triggers:
ignore:
- pr-comment.yaml
cache-poisoning:
ignore:
- build-docker.yml
- publish-playground.yml
- ty-ecosystem-analyzer.yaml
- ty-ecosystem-report.yaml
excessive-permissions:
# it's hard to test what the impact of removing these ignores would be
# without actually running the release workflow...
ignore:
- build-docker.yml
- publish-playground.yml
- publish-docs.yml
secrets-inherit:
# `cargo dist` makes extensive use of `secrets: inherit`,
# and we can't easily fix that until an upstream release changes that.
disable: true
template-injection:
ignore:
# like with `secrets-inherit`, `cargo dist` introduces some
# template injections. We've manually audited these usages for safety.
- release.yml

View File

@@ -35,6 +35,7 @@ repos:
rev: 0.7.22
hooks:
- id: mdformat
language: python # means renovate will also update `additional_dependencies`
additional_dependencies:
- mdformat-mkdocs==4.0.0
- mdformat-footnote==0.1.1
@@ -58,6 +59,7 @@ repos:
rev: 1.19.1
hooks:
- id: blacken-docs
language: python # means renovate will also update `additional_dependencies`
args: ["--pyi", "--line-length", "130"]
files: '^crates/.*/resources/mdtest/.*\.md'
exclude: |
@@ -99,8 +101,8 @@ repos:
# zizmor detects security vulnerabilities in GitHub Actions workflows.
# Additional configuration for the tool is found in `.github/zizmor.yml`
- repo: https://github.com/woodruffw/zizmor-pre-commit
rev: v1.11.0
- repo: https://github.com/zizmorcore/zizmor-pre-commit
rev: v1.16.0
hooks:
- id: zizmor

View File

@@ -1,5 +1,153 @@
# Changelog
## 0.14.4
Released on 2025-11-06.
### Preview features
- [formatter] Allow newlines after function headers without docstrings ([#21110](https://github.com/astral-sh/ruff/pull/21110))
- [formatter] Avoid extra parentheses for long `match` patterns with `as` captures ([#21176](https://github.com/astral-sh/ruff/pull/21176))
- \[`refurb`\] Expand fix safety for keyword arguments and `Decimal`s (`FURB164`) ([#21259](https://github.com/astral-sh/ruff/pull/21259))
- \[`refurb`\] Preserve argument ordering in autofix (`FURB103`) ([#20790](https://github.com/astral-sh/ruff/pull/20790))
### Bug fixes
- [server] Fix missing diagnostics for notebooks ([#21156](https://github.com/astral-sh/ruff/pull/21156))
- \[`flake8-bugbear`\] Ignore non-NFKC attribute names in `B009` and `B010` ([#21131](https://github.com/astral-sh/ruff/pull/21131))
- \[`refurb`\] Fix false negative for underscores before sign in `Decimal` constructor (`FURB157`) ([#21190](https://github.com/astral-sh/ruff/pull/21190))
- \[`ruff`\] Fix false positives on starred arguments (`RUF057`) ([#21256](https://github.com/astral-sh/ruff/pull/21256))
### Rule changes
- \[`airflow`\] extend deprecated argument `concurrency` in `airflow..DAG` (`AIR301`) ([#21220](https://github.com/astral-sh/ruff/pull/21220))
### Documentation
- Improve `extend` docs ([#21135](https://github.com/astral-sh/ruff/pull/21135))
- \[`flake8-comprehensions`\] Fix typo in `C416` documentation ([#21184](https://github.com/astral-sh/ruff/pull/21184))
- Revise Ruff setup instructions for Zed editor ([#20935](https://github.com/astral-sh/ruff/pull/20935))
### Other changes
- Make `ruff analyze graph` work with jupyter notebooks ([#21161](https://github.com/astral-sh/ruff/pull/21161))
### Contributors
- [@chirizxc](https://github.com/chirizxc)
- [@Lee-W](https://github.com/Lee-W)
- [@musicinmybrain](https://github.com/musicinmybrain)
- [@MichaReiser](https://github.com/MichaReiser)
- [@tjkuson](https://github.com/tjkuson)
- [@danparizher](https://github.com/danparizher)
- [@renovate](https://github.com/renovate)
- [@ntBre](https://github.com/ntBre)
- [@gauthsvenkat](https://github.com/gauthsvenkat)
- [@LoicRiegel](https://github.com/LoicRiegel)
## 0.14.3
Released on 2025-10-30.
### Preview features
- Respect `--output-format` with `--watch` ([#21097](https://github.com/astral-sh/ruff/pull/21097))
- \[`pydoclint`\] Fix false positive on explicit exception re-raising (`DOC501`, `DOC502`) ([#21011](https://github.com/astral-sh/ruff/pull/21011))
- \[`pyflakes`\] Revert to stable behavior if imports for module lie in alternate branches for `F401` ([#20878](https://github.com/astral-sh/ruff/pull/20878))
- \[`pylint`\] Implement `stop-iteration-return` (`PLR1708`) ([#20733](https://github.com/astral-sh/ruff/pull/20733))
- \[`ruff`\] Add support for additional eager conversion patterns (`RUF065`) ([#20657](https://github.com/astral-sh/ruff/pull/20657))
### Bug fixes
- Fix finding keyword range for clause header after statement ending with semicolon ([#21067](https://github.com/astral-sh/ruff/pull/21067))
- Fix syntax error false positive on nested alternative patterns ([#21104](https://github.com/astral-sh/ruff/pull/21104))
- \[`ISC001`\] Fix panic when string literals are unclosed ([#21034](https://github.com/astral-sh/ruff/pull/21034))
- \[`flake8-django`\] Apply `DJ001` to annotated fields ([#20907](https://github.com/astral-sh/ruff/pull/20907))
- \[`flake8-pyi`\] Fix `PYI034` to not trigger on metaclasses (`PYI034`) ([#20881](https://github.com/astral-sh/ruff/pull/20881))
- \[`flake8-type-checking`\] Fix `TC003` false positive with `future-annotations` ([#21125](https://github.com/astral-sh/ruff/pull/21125))
- \[`pyflakes`\] Fix false positive for `__class__` in lambda expressions within class definitions (`F821`) ([#20564](https://github.com/astral-sh/ruff/pull/20564))
- \[`pyupgrade`\] Fix false positive for `TypeVar` with default on Python \<3.13 (`UP046`,`UP047`) ([#21045](https://github.com/astral-sh/ruff/pull/21045))
### Rule changes
- Add missing docstring sections to the numpy list ([#20931](https://github.com/astral-sh/ruff/pull/20931))
- \[`airflow`\] Extend `airflow.models..Param` check (`AIR311`) ([#21043](https://github.com/astral-sh/ruff/pull/21043))
- \[`airflow`\] Warn that `airflow....DAG.create_dagrun` has been removed (`AIR301`) ([#21093](https://github.com/astral-sh/ruff/pull/21093))
- \[`refurb`\] Preserve digit separators in `Decimal` constructor (`FURB157`) ([#20588](https://github.com/astral-sh/ruff/pull/20588))
### Server
- Avoid sending an unnecessary "clear diagnostics" message for clients supporting pull diagnostics ([#21105](https://github.com/astral-sh/ruff/pull/21105))
### Documentation
- \[`flake8-bandit`\] Fix correct example for `S308` ([#21128](https://github.com/astral-sh/ruff/pull/21128))
### Other changes
- Clearer error message when `line-length` goes beyond threshold ([#21072](https://github.com/astral-sh/ruff/pull/21072))
### Contributors
- [@danparizher](https://github.com/danparizher)
- [@jvacek](https://github.com/jvacek)
- [@ntBre](https://github.com/ntBre)
- [@augustelalande](https://github.com/augustelalande)
- [@prakhar1144](https://github.com/prakhar1144)
- [@TaKO8Ki](https://github.com/TaKO8Ki)
- [@dylwil3](https://github.com/dylwil3)
- [@fatelei](https://github.com/fatelei)
- [@ShaharNaveh](https://github.com/ShaharNaveh)
- [@Lee-W](https://github.com/Lee-W)
## 0.14.2
Released on 2025-10-23.
### Preview features
- \[`flake8-gettext`\] Resolve qualified names and built-in bindings (`INT001`, `INT002`, `INT003`) ([#19045](https://github.com/astral-sh/ruff/pull/19045))
### Bug fixes
- Avoid reusing nested, interpolated quotes before Python 3.12 ([#20930](https://github.com/astral-sh/ruff/pull/20930))
- Catch syntax errors in nested interpolations before Python 3.12 ([#20949](https://github.com/astral-sh/ruff/pull/20949))
- \[`fastapi`\] Handle ellipsis defaults in `FAST002` autofix ([#20810](https://github.com/astral-sh/ruff/pull/20810))
- \[`flake8-simplify`\] Skip `SIM911` when unknown arguments are present ([#20697](https://github.com/astral-sh/ruff/pull/20697))
- \[`pyupgrade`\] Always parenthesize assignment expressions in fix for `f-string` (`UP032`) ([#21003](https://github.com/astral-sh/ruff/pull/21003))
- \[`pyupgrade`\] Fix `UP032` conversion for decimal ints with underscores ([#21022](https://github.com/astral-sh/ruff/pull/21022))
- \[`fastapi`\] Skip autofix for keyword and `__debug__` path params (`FAST003`) ([#20960](https://github.com/astral-sh/ruff/pull/20960))
### Rule changes
- \[`flake8-bugbear`\] Skip `B905` and `B912` for fewer than two iterables and no starred arguments ([#20998](https://github.com/astral-sh/ruff/pull/20998))
- \[`ruff`\] Use `DiagnosticTag` for more `pyflakes` and `pandas` rules ([#20801](https://github.com/astral-sh/ruff/pull/20801))
### CLI
- Improve JSON output from `ruff rule` ([#20168](https://github.com/astral-sh/ruff/pull/20168))
### Documentation
- Add source to testimonial ([#20971](https://github.com/astral-sh/ruff/pull/20971))
- Document when a rule was added ([#21035](https://github.com/astral-sh/ruff/pull/21035))
### Other changes
- [syntax-errors] Name is parameter and global ([#20426](https://github.com/astral-sh/ruff/pull/20426))
- [syntax-errors] Alternative `match` patterns bind different names ([#20682](https://github.com/astral-sh/ruff/pull/20682))
### Contributors
- [@hengky-kurniawan-1](https://github.com/hengky-kurniawan-1)
- [@ShalokShalom](https://github.com/ShalokShalom)
- [@robsdedude](https://github.com/robsdedude)
- [@LoicRiegel](https://github.com/LoicRiegel)
- [@TaKO8Ki](https://github.com/TaKO8Ki)
- [@dylwil3](https://github.com/dylwil3)
- [@11happy](https://github.com/11happy)
- [@ntBre](https://github.com/ntBre)
## 0.14.1
Released on 2025-10-16.

View File

@@ -280,6 +280,55 @@ Note that plugin-specific configuration options are defined in their own modules
Finally, regenerate the documentation and generated code with `cargo dev generate-all`.
### Opening a PR
After you finish your changes, the next step is to open a PR. By default, two
sections will be filled into the PR body: the summary and the test plan.
#### The summary
The summary is intended to give us as maintainers information about your PR.
This should typically include a link to the relevant issue(s) you're addressing
in your PR, as well as a summary of the issue and your approach to fixing it. If
you have any questions about your approach or design, or if you considered
alternative approaches, that can also be helpful to include.
AI can be helpful in generating both the code and summary of your PR, but a
successful contribution should still be carefully reviewed by you and the
summary editorialized before submitting a PR. A great summary is thorough but
also succinct and gives us the context we need to review your PR.
You can find examples of excellent issues and PRs by searching for the
[`great writeup`](https://github.com/astral-sh/ruff/issues?q=label%3A%22great%20writeup%22)
label.
#### The test plan
The test plan is likely to be shorter than the summary and can be as simple as
"Added new snapshot tests for `RUF123`," at least for rule bugs. For LSP or some
types of CLI changes, in particular, it can also be helpful to include
screenshots or recordings of your change in action.
#### Ecosystem report
After opening the PR, an ecosystem report will be run as part of CI. This shows
a diff of linter and formatter behavior before and after the changes in your PR.
Going through these changes and reporting your findings in the PR summary or an
additional comment help us to review your PR more efficiently. It's also a great
way to find new test cases to incorporate into your PR if you identify any
issues.
#### PR status
To help us know when your PR is ready for review again, please either move your
PR back to a draft while working on it (marking it ready for review afterwards
will ping the previous reviewers) or explicitly re-request a review. This helps
us to avoid re-reviewing a PR while you're still working on it and also to
prioritize PRs that are definitely ready for review.
You can also thumbs-up or mark as resolved any comments we leave to let us know
you addressed them.
## MkDocs
> [!NOTE]

527
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@ resolver = "2"
[workspace.package]
# Please update rustfmt.toml when bumping the Rust edition
edition = "2024"
rust-version = "1.88"
rust-version = "1.89"
homepage = "https://docs.astral.sh/ruff"
documentation = "https://docs.astral.sh/ruff"
repository = "https://github.com/astral-sh/ruff"
@@ -84,7 +84,7 @@ dashmap = { version = "6.0.1" }
dir-test = { version = "0.4.0" }
dunce = { version = "1.0.5" }
drop_bomb = { version = "0.1.5" }
etcetera = { version = "0.10.0" }
etcetera = { version = "0.11.0" }
fern = { version = "0.7.0" }
filetime = { version = "0.2.23" }
getrandom = { version = "0.3.1" }
@@ -103,7 +103,7 @@ hashbrown = { version = "0.16.0", default-features = false, features = [
"inline-more",
] }
heck = "0.5.0"
ignore = { version = "0.4.22" }
ignore = { version = "0.4.24" }
imara-diff = { version = "0.1.5" }
imperative = { version = "1.0.4" }
indexmap = { version = "2.6.0" }
@@ -124,7 +124,7 @@ lsp-server = { version = "0.7.6" }
lsp-types = { git = "https://github.com/astral-sh/lsp-types.git", rev = "3512a9f", features = [
"proposed",
] }
matchit = { version = "0.8.1" }
matchit = { version = "0.9.0" }
memchr = { version = "2.7.1" }
mimalloc = { version = "0.1.39" }
natord = { version = "1.0.9" }
@@ -146,13 +146,13 @@ regex-automata = { version = "0.4.9" }
rustc-hash = { version = "2.0.0" }
rustc-stable-hash = { version = "0.1.2" }
# When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml`
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "ef9f9329be6923acd050c8dddd172e3bc93e8051", default-features = false, features = [
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "05a9af7f554b64b8aadc2eeb6f2caf73d0408d09", default-features = false, features = [
"compact_str",
"macros",
"salsa_unstable",
"inventory",
] }
schemars = { version = "0.8.16" }
schemars = { version = "1.0.4" }
seahash = { version = "4.1.0" }
serde = { version = "1.0.197", features = ["derive"] }
serde-wasm-bindgen = { version = "0.6.4" }

View File

@@ -89,8 +89,7 @@ creator of [isort](https://github.com/PyCQA/isort):
> Just switched my first project to Ruff. Only one downside so far: it's so fast I couldn't believe
> it was working till I intentionally introduced some errors.
[**Tim Abbott**](https://github.com/astral-sh/ruff/issues/465#issuecomment-1317400028), lead
developer of [Zulip](https://github.com/zulip/zulip):
[**Tim Abbott**](https://github.com/zulip/zulip/pull/23431#issuecomment-1302557034), lead developer of [Zulip](https://github.com/zulip/zulip) (also [here](https://github.com/astral-sh/ruff/issues/465#issuecomment-1317400028)):
> This is just ridiculously fast... `ruff` is amazing.
@@ -148,8 +147,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
# For a specific version.
curl -LsSf https://astral.sh/ruff/0.14.1/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.14.1/install.ps1 | iex"
curl -LsSf https://astral.sh/ruff/0.14.4/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.14.4/install.ps1 | iex"
```
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
@@ -182,7 +181,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.14.1
rev: v0.14.4
hooks:
# Run the linter.
- id: ruff-check

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.14.1"
version = "0.14.4"
publish = true
authors = { workspace = true }
edition = { workspace = true }

View File

@@ -7,6 +7,8 @@ use std::sync::Arc;
use crate::commands::completions::config::{OptionString, OptionStringParser};
use anyhow::bail;
use clap::builder::Styles;
use clap::builder::styling::{AnsiColor, Effects};
use clap::builder::{TypedValueParser, ValueParserFactory};
use clap::{Parser, Subcommand, command};
use colored::Colorize;
@@ -78,6 +80,13 @@ impl GlobalConfigArgs {
}
}
// Configures Clap v3-style help menu colors
const STYLES: Styles = Styles::styled()
.header(AnsiColor::Green.on_default().effects(Effects::BOLD))
.usage(AnsiColor::Green.on_default().effects(Effects::BOLD))
.literal(AnsiColor::Cyan.on_default().effects(Effects::BOLD))
.placeholder(AnsiColor::Cyan.on_default());
#[derive(Debug, Parser)]
#[command(
author,
@@ -86,6 +95,7 @@ impl GlobalConfigArgs {
after_help = "For help with a specific command, see: `ruff help <command>`."
)]
#[command(version)]
#[command(styles = STYLES)]
pub struct Args {
#[command(subcommand)]
pub(crate) command: Command,
@@ -405,8 +415,13 @@ pub struct CheckCommand {
)]
pub statistics: bool,
/// Enable automatic additions of `noqa` directives to failing lines.
/// Optionally provide a reason to append after the codes.
#[arg(
long,
value_name = "REASON",
default_missing_value = "",
num_args = 0..=1,
require_equals = true,
// conflicts_with = "add_noqa",
conflicts_with = "show_files",
conflicts_with = "show_settings",
@@ -418,7 +433,7 @@ pub struct CheckCommand {
conflicts_with = "fix",
conflicts_with = "diff",
)]
pub add_noqa: bool,
pub add_noqa: Option<String>,
/// See the files Ruff will be run against with the current settings.
#[arg(
long,
@@ -1047,7 +1062,7 @@ Possible choices:
/// etc.).
#[expect(clippy::struct_excessive_bools)]
pub struct CheckArguments {
pub add_noqa: bool,
pub add_noqa: Option<String>,
pub diff: bool,
pub exit_non_zero_on_fix: bool,
pub exit_zero: bool,

View File

@@ -21,6 +21,7 @@ pub(crate) fn add_noqa(
files: &[PathBuf],
pyproject_config: &PyprojectConfig,
config_arguments: &ConfigArguments,
reason: Option<&str>,
) -> Result<usize> {
// Collect all the files to check.
let start = Instant::now();
@@ -76,7 +77,14 @@ pub(crate) fn add_noqa(
return None;
}
};
match add_noqa_to_path(path, package, &source_kind, source_type, &settings.linter) {
match add_noqa_to_path(
path,
package,
&source_kind,
source_type,
&settings.linter,
reason,
) {
Ok(count) => Some(count),
Err(e) => {
error!("Failed to add noqa to {}: {e}", path.display());

View File

@@ -7,6 +7,7 @@ use path_absolutize::CWD;
use ruff_db::system::{SystemPath, SystemPathBuf};
use ruff_graph::{Direction, ImportMap, ModuleDb, ModuleImports};
use ruff_linter::package::PackageRoot;
use ruff_linter::source_kind::SourceKind;
use ruff_linter::{warn_user, warn_user_once};
use ruff_python_ast::{PySourceType, SourceType};
use ruff_workspace::resolver::{ResolvedFile, match_exclusion, python_files_in_path};
@@ -127,10 +128,6 @@ pub(crate) fn analyze_graph(
},
Some(language) => PySourceType::from(language),
};
if matches!(source_type, PySourceType::Ipynb) {
debug!("Ignoring Jupyter notebook: {}", path.display());
continue;
}
// Convert to system paths.
let Ok(package) = package.map(SystemPathBuf::from_path_buf).transpose() else {
@@ -147,13 +144,34 @@ pub(crate) fn analyze_graph(
let root = root.clone();
let result = inner_result.clone();
scope.spawn(move |_| {
// Extract source code (handles both .py and .ipynb files)
let source_kind = match SourceKind::from_path(path.as_std_path(), source_type) {
Ok(Some(source_kind)) => source_kind,
Ok(None) => {
debug!("Skipping non-Python notebook: {path}");
return;
}
Err(err) => {
warn!("Failed to read source for {path}: {err}");
return;
}
};
let source_code = source_kind.source_code();
// Identify any imports via static analysis.
let mut imports =
ModuleImports::detect(&db, &path, package.as_deref(), string_imports)
.unwrap_or_else(|err| {
warn!("Failed to generate import map for {path}: {err}");
ModuleImports::default()
});
let mut imports = ModuleImports::detect(
&db,
source_code,
source_type,
&path,
package.as_deref(),
string_imports,
)
.unwrap_or_else(|err| {
warn!("Failed to generate import map for {path}: {err}");
ModuleImports::default()
});
debug!("Discovered {} imports for {}", imports.len(), path);

View File

@@ -370,7 +370,7 @@ pub(crate) fn format_source(
let line_index = LineIndex::from_source_text(unformatted);
let byte_range = range.to_text_range(unformatted, &line_index);
format_range(unformatted, byte_range, options).map(|formatted_range| {
let mut formatted = unformatted.to_string();
let mut formatted = unformatted.clone();
formatted.replace_range(
std::ops::Range::<usize>::from(formatted_range.source_range()),
formatted_range.as_code(),
@@ -879,19 +879,7 @@ impl From<&FormatCommandError> for Diagnostic {
| FormatCommandError::Write(_, source_error) => {
Diagnostic::new(DiagnosticId::Io, Severity::Error, source_error)
}
FormatCommandError::Format(_, format_module_error) => match format_module_error {
FormatModuleError::ParseError(parse_error) => Diagnostic::new(
DiagnosticId::InternalError,
Severity::Error,
&parse_error.error,
),
FormatModuleError::FormatError(format_error) => {
Diagnostic::new(DiagnosticId::InternalError, Severity::Error, format_error)
}
FormatModuleError::PrintError(print_error) => {
Diagnostic::new(DiagnosticId::InternalError, Severity::Error, print_error)
}
},
FormatCommandError::Format(_, format_module_error) => format_module_error.into(),
FormatCommandError::RangeFormatNotebook(_) => Diagnostic::new(
DiagnosticId::InvalidCliOption,
Severity::Error,

View File

@@ -16,6 +16,8 @@ struct LinterInfo {
prefix: &'static str,
name: &'static str,
#[serde(skip_serializing_if = "Option::is_none")]
url: Option<&'static str>,
#[serde(skip_serializing_if = "Option::is_none")]
categories: Option<Vec<LinterCategoryInfo>>,
}
@@ -50,6 +52,7 @@ pub(crate) fn linter(format: HelpFormat) -> Result<()> {
.map(|linter_info| LinterInfo {
prefix: linter_info.common_prefix(),
name: linter_info.name(),
url: linter_info.url(),
categories: linter_info.upstream_categories().map(|cats| {
cats.iter()
.map(|c| LinterCategoryInfo {

View File

@@ -7,6 +7,7 @@ use serde::{Serialize, Serializer};
use strum::IntoEnumIterator;
use ruff_linter::FixAvailability;
use ruff_linter::codes::RuleGroup;
use ruff_linter::registry::{Linter, Rule, RuleNamespace};
use crate::args::HelpFormat;
@@ -19,9 +20,12 @@ struct Explanation<'a> {
summary: &'a str,
message_formats: &'a [&'a str],
fix: String,
fix_availability: FixAvailability,
#[expect(clippy::struct_field_names)]
explanation: Option<&'a str>,
preview: bool,
status: RuleGroup,
source_location: SourceLocation,
}
impl<'a> Explanation<'a> {
@@ -36,8 +40,14 @@ impl<'a> Explanation<'a> {
summary: rule.message_formats()[0],
message_formats: rule.message_formats(),
fix,
fix_availability: rule.fixable(),
explanation: rule.explanation(),
preview: rule.is_preview(),
status: rule.group(),
source_location: SourceLocation {
file: rule.file(),
line: rule.line(),
},
}
}
}
@@ -122,3 +132,14 @@ pub(crate) fn rules(format: HelpFormat) -> Result<()> {
}
Ok(())
}
/// The location of the rule's implementation in the Ruff source tree, relative to the repository
/// root.
///
/// For most rules this will point to the `#[derive(ViolationMetadata)]` line above the rule's
/// struct.
#[derive(Serialize)]
struct SourceLocation {
file: &'static str,
line: u32,
}

View File

@@ -319,12 +319,20 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<Exi
warn_user!("Detected debug build without --no-cache.");
}
if cli.add_noqa {
if let Some(reason) = &cli.add_noqa {
if !fix_mode.is_generate() {
warn_user!("--fix is incompatible with --add-noqa.");
}
if reason.contains(['\n', '\r']) {
return Err(anyhow::anyhow!(
"--add-noqa <reason> cannot contain newline characters"
));
}
let reason_opt = (!reason.is_empty()).then_some(reason.as_str());
let modifications =
commands::add_noqa::add_noqa(&files, &pyproject_config, &config_arguments)?;
commands::add_noqa::add_noqa(&files, &pyproject_config, &config_arguments, reason_opt)?;
if modifications > 0 && config_arguments.log_level >= LogLevel::Default {
let s = if modifications == 1 { "" } else { "s" };
#[expect(clippy::print_stderr)]

View File

@@ -9,9 +9,7 @@ use itertools::{Itertools, iterate};
use ruff_linter::linter::FixTable;
use serde::Serialize;
use ruff_db::diagnostic::{
Diagnostic, DiagnosticFormat, DisplayDiagnosticConfig, DisplayDiagnostics, SecondaryCode,
};
use ruff_db::diagnostic::{Diagnostic, DisplayDiagnosticConfig, SecondaryCode};
use ruff_linter::fs::relativize_path;
use ruff_linter::logging::LogLevel;
use ruff_linter::message::{EmitterContext, render_diagnostics};
@@ -390,21 +388,18 @@ impl Printer {
let context = EmitterContext::new(&diagnostics.notebook_indexes);
let format = if preview {
DiagnosticFormat::Full
self.format
} else {
DiagnosticFormat::Concise
OutputFormat::Concise
};
let config = DisplayDiagnosticConfig::default()
.preview(preview)
.hide_severity(true)
.color(!cfg!(test) && colored::control::SHOULD_COLORIZE.should_colorize())
.with_show_fix_status(show_fix_status(self.fix_mode, fixables.as_ref()))
.format(format)
.with_fix_applicability(self.unsafe_fixes.required_applicability());
write!(
writer,
"{}",
DisplayDiagnostics::new(&context, &config, &diagnostics.inner)
)?;
.with_fix_applicability(self.unsafe_fixes.required_applicability())
.show_fix_diff(preview);
render_diagnostics(writer, format, config, &context, &diagnostics.inner)?;
}
writer.flush()?;

View File

@@ -653,3 +653,133 @@ fn venv() -> Result<()> {
Ok(())
}
#[test]
fn notebook_basic() -> Result<()> {
let tempdir = TempDir::new()?;
let root = ChildPath::new(tempdir.path());
root.child("ruff").child("__init__.py").write_str("")?;
root.child("ruff")
.child("a.py")
.write_str(indoc::indoc! {r#"
def helper():
pass
"#})?;
// Create a basic notebook with a simple import
root.child("notebook.ipynb").write_str(indoc::indoc! {r#"
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from ruff.a import helper"
]
}
],
"metadata": {
"language_info": {
"name": "python",
"version": "3.12.0"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
"#})?;
insta::with_settings!({
filters => INSTA_FILTERS.to_vec(),
}, {
assert_cmd_snapshot!(command().current_dir(&root), @r###"
success: true
exit_code: 0
----- stdout -----
{
"notebook.ipynb": [
"ruff/a.py"
],
"ruff/__init__.py": [],
"ruff/a.py": []
}
----- stderr -----
"###);
});
Ok(())
}
#[test]
fn notebook_with_magic() -> Result<()> {
let tempdir = TempDir::new()?;
let root = ChildPath::new(tempdir.path());
root.child("ruff").child("__init__.py").write_str("")?;
root.child("ruff")
.child("a.py")
.write_str(indoc::indoc! {r#"
def helper():
pass
"#})?;
// Create a notebook with IPython magic commands and imports
root.child("notebook.ipynb").write_str(indoc::indoc! {r#"
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%load_ext autoreload\n",
"%autoreload 2"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from ruff.a import helper"
]
}
],
"metadata": {
"language_info": {
"name": "python",
"version": "3.12.0"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
"#})?;
insta::with_settings!({
filters => INSTA_FILTERS.to_vec(),
}, {
assert_cmd_snapshot!(command().current_dir(&root), @r###"
success: true
exit_code: 0
----- stdout -----
{
"notebook.ipynb": [
"ruff/a.py"
],
"ruff/__init__.py": [],
"ruff/a.py": []
}
----- stderr -----
"###);
});
Ok(())
}

File diff suppressed because it is too large Load Diff

View File

@@ -1760,6 +1760,64 @@ from foo import ( # noqa: F401
Ok(())
}
#[test]
fn add_noqa_with_reason() -> Result<()> {
let fixture = CliTest::new()?;
fixture.write_file(
"test.py",
r#"import os
def foo():
x = 1
"#,
)?;
assert_cmd_snapshot!(fixture
.check_command()
.arg("--add-noqa=TODO: fix")
.arg("--select=F401,F841")
.arg("test.py"), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Added 2 noqa directives.
");
let content = fs::read_to_string(fixture.root().join("test.py"))?;
insta::assert_snapshot!(content, @r"
import os # noqa: F401 TODO: fix
def foo():
x = 1 # noqa: F841 TODO: fix
");
Ok(())
}
#[test]
fn add_noqa_with_newline_in_reason() -> Result<()> {
let fixture = CliTest::new()?;
fixture.write_file("test.py", "import os\n")?;
assert_cmd_snapshot!(fixture
.check_command()
.arg("--add-noqa=line1\nline2")
.arg("--select=F401")
.arg("test.py"), @r###"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
ruff failed
Cause: --add-noqa <reason> cannot contain newline characters
"###);
Ok(())
}
/// Infer `3.11` from `requires-python` in `pyproject.toml`.
#[test]
fn requires_python() -> Result<()> {

View File

@@ -15,6 +15,7 @@ use std::{
};
use tempfile::TempDir;
mod format;
mod lint;
const BIN_NAME: &str = "ruff";
@@ -57,6 +58,16 @@ impl CliTest {
Self::with_settings(|_, settings| settings)
}
pub(crate) fn with_files<'a>(
files: impl IntoIterator<Item = (&'a str, &'a str)>,
) -> anyhow::Result<Self> {
let case = Self::new()?;
for file in files {
case.write_file(file.0, file.1)?;
}
Ok(case)
}
pub(crate) fn with_settings(
setup_settings: impl FnOnce(&Path, insta::Settings) -> insta::Settings,
) -> Result<Self> {
@@ -174,4 +185,10 @@ impl CliTest {
command
}
pub(crate) fn format_command(&self) -> Command {
let mut command = self.command();
command.args(["format", "--no-cache"]);
command
}
}

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/format.rs
source: crates/ruff/tests/cli/format.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/format.rs
source: crates/ruff/tests/cli/format.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/format.rs
source: crates/ruff/tests/cli/format.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/format.rs
source: crates/ruff/tests/cli/format.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/format.rs
source: crates/ruff/tests/cli/format.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/format.rs
source: crates/ruff/tests/cli/format.rs
info:
program: ruff
args:
@@ -7,6 +7,7 @@ info:
- "--no-cache"
- "--output-format"
- grouped
- "--preview"
- "--check"
- input.py
---

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/format.rs
source: crates/ruff/tests/cli/format.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/format.rs
source: crates/ruff/tests/cli/format.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/format.rs
source: crates/ruff/tests/cli/format.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/format.rs
source: crates/ruff/tests/cli/format.rs
info:
program: ruff
args:
@@ -7,6 +7,7 @@ info:
- "--no-cache"
- "--output-format"
- pylint
- "--preview"
- "--check"
- input.py
---

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/format.rs
source: crates/ruff/tests/cli/format.rs
info:
program: ruff
args:

View File

@@ -1,5 +1,5 @@
---
source: crates/ruff/tests/format.rs
source: crates/ruff/tests/cli/format.rs
info:
program: ruff
args:

View File

@@ -951,6 +951,20 @@ fn rule_f401() {
assert_cmd_snapshot!(ruff_cmd().args(["rule", "F401"]));
}
#[test]
fn rule_f401_output_json() {
insta::with_settings!({filters => vec![
(r#"("file": ")[^"]+(",)"#, "$1<FILE>$2"),
]}, {
assert_cmd_snapshot!(ruff_cmd().args(["rule", "F401", "--output-format", "json"]));
});
}
#[test]
fn rule_f401_output_text() {
assert_cmd_snapshot!(ruff_cmd().args(["rule", "F401", "--output-format", "text"]));
}
#[test]
fn rule_invalid_rule_name() {
assert_cmd_snapshot!(ruff_cmd().args(["rule", "RUF404"]), @r"
@@ -965,6 +979,34 @@ fn rule_invalid_rule_name() {
");
}
#[test]
fn rule_invalid_rule_name_output_json() {
assert_cmd_snapshot!(ruff_cmd().args(["rule", "RUF404", "--output-format", "json"]), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: invalid value 'RUF404' for '[RULE]'
For more information, try '--help'.
");
}
#[test]
fn rule_invalid_rule_name_output_text() {
assert_cmd_snapshot!(ruff_cmd().args(["rule", "RUF404", "--output-format", "text"]), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: invalid value 'RUF404' for '[RULE]'
For more information, try '--help'.
");
}
#[test]
fn show_statistics() {
let mut cmd = RuffCheck::default()

View File

@@ -0,0 +1,38 @@
---
source: crates/ruff/tests/integration_test.rs
info:
program: ruff
args:
- rule
- F401
- "--output-format"
- json
---
success: true
exit_code: 0
----- stdout -----
{
"name": "unused-import",
"code": "F401",
"linter": "Pyflakes",
"summary": "`{name}` imported but unused; consider using `importlib.util.find_spec` to test for availability",
"message_formats": [
"`{name}` imported but unused; consider using `importlib.util.find_spec` to test for availability",
"`{name}` imported but unused; consider removing, adding to `__all__`, or using a redundant alias",
"`{name}` imported but unused"
],
"fix": "Fix is sometimes available.",
"fix_availability": "Sometimes",
"explanation": "## What it does\nChecks for unused imports.\n\n## Why is this bad?\nUnused imports add a performance overhead at runtime, and risk creating\nimport cycles. They also increase the cognitive load of reading the code.\n\nIf an import statement is used to check for the availability or existence\nof a module, consider using `importlib.util.find_spec` instead.\n\nIf an import statement is used to re-export a symbol as part of a module's\npublic interface, consider using a \"redundant\" import alias, which\ninstructs Ruff (and other tools) to respect the re-export, and avoid\nmarking it as unused, as in:\n\n```python\nfrom module import member as member\n```\n\nAlternatively, you can use `__all__` to declare a symbol as part of the module's\ninterface, as in:\n\n```python\n# __init__.py\nimport some_module\n\n__all__ = [\"some_module\"]\n```\n\n## Preview\nWhen [preview] is enabled (and certain simplifying assumptions\nare met), we analyze all import statements for a given module\nwhen determining whether an import is used, rather than simply\nthe last of these statements. This can result in both different and\nmore import statements being marked as unused.\n\nFor example, if a module consists of\n\n```python\nimport a\nimport a.b\n```\n\nthen both statements are marked as unused under [preview], whereas\nonly the second is marked as unused under stable behavior.\n\nAs another example, if a module consists of\n\n```python\nimport a.b\nimport a\n\na.b.foo()\n```\n\nthen a diagnostic will only be emitted for the first line under [preview],\nwhereas a diagnostic would only be emitted for the second line under\nstable behavior.\n\nNote that this behavior is somewhat subjective and is designed\nto conform to the developer's intuition rather than Python's actual\nexecution. To wit, the statement `import a.b` automatically executes\n`import a`, so in some sense `import a` is _always_ redundant\nin the presence of `import a.b`.\n\n\n## Fix safety\n\nFixes to remove unused imports are safe, except in `__init__.py` files.\n\nApplying fixes to `__init__.py` files is currently in preview. The fix offered depends on the\ntype of the unused import. Ruff will suggest a safe fix to export first-party imports with\neither a redundant alias or, if already present in the file, an `__all__` entry. If multiple\n`__all__` declarations are present, Ruff will not offer a fix. Ruff will suggest an unsafe fix\nto remove third-party and standard library imports -- the fix is unsafe because the module's\ninterface changes.\n\nSee [this FAQ section](https://docs.astral.sh/ruff/faq/#how-does-ruff-determine-which-of-my-imports-are-first-party-third-party-etc)\nfor more details on how Ruff\ndetermines whether an import is first or third-party.\n\n## Example\n\n```python\nimport numpy as np # unused import\n\n\ndef area(radius):\n return 3.14 * radius**2\n```\n\nUse instead:\n\n```python\ndef area(radius):\n return 3.14 * radius**2\n```\n\nTo check the availability of a module, use `importlib.util.find_spec`:\n\n```python\nfrom importlib.util import find_spec\n\nif find_spec(\"numpy\") is not None:\n print(\"numpy is installed\")\nelse:\n print(\"numpy is not installed\")\n```\n\n## Options\n- `lint.ignore-init-module-imports`\n- `lint.pyflakes.allowed-unused-imports`\n\n## References\n- [Python documentation: `import`](https://docs.python.org/3/reference/simple_stmts.html#the-import-statement)\n- [Python documentation: `importlib.util.find_spec`](https://docs.python.org/3/library/importlib.html#importlib.util.find_spec)\n- [Typing documentation: interface conventions](https://typing.python.org/en/latest/spec/distributing.html#library-interface-public-and-private-symbols)\n\n[preview]: https://docs.astral.sh/ruff/preview/\n",
"preview": false,
"status": {
"Stable": {
"since": "v0.0.18"
}
},
"source_location": {
"file": "<FILE>",
"line": 145
}
}
----- stderr -----

View File

@@ -0,0 +1,140 @@
---
source: crates/ruff/tests/integration_test.rs
info:
program: ruff
args:
- rule
- F401
- "--output-format"
- text
---
success: true
exit_code: 0
----- stdout -----
# unused-import (F401)
Derived from the **Pyflakes** linter.
Fix is sometimes available.
## What it does
Checks for unused imports.
## Why is this bad?
Unused imports add a performance overhead at runtime, and risk creating
import cycles. They also increase the cognitive load of reading the code.
If an import statement is used to check for the availability or existence
of a module, consider using `importlib.util.find_spec` instead.
If an import statement is used to re-export a symbol as part of a module's
public interface, consider using a "redundant" import alias, which
instructs Ruff (and other tools) to respect the re-export, and avoid
marking it as unused, as in:
```python
from module import member as member
```
Alternatively, you can use `__all__` to declare a symbol as part of the module's
interface, as in:
```python
# __init__.py
import some_module
__all__ = ["some_module"]
```
## Preview
When [preview] is enabled (and certain simplifying assumptions
are met), we analyze all import statements for a given module
when determining whether an import is used, rather than simply
the last of these statements. This can result in both different and
more import statements being marked as unused.
For example, if a module consists of
```python
import a
import a.b
```
then both statements are marked as unused under [preview], whereas
only the second is marked as unused under stable behavior.
As another example, if a module consists of
```python
import a.b
import a
a.b.foo()
```
then a diagnostic will only be emitted for the first line under [preview],
whereas a diagnostic would only be emitted for the second line under
stable behavior.
Note that this behavior is somewhat subjective and is designed
to conform to the developer's intuition rather than Python's actual
execution. To wit, the statement `import a.b` automatically executes
`import a`, so in some sense `import a` is _always_ redundant
in the presence of `import a.b`.
## Fix safety
Fixes to remove unused imports are safe, except in `__init__.py` files.
Applying fixes to `__init__.py` files is currently in preview. The fix offered depends on the
type of the unused import. Ruff will suggest a safe fix to export first-party imports with
either a redundant alias or, if already present in the file, an `__all__` entry. If multiple
`__all__` declarations are present, Ruff will not offer a fix. Ruff will suggest an unsafe fix
to remove third-party and standard library imports -- the fix is unsafe because the module's
interface changes.
See [this FAQ section](https://docs.astral.sh/ruff/faq/#how-does-ruff-determine-which-of-my-imports-are-first-party-third-party-etc)
for more details on how Ruff
determines whether an import is first or third-party.
## Example
```python
import numpy as np # unused import
def area(radius):
return 3.14 * radius**2
```
Use instead:
```python
def area(radius):
return 3.14 * radius**2
```
To check the availability of a module, use `importlib.util.find_spec`:
```python
from importlib.util import find_spec
if find_spec("numpy") is not None:
print("numpy is installed")
else:
print("numpy is not installed")
```
## Options
- `lint.ignore-init-module-imports`
- `lint.pyflakes.allowed-unused-imports`
## References
- [Python documentation: `import`](https://docs.python.org/3/reference/simple_stmts.html#the-import-statement)
- [Python documentation: `importlib.util.find_spec`](https://docs.python.org/3/library/importlib.html#importlib.util.find_spec)
- [Typing documentation: interface conventions](https://typing.python.org/en/latest/spec/distributing.html#library-interface-public-and-private-symbols)
[preview]: https://docs.astral.sh/ruff/preview/
----- stderr -----

View File

@@ -667,7 +667,7 @@ fn attrs(criterion: &mut Criterion) {
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY313,
},
100,
110,
);
bench_project(&benchmark, criterion);
@@ -684,7 +684,7 @@ fn anyio(criterion: &mut Criterion) {
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY313,
},
100,
150,
);
bench_project(&benchmark, criterion);

View File

@@ -146,7 +146,7 @@ static FREQTRADE: Benchmark = Benchmark::new(
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
400,
525,
);
static PANDAS: Benchmark = Benchmark::new(
@@ -210,7 +210,7 @@ static TANJUN: Benchmark = Benchmark::new(
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
100,
320,
);
static STATIC_FRAME: Benchmark = Benchmark::new(
@@ -226,7 +226,7 @@ static STATIC_FRAME: Benchmark = Benchmark::new(
max_dep_date: "2025-08-09",
python_version: PythonVersion::PY311,
},
630,
800,
);
#[track_caller]

View File

@@ -112,16 +112,16 @@ impl std::fmt::Display for Diff<'_> {
// `None`, indicating a regular script file, all the lines will be in one "cell" under the
// `None` key.
let cells = if let Some(notebook_index) = &self.notebook_index {
let mut last_cell = OneIndexed::MIN;
let mut last_cell_index = OneIndexed::MIN;
let mut cells: Vec<(Option<OneIndexed>, TextSize)> = Vec::new();
for (row, cell) in notebook_index.iter() {
if cell != last_cell {
let offset = source_code.line_start(row);
cells.push((Some(last_cell), offset));
last_cell = cell;
for cell in notebook_index.iter() {
if cell.cell_index() != last_cell_index {
let offset = source_code.line_start(cell.start_row());
cells.push((Some(last_cell_index), offset));
last_cell_index = cell.cell_index();
}
}
cells.push((Some(last_cell), source_text.text_len()));
cells.push((Some(last_cell_index), source_text.text_len()));
cells
} else {
vec![(None, source_text.text_len())]

View File

@@ -470,6 +470,17 @@ impl File {
self.source_type(db).is_stub()
}
/// Returns `true` if the file is an `__init__.pyi`
pub fn is_package_stub(self, db: &dyn Db) -> bool {
self.path(db).as_str().ends_with("__init__.pyi")
}
/// Returns `true` if the file is an `__init__.pyi`
pub fn is_package(self, db: &dyn Db) -> bool {
let path = self.path(db).as_str();
path.ends_with("__init__.pyi") || path.ends_with("__init__.py")
}
pub fn source_type(self, db: &dyn Db) -> PySourceType {
match self.path(db) {
FilePath::System(path) => path

View File

@@ -200,7 +200,12 @@ impl System for OsSystem {
/// The walker ignores files according to [`ignore::WalkBuilder::standard_filters`]
/// when setting [`WalkDirectoryBuilder::standard_filters`] to true.
fn walk_directory(&self, path: &SystemPath) -> WalkDirectoryBuilder {
WalkDirectoryBuilder::new(path, OsDirectoryWalker {})
WalkDirectoryBuilder::new(
path,
OsDirectoryWalker {
cwd: self.current_directory().to_path_buf(),
},
)
}
fn glob(
@@ -454,7 +459,9 @@ struct ListedDirectory {
}
#[derive(Debug)]
struct OsDirectoryWalker;
struct OsDirectoryWalker {
cwd: SystemPathBuf,
}
impl DirectoryWalker for OsDirectoryWalker {
fn walk(
@@ -473,6 +480,7 @@ impl DirectoryWalker for OsDirectoryWalker {
};
let mut builder = ignore::WalkBuilder::new(first.as_std_path());
builder.current_dir(self.cwd.as_std_path());
builder.standard_filters(standard_filters);
builder.hidden(hidden);

View File

@@ -723,10 +723,11 @@ impl ruff_cache::CacheKey for SystemPathBuf {
/// A slice of a virtual path on [`System`](super::System) (akin to [`str`]).
#[repr(transparent)]
#[derive(Eq, PartialEq, Hash, PartialOrd, Ord)]
pub struct SystemVirtualPath(str);
impl SystemVirtualPath {
pub fn new(path: &str) -> &SystemVirtualPath {
pub const fn new(path: &str) -> &SystemVirtualPath {
// SAFETY: SystemVirtualPath is marked as #[repr(transparent)] so the conversion from a
// *const str to a *const SystemVirtualPath is valid.
unsafe { &*(path as *const str as *const SystemVirtualPath) }
@@ -767,8 +768,8 @@ pub struct SystemVirtualPathBuf(String);
impl SystemVirtualPathBuf {
#[inline]
pub fn as_path(&self) -> &SystemVirtualPath {
SystemVirtualPath::new(&self.0)
pub const fn as_path(&self) -> &SystemVirtualPath {
SystemVirtualPath::new(self.0.as_str())
}
}
@@ -852,6 +853,12 @@ impl ruff_cache::CacheKey for SystemVirtualPathBuf {
}
}
impl Borrow<SystemVirtualPath> for SystemVirtualPathBuf {
fn borrow(&self) -> &SystemVirtualPath {
self.as_path()
}
}
/// Deduplicates identical paths and removes nested paths.
///
/// # Examples

View File

@@ -8,6 +8,7 @@ use std::path::PathBuf;
use anyhow::Result;
use itertools::Itertools;
use regex::{Captures, Regex};
use ruff_linter::codes::RuleGroup;
use strum::IntoEnumIterator;
use ruff_linter::FixAvailability;
@@ -31,6 +32,47 @@ pub(crate) fn main(args: &Args) -> Result<()> {
let _ = writeln!(&mut output, "# {} ({})", rule.name(), rule.noqa_code());
let status_text = match rule.group() {
RuleGroup::Stable { since } => {
format!(
r#"Added in <a href="https://github.com/astral-sh/ruff/releases/tag/{since}">{since}</a>"#
)
}
RuleGroup::Preview { since } => {
format!(
r#"Preview (since <a href="https://github.com/astral-sh/ruff/releases/tag/{since}">{since}</a>)"#
)
}
RuleGroup::Deprecated { since } => {
format!(
r#"Deprecated (since <a href="https://github.com/astral-sh/ruff/releases/tag/{since}">{since}</a>)"#
)
}
RuleGroup::Removed { since } => {
format!(
r#"Removed (since <a href="https://github.com/astral-sh/ruff/releases/tag/{since}">{since}</a>)"#
)
}
};
let _ = writeln!(
&mut output,
r#"<small>
{status_text} ·
<a href="https://github.com/astral-sh/ruff/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20(%27{encoded_name}%27%20OR%20{rule_code})" target="_blank">Related issues</a> ·
<a href="https://github.com/astral-sh/ruff/blob/main/{file}#L{line}" target="_blank">View source</a>
</small>
"#,
encoded_name =
url::form_urlencoded::byte_serialize(rule.name().as_str().as_bytes())
.collect::<String>(),
rule_code = rule.noqa_code(),
file =
url::form_urlencoded::byte_serialize(rule.file().replace('\\', "/").as_bytes())
.collect::<String>(),
line = rule.line(),
);
let (linter, _) = Linter::parse_code(&rule.noqa_code().to_string()).unwrap();
if linter.url().is_some() {
let common_prefix: String = match linter.common_prefix() {

View File

@@ -3,7 +3,7 @@ use std::path::PathBuf;
use anyhow::{Result, bail};
use pretty_assertions::StrComparison;
use schemars::schema_for;
use schemars::generate::SchemaSettings;
use crate::ROOT_DIR;
use crate::generate_all::{Mode, REGENERATE_ALL_COMMAND};
@@ -17,7 +17,9 @@ pub(crate) struct Args {
}
pub(crate) fn main(args: &Args) -> Result<()> {
let schema = schema_for!(Options);
let settings = SchemaSettings::draft07();
let generator = settings.into_generator();
let schema = generator.into_root_schema_for::<Options>();
let schema_string = serde_json::to_string_pretty(&schema).unwrap();
let filename = "ruff.schema.json";
let schema_path = PathBuf::from(ROOT_DIR).join(filename);

View File

@@ -62,7 +62,7 @@ fn generate_set(output: &mut String, set: Set, parents: &mut Vec<Set>) {
generate_set(
output,
Set::Named {
name: set_name.to_string(),
name: set_name.clone(),
set: *sub_set,
},
parents,

View File

@@ -32,20 +32,24 @@ fn generate_table(table_out: &mut String, rules: impl IntoIterator<Item = Rule>,
table_out.push('\n');
for rule in rules {
let status_token = match rule.group() {
RuleGroup::Removed => {
RuleGroup::Removed { since } => {
format!(
"<span {SYMBOL_STYLE} title='Rule has been removed'>{REMOVED_SYMBOL}</span>"
"<span {SYMBOL_STYLE} title='Rule was removed in {since}'>{REMOVED_SYMBOL}</span>"
)
}
RuleGroup::Deprecated => {
RuleGroup::Deprecated { since } => {
format!(
"<span {SYMBOL_STYLE} title='Rule has been deprecated'>{WARNING_SYMBOL}</span>"
"<span {SYMBOL_STYLE} title='Rule has been deprecated since {since}'>{WARNING_SYMBOL}</span>"
)
}
RuleGroup::Preview => {
format!("<span {SYMBOL_STYLE} title='Rule is in preview'>{PREVIEW_SYMBOL}</span>")
RuleGroup::Preview { since } => {
format!(
"<span {SYMBOL_STYLE} title='Rule has been in preview since {since}'>{PREVIEW_SYMBOL}</span>"
)
}
RuleGroup::Stable { since } => {
format!("<span {SYMBOL_STYLE} title='Rule has been stable since {since}'></span>")
}
RuleGroup::Stable => format!("<span {SYMBOL_STYLE}></span>"),
};
let fix_token = match rule.fixable() {

View File

@@ -104,7 +104,7 @@ fn generate_set(output: &mut String, set: Set, parents: &mut Vec<Set>) {
generate_set(
output,
Set::Named {
name: set_name.to_string(),
name: set_name.clone(),
set: *sub_set,
},
parents,

View File

@@ -3,7 +3,7 @@ use std::path::PathBuf;
use anyhow::{Result, bail};
use pretty_assertions::StrComparison;
use schemars::schema_for;
use schemars::generate::SchemaSettings;
use crate::ROOT_DIR;
use crate::generate_all::{Mode, REGENERATE_ALL_COMMAND};
@@ -17,7 +17,9 @@ pub(crate) struct Args {
}
pub(crate) fn main(args: &Args) -> Result<()> {
let schema = schema_for!(Options);
let settings = SchemaSettings::draft07();
let generator = settings.into_generator();
let schema = generator.into_root_schema_for::<Options>();
let schema_string = serde_json::to_string_pretty(&schema).unwrap();
let filename = "ty.schema.json";
let schema_path = PathBuf::from(ROOT_DIR).join(filename);

View File

@@ -1006,7 +1006,7 @@ impl<Context> std::fmt::Debug for Align<'_, Context> {
/// Block indents indent a block of code, such as in a function body, and therefore insert a line
/// break before and after the content.
///
/// Doesn't create an indentation if the passed in content is [`FormatElement.is_empty`].
/// Doesn't create an indentation if the passed in content is empty.
///
/// # Examples
///

View File

@@ -487,7 +487,7 @@ pub trait FormatElements {
/// Represents the width by adding 1 to the actual width so that the width can be represented by a [`NonZeroU32`],
/// allowing [`TextWidth`] or [`Option<Width>`] fit in 4 bytes rather than 8.
///
/// This means that 2^32 can not be precisely represented and instead has the same value as 2^32-1.
/// This means that 2^32 cannot be precisely represented and instead has the same value as 2^32-1.
/// This imprecision shouldn't matter in practice because either text are longer than any configured line width
/// and thus, the text should break.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]

View File

@@ -3,8 +3,9 @@ use std::collections::{BTreeMap, BTreeSet};
use anyhow::Result;
use ruff_db::system::{SystemPath, SystemPathBuf};
use ruff_python_ast::PySourceType;
use ruff_python_ast::helpers::to_module_path;
use ruff_python_parser::{Mode, ParseOptions, parse};
use ruff_python_parser::{ParseOptions, parse};
use crate::collector::Collector;
pub use crate::db::ModuleDb;
@@ -24,13 +25,14 @@ impl ModuleImports {
/// Detect the [`ModuleImports`] for a given Python file.
pub fn detect(
db: &ModuleDb,
source: &str,
source_type: PySourceType,
path: &SystemPath,
package: Option<&SystemPath>,
string_imports: StringImports,
) -> Result<Self> {
// Read and parse the source code.
let source = std::fs::read_to_string(path)?;
let parsed = parse(&source, ParseOptions::from(Mode::Module))?;
// Parse the source code.
let parsed = parse(source, ParseOptions::from(source_type))?;
let module_path =
package.and_then(|package| to_module_path(package.as_std_path(), path.as_std_path()));

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_linter"
version = "0.14.1"
version = "0.14.4"
publish = false
authors = { workspace = true }
edition = { workspace = true }

View File

@@ -22,6 +22,7 @@ DAG(dag_id="class_schedule_interval", schedule_interval="@hourly")
DAG(dag_id="class_timetable", timetable=NullTimetable())
DAG(dag_id="class_concurrency", concurrency=12)
DAG(dag_id="class_fail_stop", fail_stop=True)

View File

@@ -10,6 +10,7 @@ from airflow.datasets import (
)
from airflow.datasets.manager import DatasetManager
from airflow.lineage.hook import DatasetLineageInfo, HookLineageCollector
from airflow.models.dag import DAG
from airflow.providers.amazon.aws.auth_manager.aws_auth_manager import AwsAuthManager
from airflow.providers.apache.beam.hooks import BeamHook, NotAir302HookError
from airflow.providers.google.cloud.secrets.secret_manager import (
@@ -20,6 +21,7 @@ from airflow.providers_manager import ProvidersManager
from airflow.secrets.base_secrets import BaseSecretsBackend
from airflow.secrets.local_filesystem import LocalFilesystemBackend
# airflow.Dataset
dataset_from_root = DatasetFromRoot()
dataset_from_root.iter_datasets()
@@ -56,6 +58,10 @@ hlc.add_input_dataset()
hlc.add_output_dataset()
hlc.collected_datasets()
# airflow.models.dag.DAG
test_dag = DAG(dag_id="test_dag")
test_dag.create_dagrun()
# airflow.providers.amazon.auth_manager.aws_auth_manager
aam = AwsAuthManager()
aam.is_authorized_dataset()
@@ -96,3 +102,15 @@ base_secret_backend.get_connections()
# airflow.secrets.local_filesystem
lfb = LocalFilesystemBackend()
lfb.get_connections()
from airflow.models import DAG
# airflow.DAG
test_dag = DAG(dag_id="test_dag")
test_dag.create_dagrun()
from airflow import DAG
# airflow.DAG
test_dag = DAG(dag_id="test_dag")
test_dag.create_dagrun()

View File

@@ -91,10 +91,20 @@ get_unique_task_id()
task_decorator_factory()
from airflow.models import Param
from airflow.models import DagParam, Param, ParamsDict
# airflow.models
Param()
DagParam()
ParamsDict()
from airflow.models.param import DagParam, Param, ParamsDict
# airflow.models.param
Param()
DagParam()
ParamsDict()
from airflow.sensors.base import (

View File

@@ -0,0 +1,87 @@
"""Test FAST002 ellipsis handling."""
from fastapi import Body, Cookie, FastAPI, Header, Query
app = FastAPI()
# Cases that should be fixed - ellipsis should be removed
@app.get("/test1")
async def test_ellipsis_query(
# This should become: param: Annotated[str, Query(description="Test param")]
param: str = Query(..., description="Test param"),
) -> str:
return param
@app.get("/test2")
async def test_ellipsis_header(
# This should become: auth: Annotated[str, Header(description="Auth header")]
auth: str = Header(..., description="Auth header"),
) -> str:
return auth
@app.post("/test3")
async def test_ellipsis_body(
# This should become: data: Annotated[dict, Body(description="Request body")]
data: dict = Body(..., description="Request body"),
) -> dict:
return data
@app.get("/test4")
async def test_ellipsis_cookie(
# This should become: session: Annotated[str, Cookie(description="Session ID")]
session: str = Cookie(..., description="Session ID"),
) -> str:
return session
@app.get("/test5")
async def test_simple_ellipsis(
# This should become: id: Annotated[str, Query()]
id: str = Query(...),
) -> str:
return id
@app.get("/test6")
async def test_multiple_kwargs_with_ellipsis(
# This should become: param: Annotated[str, Query(description="Test", min_length=1, max_length=10)]
param: str = Query(..., description="Test", min_length=1, max_length=10),
) -> str:
return param
# Cases with actual default values - these should preserve the default
@app.get("/test7")
async def test_with_default_value(
# This should become: param: Annotated[str, Query(description="Test")] = "default"
param: str = Query("default", description="Test"),
) -> str:
return param
@app.get("/test8")
async def test_with_default_none(
# This should become: param: Annotated[str | None, Query(description="Test")] = None
param: str | None = Query(None, description="Test"),
) -> str:
return param or "empty"
@app.get("/test9")
async def test_mixed_parameters(
# First param should be fixed with default preserved
optional_param: str = Query("default", description="Optional"),
# Second param should not be fixed because of the preceding default
required_param: str = Query(..., description="Required"),
# Third param should be fixed with default preserved
another_optional_param: int = Query(42, description="Another optional"),
) -> str:
return f"{required_param}-{optional_param}-{another_optional_param}"

View File

@@ -256,3 +256,12 @@ async def f4():
@app.get("/f5/{param: int}")
async def f5():
return locals()
# https://github.com/astral-sh/ruff/issues/20941
@app.get("/imports/{import}")
async def get_import():
...
@app.get("/debug/{__debug__}")
async def get_debug():
...

View File

@@ -70,3 +70,12 @@ builtins.getattr(foo, "bar")
# Regression test for: https://github.com/astral-sh/ruff/issues/18353
setattr(foo, "__debug__", 0)
# Regression test for: https://github.com/astral-sh/ruff/issues/21126
# Non-NFKC attribute names should be marked as unsafe. Python normalizes identifiers in
# attribute access (obj.attr) using NFKC, but does not normalize string
# arguments passed to getattr/setattr. Rewriting `getattr(ns, "ſ")` to
# `ns.ſ` would be interpreted as `ns.s` at runtime, changing behavior.
# Example: the long s character "ſ" normalizes to "s" under NFKC.
getattr(foo, "ſ")
setattr(foo, "ſ", 1)

View File

@@ -1,12 +1,10 @@
from itertools import count, cycle, repeat
# Errors
zip()
zip(range(3))
zip("a", "b")
zip("a", "b", *zip("c"))
zip(zip("a"), strict=False)
zip(zip("a", strict=True))
zip(zip("a", "b"), strict=False)
zip(zip("a", strict=True),"b")
# OK
zip(range(3), strict=True)
@@ -27,3 +25,10 @@ zip([1, 2, 3], repeat(1, times=4))
import builtins
# Still an error even though it uses the qualified name
builtins.zip([1, 2, 3])
# Regression https://github.com/astral-sh/ruff/issues/20997
# Ok
zip()
zip(range(3))
# Error
zip(*lot_of_iterators)

View File

@@ -31,3 +31,6 @@ map(lambda x, y: x + y, [1, 2, 3], cycle([1, 2, 3]))
map(lambda x, y: x + y, [1, 2, 3], repeat(1))
map(lambda x, y: x + y, [1, 2, 3], repeat(1, times=None))
map(lambda x, y: x + y, [1, 2, 3], count())
# Regression https://github.com/astral-sh/ruff/issues/20997
map(f, *lots_of_iterators)

View File

@@ -46,3 +46,9 @@ class CorrectModel(models.Model):
max_length=255, null=True, blank=True, unique=True
)
urlfieldu = models.URLField(max_length=255, null=True, blank=True, unique=True)
class IncorrectModelWithSimpleAnnotations(models.Model):
charfield: models.CharField = models.CharField(max_length=255, null=True)
textfield: models.TextField = models.TextField(max_length=255, null=True)
slugfield: models.SlugField = models.SlugField(max_length=255, null=True)

View File

@@ -2,3 +2,52 @@ _(f"{'value'}")
# Don't trigger for t-strings
_(t"{'value'}")
gettext(f"{'value'}")
ngettext(f"{'value'}", f"{'values'}", 2)
_gettext(f"{'value'}") # no lint
gettext_fn(f"{'value'}") # no lint
# https://github.com/astral-sh/ruff/issues/19028 - import aliases
import gettext as gettext_mod
from gettext import (
gettext as gettext_fn,
ngettext as ngettext_fn,
)
name = "Guido"
gettext_mod.gettext(f"Hello, {name}!")
gettext_mod.ngettext(f"Hello, {name}!", f"Hello, {name}s!", 2)
gettext_fn(f"Hello, {name}!")
ngettext_fn(f"Hello, {name}!", f"Hello, {name}s!", 2)
# https://github.com/astral-sh/ruff/issues/19028 - deferred translations
def _(message): return message
animals = [_(f"{'mollusk'}"),
_(f"{'albatross'}"),
_(f"{'rat'}"),
_(f"{'penguin'}"),
_(f"{'python'}"),]
del _
for a in animals:
print(_(a))
print(_(f"{a}"))
# https://github.com/astral-sh/ruff/issues/19028
# - installed translations/i18n functions dynamically assigned to builtins
import builtins
gettext_mod.install("domain")
builtins.__dict__["gettext"] = gettext_fn
builtins._(f"{'value'}")
builtins.gettext(f"{'value'}")
builtins.ngettext(f"{'value'}", f"{'values'}", 2)

View File

@@ -1 +1,49 @@
_("{}".format("line"))
gettext("{}".format("line"))
ngettext("{}".format("line"), "{}".format("lines"), 2)
_gettext("{}".format("line")) # no lint
gettext_fn("{}".format("line")) # no lint
# https://github.com/astral-sh/ruff/issues/19028
import gettext as gettext_mod
from gettext import (
gettext as gettext_fn,
ngettext as ngettext_fn,
)
name = "Guido"
gettext_mod.gettext("Hello, {}!".format(name))
gettext_mod.ngettext("Hello, {}!".format(name), "Hello, {}s!".format(name), 2)
gettext_fn("Hello, {}!".format(name))
ngettext_fn("Hello, {}!".format(name), "Hello, {}s!".format(name), 2)
# https://github.com/astral-sh/ruff/issues/19028 - deferred translations
def _(message): return message
animals = [_("{}".format("mollusk")),
_("{}".format("albatross")),
_("{}".format("rat")),
_("{}".format("penguin")),
_("{}".format("python")),]
del _
for a in animals:
print(_(a))
print(_("{}".format(a)))
# https://github.com/astral-sh/ruff/issues/19028
# - installed translations/i18n functions dynamically assigned to builtins
import builtins
gettext_mod.install("domain")
builtins.__dict__["gettext"] = gettext_fn
builtins._("{}".format("line"))
builtins.gettext("{}".format("line"))
builtins.ngettext("{}".format("line"), "{}".format("lines"), 2)

View File

@@ -1 +1,49 @@
_("%s" % "line")
gettext("%s" % "line")
ngettext("%s" % "line", "%s" % "lines", 2)
_gettext("%s" % "line") # no lint
gettext_fn("%s" % "line") # no lint
# https://github.com/astral-sh/ruff/issues/19028
import gettext as gettext_mod
from gettext import (
gettext as gettext_fn,
ngettext as ngettext_fn,
)
name = "Guido"
gettext_mod.gettext("Hello, %s!" % name)
gettext_mod.ngettext("Hello, %s!" % name, "Hello, %ss!" % name, 2)
gettext_fn("Hello, %s!" % name)
ngettext_fn("Hello, %s!" % name, "Hello, %ss!" % name, 2)
# https://github.com/astral-sh/ruff/issues/19028 - deferred translations
def _(message): return message
animals = [_("%s" %"mollusk"),
_("%s" %"albatross"),
_("%s" %"rat"),
_("%s" %"penguin"),
_("%s" %"python"),]
del _
for a in animals:
print(_(a))
print(_("%s" % a))
# https://github.com/astral-sh/ruff/issues/19028
# - installed translations/i18n functions dynamically assigned to builtins
import builtins
gettext_mod.install("domain")
builtins.__dict__["gettext"] = gettext_fn
builtins._("%s" % "line")
builtins.gettext("%s" % "line")
builtins.ngettext("%s" % "line", "%s" % "lines", 2)

View File

@@ -0,0 +1,7 @@
# Regression test for https://github.com/astral-sh/ruff/issues/21023
'' '
"" ""
'' '' '
"" "" "
f"" f"
f"" f"" f"

View File

@@ -359,3 +359,29 @@ class Generic5(list[PotentialTypeVar]):
def __new__(cls: type[Generic5]) -> Generic5: ...
def __enter__(self: Generic5) -> Generic5: ...
# Test cases based on issue #20781 - metaclasses that triggers IsMetaclass::Maybe
class MetaclassInWhichSelfCannotBeUsed5(type(Protocol)):
def __new__(
cls, name: str, bases: tuple[type[Any], ...], attrs: dict[str, Any], **kwargs: Any
) -> MetaclassInWhichSelfCannotBeUsed5:
new_class = super().__new__(cls, name, bases, attrs, **kwargs)
return new_class
import django.db.models.base
class MetaclassInWhichSelfCannotBeUsed6(django.db.models.base.ModelBase):
def __new__(cls, name: str, bases: tuple[Any, ...], attrs: dict[str, Any], **kwargs: Any) -> MetaclassInWhichSelfCannotBeUsed6:
...
class MetaclassInWhichSelfCannotBeUsed7(django.db.models.base.ModelBase):
def __new__(cls, /, name: str, bases: tuple[object, ...], attrs: dict[str, object], **kwds: object) -> MetaclassInWhichSelfCannotBeUsed7:
...
class MetaclassInWhichSelfCannotBeUsed8(django.db.models.base.ModelBase):
def __new__(cls, name: builtins.str, bases: tuple, attributes: dict, /, **kw) -> MetaclassInWhichSelfCannotBeUsed8:
...

View File

@@ -252,3 +252,28 @@ from some_module import PotentialTypeVar
class Generic5(list[PotentialTypeVar]):
def __new__(cls: type[Generic5]) -> Generic5: ...
def __enter__(self: Generic5) -> Generic5: ...
# Test case based on issue #20781 - metaclass that triggers IsMetaclass::Maybe
class MetaclassInWhichSelfCannotBeUsed5(type(Protocol)):
def __new__(
cls, name: str, bases: tuple[type[Any], ...], attrs: dict[str, Any], **kwargs: Any
) -> MetaclassInWhichSelfCannotBeUsed5: ...
import django.db.models.base
class MetaclassInWhichSelfCannotBeUsed6(django.db.models.base.ModelBase):
def __new__(cls, name: str, bases: tuple[Any, ...], attrs: dict[str, Any], **kwargs: Any) -> MetaclassInWhichSelfCannotBeUsed6:
...
class MetaclassInWhichSelfCannotBeUsed7(django.db.models.base.ModelBase):
def __new__(cls, /, name: str, bases: tuple[object, ...], attrs: dict[str, object], **kwds: object) -> MetaclassInWhichSelfCannotBeUsed7:
...
class MetaclassInWhichSelfCannotBeUsed8(django.db.models.base.ModelBase):
def __new__(cls, name: builtins.str, bases: tuple, attributes: dict, /, **kw) -> MetaclassInWhichSelfCannotBeUsed8:
...

View File

@@ -204,3 +204,15 @@ x = 1
print(f"{x=}" or "bar") # SIM222
(lambda: 1) or True # SIM222
(i for i in range(1)) or "bar" # SIM222
# https://github.com/astral-sh/ruff/issues/21136
def get_items():
return tuple(item for item in Item.objects.all()) or None # OK
def get_items_list():
return tuple([item for item in items]) or None # OK
def get_items_set():
return tuple({item for item in items}) or None # OK

View File

@@ -34,3 +34,7 @@ def foo():
# https://github.com/astral-sh/ruff/issues/18776
flag_stars = {}
for country, stars in(zip)(flag_stars.keys(), flag_stars.values()):...
# Regression test for https://github.com/astral-sh/ruff/issues/18778
d = {}
for country, stars in zip(d.keys(*x), d.values("hello")):...

View File

@@ -14,3 +14,14 @@ def f():
import os
print(os)
# regression test for https://github.com/astral-sh/ruff/issues/21121
from dataclasses import KW_ONLY, dataclass
@dataclass
class DataClass:
a: int
_: KW_ONLY # should be an exception to TC003, even with future-annotations
b: int

View File

@@ -370,3 +370,22 @@ class Foo:
The flag converter instance with all flags parsed.
"""
return
# OK
def baz(x: int) -> int:
"""
Show a `Warnings` DOC102 false positive.
Parameters
----------
x : int
Warnings
--------
This function demonstrates a DOC102 false positive
Returns
-------
int
"""
return x

View File

@@ -81,3 +81,55 @@ def calculate_speed(distance: float, time: float) -> float:
except TypeError:
print("Not a number? Shame on you!")
raise
# This should NOT trigger DOC502 because OSError is explicitly re-raised
def f():
"""Do nothing.
Raises:
OSError: If the OS errors.
"""
try:
pass
except OSError as e:
raise e
# This should NOT trigger DOC502 because OSError is explicitly re-raised with from None
def g():
"""Do nothing.
Raises:
OSError: If the OS errors.
"""
try:
pass
except OSError as e:
raise e from None
# This should NOT trigger DOC502 because ValueError is explicitly re-raised from tuple exception
def h():
"""Do nothing.
Raises:
ValueError: If something goes wrong.
"""
try:
pass
except (ValueError, TypeError) as e:
raise e
# This should NOT trigger DOC502 because TypeError is explicitly re-raised from tuple exception
def i():
"""Do nothing.
Raises:
TypeError: If something goes wrong.
"""
try:
pass
except (ValueError, TypeError) as e:
raise e

View File

@@ -0,0 +1,21 @@
class C:
f = lambda self: __class__
print(C().f().__name__)
# Test: nested lambda
class D:
g = lambda self: (lambda: __class__)
print(D().g()().__name__)
# Test: lambda outside class (should still fail)
h = lambda: __class__
# Test: lambda referencing module-level variable (should not be flagged as F821)
import uuid
class E:
uuid = lambda: str(uuid.uuid4())

View File

@@ -0,0 +1,131 @@
"""Test cases for PLR1708 stop-iteration-return."""
# Valid cases - should not trigger the rule
def normal_function():
raise StopIteration # Not a generator, should not trigger
def normal_function_with_value():
raise StopIteration("value") # Not a generator, should not trigger
def generator_with_return():
yield 1
yield 2
return "finished" # This is the correct way
def generator_with_yield_from():
yield from [1, 2, 3]
def generator_without_stop_iteration():
yield 1
yield 2
# No explicit termination
def generator_with_other_exception():
yield 1
raise ValueError("something else") # Different exception
# Invalid cases - should trigger the rule
def generator_with_stop_iteration():
yield 1
yield 2
raise StopIteration # Should trigger
def generator_with_stop_iteration_value():
yield 1
yield 2
raise StopIteration("finished") # Should trigger
def generator_with_stop_iteration_expr():
yield 1
yield 2
raise StopIteration(1 + 2) # Should trigger
def async_generator_with_stop_iteration():
yield 1
yield 2
raise StopIteration("async") # Should trigger
def nested_generator():
def inner_gen():
yield 1
raise StopIteration("inner") # Should trigger
yield from inner_gen()
def generator_in_class():
class MyClass:
def generator_method(self):
yield 1
raise StopIteration("method") # Should trigger
return MyClass
# Complex cases
def complex_generator():
try:
yield 1
yield 2
raise StopIteration("complex") # Should trigger
except ValueError:
yield 3
finally:
pass
def generator_with_conditional_stop_iteration(condition):
yield 1
if condition:
raise StopIteration("conditional") # Should trigger
yield 2
# Edge cases
def generator_with_bare_stop_iteration():
yield 1
raise StopIteration # Should trigger (no arguments)
def generator_with_stop_iteration_in_loop():
for i in range(5):
yield i
if i == 3:
raise StopIteration("loop") # Should trigger
# Should not trigger - different exceptions
def generator_with_runtime_error():
yield 1
raise RuntimeError("not StopIteration") # Should not trigger
def generator_with_custom_exception():
yield 1
raise CustomException("custom") # Should not trigger
class CustomException(Exception):
pass
# Generator comprehensions should not be affected
list_comp = [x for x in range(10)] # Should not trigger
# Lambda in generator context
def generator_with_lambda():
yield 1
func = lambda x: x # Just a regular lambda
yield 2

View File

@@ -0,0 +1,5 @@
from .builtins import next
from ..builtins import str
from ...builtins import int
from .builtins import next as _next

View File

@@ -269,3 +269,10 @@ raise ValueError(
# Not a valid type annotation but this test shouldn't result in a panic.
# Refer: https://github.com/astral-sh/ruff/issues/11736
x: "'{} + {}'.format(x, y)"
# Regression https://github.com/astral-sh/ruff/issues/21000
# Fix should parenthesize walrus
if __name__ == "__main__":
number = 0
string = "{}".format(number := number + 1)
print(string)

View File

@@ -26,3 +26,8 @@
"{.real}".format({1, 2})
"{.real}".format({1: 2, 3: 4})
"{}".format((i for i in range(2)))
# https://github.com/astral-sh/ruff/issues/21017
"{.real}".format(1_2)
"{0.real}".format(1_2)
"{a.real}".format(a=1_2)

View File

@@ -0,0 +1,17 @@
"""
Regression test for an ecosystem hit on
https://github.com/astral-sh/ruff/pull/21125.
We should mark all of the components of special dataclass annotations as
runtime-required, not just the first layer.
"""
from dataclasses import dataclass
from typing import ClassVar, Optional
@dataclass(frozen=True)
class EmptyCell:
_singleton: ClassVar[Optional["EmptyCell"]] = None
# the behavior of _singleton above should match a non-ClassVar
_doubleton: "EmptyCell"

View File

@@ -0,0 +1,13 @@
"""This is placed in a separate fixture as `TypeVar` needs to be imported
from `typing_extensions` to support default arguments in Python version < 3.13.
We verify that UP046 doesn't apply in this case.
"""
from typing import Generic
from typing_extensions import TypeVar
T = TypeVar("T", default=str)
class DefaultTypeVar(Generic[T]):
var: T

View File

@@ -0,0 +1,12 @@
"""This is placed in a separate fixture as `TypeVar` needs to be imported
from `typing_extensions` to support default arguments in Python version < 3.13.
We verify that UP047 doesn't apply in this case.
"""
from typing_extensions import TypeVar
T = TypeVar("T", default=int)
def default_var(var: T) -> T:
return var

View File

@@ -125,3 +125,18 @@ with open(*filename, mode="r") as f:
# `buffering`.
with open(*filename, file="file.txt", mode="r") as f:
x = f.read()
# FURB101
with open("file.txt", encoding="utf-8") as f:
contents: str = f.read()
# FURB101 but no fix because it would remove the assignment to `x`
with open("file.txt", encoding="utf-8") as f:
contents, x = f.read(), 2
# FURB101 but no fix because it would remove the `process_contents` call
with open("file.txt", encoding="utf-8") as f:
contents = process_contents(f.read())
with open("file.txt", encoding="utf-8") as f:
contents: str = process_contents(f.read())

View File

@@ -145,3 +145,11 @@ with open("file.txt", "w") as f:
with open("file.txt", "w") as f:
for line in text:
f.write(line)
# See: https://github.com/astral-sh/ruff/issues/20785
import json
data = {"price": 100}
with open("test.json", "wb") as f:
f.write(json.dumps(data, indent=4).encode("utf-8"))

View File

@@ -19,6 +19,9 @@ print("", *args, sep="")
print("", **kwargs)
print(sep="\t")
print(sep=print(1))
print(f"")
print(f"", sep=",")
print(f"", end="bar")
# OK.
@@ -33,3 +36,4 @@ print("foo", "", sep=",")
print("foo", "", "bar", "", sep=",")
print("", "", **kwargs)
print(*args, sep=",")
print(f"foo")

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