Compare commits

..

276 Commits

Author SHA1 Message Date
Micha Reiser
da6403b1bf [ty] Use diagnostic rendering for semantic token tests 2025-07-08 14:05:51 +02:00
Alex Waygood
e16473d260 [ty] Add a new property test: all types assignable to Iterable[object] should be considered iterable (#19186) 2025-07-08 10:54:06 +01:00
Alex Waygood
220a584c11 [ty] Add an instance of an Any subclass to the property tests (#19180) 2025-07-08 10:53:50 +01:00
Dhruv Manilawala
1ddda241f6 [ty] Add an empty line to separate bullet points (#19195)
Without the newline, the rendering would just combine all the bullet
points in a single line like in
https://docs.astral.sh/ty/reference/configuration/#exclude_1. With the
empty line, it would be similar to
https://docs.astral.sh/ty/reference/configuration/#include_1.
2025-07-08 05:10:31 +00:00
UnboundVariable
278f93022a [ty] First cut at semantic token provider (#19108)
This PR implements a basic semantic token provider for ty's language
server. This allows for more accurate semantic highlighting / coloring
within editors that support this LSP functionality.

Here are screen shots that show how code appears in VS Code using the
"rainbow" theme both before and after this change.


![461737617-15630625-d4a9-4ec5-9886-77b00eb7a41a](https://github.com/user-attachments/assets/f963b55b-3195-41d1-ba38-ac2e7508d5f5)


![461737624-d6dcf5f0-7b9b-47de-a410-e202c63e2058](https://github.com/user-attachments/assets/111ca2c5-bb4f-4c8a-a0b5-6c1b2b6f246b)

The token types and modifier tags in this implementation largely mirror
those used in Microsoft's default language server for Python.

The implementation supports two LSP interfaces. The first provides
semantic tokens for an entire document, and the second returns semantic
tokens for a requested range within a document.

The PR includes unit tests. It also includes comments that document
known limitations and areas for future improvements.

---------

Co-authored-by: UnboundVariable <unbound@gmail.com>
2025-07-07 15:34:47 -07:00
GiGaGon
4dd2c03144 [flake8-simplify] Make example error out-of-the-box (SIM116) (#19111)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

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

Part of #18972

This PR makes [if-else-block-instead-of-dict-lookup
(SIM116)](https://docs.astral.sh/ruff/rules/if-else-block-instead-of-dict-lookup/#if-else-block-instead-of-dict-lookup-sim116)'s
example error out-of-the-box

[Old example](https://play.ruff.rs/718f17ee-fbe2-4520-97c6-153bc0f4502d)
```py
if x == 1:
    return "Hello"
elif x == 2:
    return "Goodbye"
else:
    return "Goodnight"
```

[New example](https://play.ruff.rs/8a9b47b4-da46-4a50-8576-362cdd707cee)
```py
def find_phrase(x):
    if x == 1:
        return "Hello"
    elif x == 2:
        return "Goodbye"
    elif x == 3:
        return "Good morning"
    else:
        return "Goodnight"
```

The "Use instead" section was also updated to reflect the new case. I
also changed it to use an intermediary variable since I find the `return
<long dict>.get` very ugly and hard to read.

## Test Plan

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

N/A, no functionality/tests affected
2025-07-07 17:17:55 -04:00
GiGaGon
de5264fe13 [flake8-use-pathlib] Make example error out-of-the-box (PTH210) (#19189)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

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

Part of #18972

This PR makes [invalid-pathlib-with-suffix
(PTH210)](https://docs.astral.sh/ruff/rules/invalid-pathlib-with-suffix/#invalid-pathlib-with-suffix-pth210)'s
example error out-of-the-box.

[Old example](https://play.ruff.rs/d45720cc-fd08-4443-820f-b3bc9756ac59)
```py
path.with_suffix("py")
```

[New example](https://play.ruff.rs/4103669e-19c5-464a-a3fb-6e7d190ce5fd)
```py
from pathlib import Path

path = Path()

path.with_suffix("py")
```

The "Use instead" section was also modified similarly.

## Test Plan

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

N/A, no functionality/tests affected
2025-07-07 17:04:35 -04:00
chiri
e23780c2e1 [flake8-use-pathlib] Add autofixes for PTH203, PTH204, PTH205 (#18922)
<!--
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
Part of #2331 |
[#18763](https://github.com/astral-sh/ruff/pull/18763#issuecomment-2988340436)
<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan
update snapshots
<!-- How was it tested? -->
2025-07-07 16:56:21 -04:00
GiGaGon
47f88b3008 [flake8-type-checking] Fix syntax error introduced by fix (TC008) (#19150)
<!--
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? -->

I noticed this while working on #18972. If the string targeted by
[quoted-type-alias
(TC008)](https://docs.astral.sh/ruff/rules/quoted-type-alias/#quoted-type-alias-tc008)
is a multiline string, the fix would introduce a syntax error. This PR
fixes that by adding parenthesis around the resulting replacement if the
string contained any newline characters (`\n`, `\r`) if it doesn't
already have parenthesis outside `("""...""")` or inside `"""(...)"""`
the annotation.

Failing examples:
https://play.ruff.rs/8793eb95-860a-4bb3-9cbc-6a042fee2946
```
PS D:\rust_projects\ruff> Get-Content issue.py
```
```py
from typing import TypeAlias

OptInt: TypeAlias = """int
| None"""

type OptInt = """int
| None"""
```
```
PS D:\rust_projects\ruff> uvx ruff check issue.py --isolated --select TC008 --fix --diff --preview
```
```

error: Fix introduced a syntax error. Reverting all changes.

This indicates a bug in Ruff. If you could open an issue at:

    https://github.com/astral-sh/ruff/issues/new?title=%5BFix%20error%5D

...quoting the contents of `issue.py`, the rule codes TC008, along with the `pyproject.toml` settings and executed command, we'd be very appreciative!
```

This PR also makes the example error out-of-the-box for #18972

Old example: https://play.ruff.rs/f6cd5adb-7f9b-444d-bb3e-8c045241d93e
```py
OptInt: TypeAlias = "int | None"
```

New example: https://play.ruff.rs/906c1056-72c0-4777-b70b-2114eb9e6eaf
```py
from typing import TypeAlias

OptInt: TypeAlias = "int | None"
```

The import was also added to the "Use instead" section.

## Test Plan

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

Added multiple test cases
2025-07-07 15:34:14 -05:00
GiGaGon
6e77e1b760 [flake8-pyi] Make example error out-of-the-box (PYI007, PYI008) (#19103)
## Summary

Part of #18972

Both in one PR since they are in the same file

No playground links since the playground does not support rules that
only apply to PYI files

PYI007
---

This PR makes [unrecognized-platform-check
(PYI007)](https://docs.astral.sh/ruff/rules/unrecognized-platform-check/#unrecognized-platform-check-pyi007)'s
example error out-of-the-box

Old example:
```
PS ~\Desktop\New_folder\ruff>echo @"
```
```py
if sys.platform.startswith("linux"):
    # Linux specific definitions
    ...
else:
    # Posix specific definitions
    ...
```
```
"@ | uvx ruff check --isolated --preview --select PYI007 --stdin-filename "test.pyi" -
```
```
All checks passed!
```

New example:
```
PS ~\Desktop\New_folder\ruff>echo @"
```
```py
import sys

if sys.platform is "linux":
    # Linux specific definitions
    ...
else:
    # Posix specific definitions
    ...
```
```
"@ | uvx ruff check --isolated --preview --select PYI007 --stdin-filename "test.pyi" -
```
```snap
test.pyi:3:4: PYI007 Unrecognized `sys.platform` check
  |
1 | import sys
2 |
3 | if sys.platform is "linux":
  |    ^^^^^^^^^^^^^^^^^^^^^^^ PYI007
4 |     # Linux specific definitions
5 |     ...
  |

Found 1 error.
```

Imports were also added to the "use instead" section

> [!NOTE]
> `PYI007` is really hard to trigger, it's only specifically in the case
of a comparison where the operator is not `!=` or `==`. The original
example raises [complex-if-statement-in-stub
(PYI002)](https://docs.astral.sh/ruff/rules/complex-if-statement-in-stub/#complex-if-statement-in-stub-pyi002)
with or without the `import sys`

PYI008
---

This PR makes [unrecognized-platform-name
(PYI008)](https://docs.astral.sh/ruff/rules/unrecognized-platform-name/#unrecognized-platform-name-pyi008)'s
example error out-of-the-box

Old example:
```
PS ~\Desktop\New_folder\ruff>echo @"
```
```py
if sys.platform == "linus": ...
```
```
"@ | uvx ruff check --isolated --preview --select PYI008 --stdin-filename "test.pyi" -
```
```
All checks passed!
```

New example:
```
PS ~\Desktop\New_folder\ruff>echo @"
```
```py
import sys

if sys.platform == "linus": ...
```
```
"@ | uvx ruff check --isolated --preview --select PYI008 --stdin-filename "test.pyi" -
```
```snap
test.pyi:3:20: PYI008 Unrecognized platform `linus`
  |
1 | import sys
2 |
3 | if sys.platform == "linus": ...
  |                    ^^^^^^^ PYI008
  |

Found 1 error.
```

Imports were also added to the "use instead" section

> [!NOTE]
> The original example raises `PYI002` instead

## Test Plan

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

N/A, no functionality/tests affected

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-07-07 21:11:43 +01:00
renovate[bot]
845a1eeba6 Update Rust crate indicatif to 0.18.0 (#19165)
## Summary

Updates `indicatif` and `tracing-indicatif`.
2025-07-07 13:19:23 -04:00
Ibraheem Ahmed
cd848986d7 [ty] Add separate CI job for memory usage stats (#19134)
## Summary

As discussed in https://github.com/astral-sh/ruff/pull/19059.
2025-07-07 12:17:02 -04:00
Dhruv Manilawala
56258bb3b7 [ty] Add documentation for server traits (#19137)
This PR adds some basic documentation for the traits in the server
implementation.
2025-07-07 14:26:09 +00:00
Dhruv Manilawala
8cf1b876ee Rename to SessionSnapshot, move unwind assertion closer (#19177)
This PR addresses the post-merge review comments from
https://github.com/astral-sh/ruff/pull/19041, specifically it:
- Rename `WorkspaceSnapshot` to `SessionSnapshot`
- Rename `take_workspace_snapshot` to `take_session_snapshot`
- Rename `take_snapshot` to `take_document_snapshot`
- Move `AssertUnwindSafe` closer to the `catch_unwind` call which
requires the assertion
2025-07-07 19:44:23 +05:30
GiGaGon
1fd48120ba [flake8-type-checking] Make example error out-of-the-box (TC001) (#19151)
## Summary

Part of #18972

This PR makes [typing-only-first-party-import
(TC001)](https://docs.astral.sh/ruff/rules/typing-only-first-party-import/#typing-only-first-party-import-tc001)'s
example error out-of-the-box. The old example raised `TC002` instead of
`TC001`, so this makes it a `from .` import to fix that.

[Old example](https://play.ruff.rs/1fdbb293-86fc-4ed2-b2ff-b4836cea0c59)
```py
from __future__ import annotations

import local_module


def func(sized: local_module.Container) -> int:
    return len(sized)
```

[New example](https://play.ruff.rs/b886535c-9203-48bb-812b-1aa306f2c287)
```py
from __future__ import annotations

from . import local_module


def func(sized: local_module.Container) -> int:
    return len(sized)
```

The "Use instead" section was also modified similarly.

## Test Plan

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

N/A, no functionality/tests affected
2025-07-07 08:53:37 -05:00
David Peter
e7fb3684e8 [ty] Bare ClassVar annotations (#15768)
## Summary

It was recently clarified in the [typing
spec](https://typing.python.org/en/latest/spec/class-compat.html#classvar)
that bare `ClassVar` annotations are allowed. For annotated assignments
with a right hand side value, the spec requires type checkers to infer
the type as something "to which [the] value is assignable". For a value
of `2`, the spec suggests `int`, `Literal[2]`, or `Any` as examples.
Here, we choose `Unknown | Literal[2]` instead, conforming with out
usual treatment of attribute types.

closes https://github.com/astral-sh/ty/issues/211
2025-07-07 15:04:27 +02:00
David Peter
4aaf32476a [ty] Re-enable multithreaded pydantic benchmark (#19176)
## Summary

I played with those numbers a bit locally and `sample_size=3,
sample_count=8` seemed like a rather stable setup. This means a single
sample consistents of 3 iterations of checking pydantic multithreaded.
And this is repeated 8 times for statistics. A single check took ~300 ms
previously on the runners, so this should only take 7 s.
2025-07-07 14:28:15 +02:00
Alex Waygood
a6637964d2 [ty] Implement equivalence for protocols with method members (#18659)
## Summary

This PR implements the following pieces of `Protocol` semantics:
1. A protocol with a method member that does not have a fully static
signature should not be considered fully static. I.e., this protocol is
not fully static because `Foo.x` has no return type; we previously
incorrectly considered that it was:
  ```py
  class Foo(Protocol):
      def f(self): ...
  ```
2. Two protocols `P1` and `P2`, both with method members `x`, should be
considered equivalent if the signature of `P1.x` is equivalent to the
signature of `P2.x`. Currently we do not recognize this.

Implementing these semantics requires distinguishing between method
members and non-method members. The stored type of a method member must
be eagerly upcast to a `Callable` type when collecting the protocol's
interface: doing otherwise would mean that it would be hard to implement
equivalence of protocols even in the face of differently ordered unions,
since the two equivalent protocols would have different Salsa IDs even
when normalized.

The semantics implemented by this PR are that we consider something a
method member if:
1. It is accessible on the class itself; and
2. It is a function-like callable: a callable type that also has a
`__get__` method, meaning it can be used as a method when accessed on
instances.

Note that the spec has complicated things to say about classmethod
members and staticmethod members. These semantics are not implemented by
this PR; they are all deferred for now.

The infrastructure added in this PR fixes bugs in its own right, but
also lays the groundwork for implementing subtyping and assignability
rules for method members of protocols. A (currently failing) test is
added to verify this.

## Test Plan

mdtests
2025-07-07 12:28:32 +01:00
David Peter
c15aa572ff [ty] Use RHS inferred type for bare Final symbols (#19142)
## Summary

Infer the type of symbols with a `Final` qualifier as their
right-hand-side inferred type:
```py
x: Final = 1
y: Final[int] = 1

def _():
    reveal_type(x)  # previously: Unknown, now: Literal[1]
    reveal_type(y)  # int, same as before
```
Part of https://github.com/astral-sh/ty/issues/158

## Ecosystem analysis

### aiohttp

```diff
aiohttp (https://github.com/aio-libs/aiohttp)
+ error[invalid-argument-type] aiohttp/compression_utils.py:131:54: Argument to bound method `__init__` is incorrect: Expected `ZLibBackendProtocol`, found `<module 'zlib'>`
```

This code [creates a
protocol](a83597fa88/aiohttp/compression_utils.py (L52-L77))
that looks like
```pyi
class ZLibBackendProtocol(Protocol):
    Z_FULL_FLUSH: int
    Z_SYNC_FLUSH: int
    # more fields…
```

It then [tries to
assign](a83597fa88/aiohttp/compression_utils.py (L131))
the module literal `zlib` to that protocol. Howefer, in typeshed, these
`zlib` members are annotated like this:
```pyi
Z_FULL_FLUSH: Final = 3
Z_SYNC_FLUSH: Final = 2
```
With the proposed change here, we now infer these as `Literal[3]` /
`Literal[2]`. Since protocol members have to be assignable both ways
(invariance), we do not consider `zlib` assignable to this protocol
anymore.

That seems rather unfortunate. Not sure who is to blame here? That
`ZLibBackendProtocol` protocol should probably not annotate the members
with `int`, given that `typeshed` doesn't use an explicit annotation
here either? But what should they do instead? Annotate those fields with
`Any`?

Or is it another case where we should consider literal-widening?

FYI @AlexWaygood 

### cloud-init

```diff
cloud-init (https://github.com/canonical/cloud-init)
+ error[invalid-argument-type] tests/unittests/sources/test_smartos.py:575:32: Argument to function `oct` is incorrect: Expected `SupportsIndex`, found `int | float`
+ error[invalid-argument-type] tests/unittests/sources/test_smartos.py:593:32: Argument to function `oct` is incorrect: Expected `SupportsIndex`, found `int | float`
+ error[invalid-argument-type] tests/unittests/sources/test_smartos.py:647:35: Argument to function `oct` is incorrect: Expected `SupportsIndex`, found `int | float`
```

New false positives on expressions like
`oct(os.stat(legacy_script_f)[stat.ST_MODE])`. We now correctly infer
`stat.ST_MODE` as `Literal[1]`, because in typeshed, it is annotated as
`ST_MODE: Final = 0`. `os.stat` returns a `stat_result` which is a tuple
subclass. Accessing it at index 0 should return an `int`, but we
currently return `int | float`, presumably due to missing support for
tuple subclasses (FYI @AlexWaygood):
```pyi
class stat_result(structseq[float], tuple[int, int, int, int, int, int, int, float, float, float]):
```
In terms of `typing.Final`, things are working as expected here.


### pywin-32

Many new false positives similar to:

```diff
pywin32 (https://github.com/mhammond/pywin32)
+ error[invalid-argument-type] Pythonwin/pywin/docking/DockingBar.py:288:55: Argument to function `LoadCursor` is incorrect: Expected `PyResourceId`, found `Literal[32645]`
```

The line in question calls `win32api.LoadCursor(0, win32con.IDC_ARROW)`.
The `win32con.IDC_ARROW` symbol is annotated as [`IDC_ARROW: Final =
32512` in
typeshed](2408c028f4/stubs/pywin32/win32/lib/win32con.pyi (L594)),
but
[`LoadCursor`](2408c028f4/stubs/pywin32/win32/win32api.pyi (L197))
expects a
[`PyResourceId`](2408c028f4/stubs/pywin32/_win32typing.pyi (L1252)),
which is an empty class. So.. this seems like a true positive to me,
unless that typeshed annotation of `IDC_ARROW` is meant to imply that
the type should be `Unknown`/`Any`?

### streamlit

```diff
streamlit (https://github.com/streamlit/streamlit)
+ error[invalid-argument-type] lib/streamlit/string_util.py:163:37: Argument to bound method `translate` is incorrect: Expected `bytes`, found `bytearray`
```

This looks like a true positive? The code calls `inp.translate(None,
TEXTCHARS)`. `inp` is `bytes`, and `TEXTCHARS` is:
```py
TEXTCHARS: Final = bytearray(
    {7, 8, 9, 10, 12, 13, 27} | set(range(0x20, 0x100)) - {0x7F}
)
```
~~We now infer this as `bytearray`, but `bytes.translate` [expects
`bytes` for its `delete`
parameter](2408c028f4/stdlib/builtins.pyi (L710)).
This seems to work at runtime, so maybe the typeshed annotation is
wrong?~~ (Edit: this is now fixed in typeshed)
```pycon
>>> b"abc".translate(None, bytearray(b"b"))
b'ac'
```

## rotki

```diff
+ error[invalid-return-type] rotkehlchen/chain/ethereum/modules/yearn/decoder.py:412:13: Return type does not match returned value: expected `dict[Unknown, str]`, found `dict[Unknown, Literal["yearn-v1", "yearn-v2"]]`
```

The code in question looks like
```py
    def addresses_to_counterparties(self) -> dict[ChecksumEvmAddress, str]:
        return dict.fromkeys(self.vaults, CPT_BEEFY_FINANCE)
```
where `CPT_BEEFY_FINANCE: Final = 'beefy_finance'. We previously
inferred the value type of the returned `dict` as `Unknown`, and now we
infer it as `Literal["beefy_finance"]`, which does not match the
annotated return type because `dict` is invariant in the value type.

```diff
+ error[invalid-argument-type] rotkehlchen/tests/unit/decoders/test_curve.py:249:9: Argument is incorrect: Expected `int`, found `FVal`
```
There are true positives that were previously silenced through the
`Unknown`.

## Test Plan

New Markdown tests
2025-07-07 13:16:40 +02:00
Ivan Yakushev
e0b7f496f2 [ty] Support declaration-only attributes (#19048)
## Summary

Following ty issue [#698](https://github.com/astral-sh/ty/issues/698)
this PR adds support for declarations.

closes #698

## Test Plan

Tested against mdtest (specifically attributes).

---------

Co-authored-by: David Peter <mail@david-peter.de>
2025-07-07 12:55:32 +02:00
github-actions[bot]
b6edfbc70f [ty] Sync vendored typeshed stubs (#19174)
Close and reopen this PR to trigger CI

---------

Co-authored-by: typeshedbot <>
Co-authored-by: David Peter <mail@david-peter.de>
2025-07-07 12:00:09 +02:00
renovate[bot]
9be8276616 Update dependency pyodide to ^0.28.0 (#19164)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-07 10:38:29 +02:00
renovate[bot]
d0099fd012 Update NPM Development dependencies (#19170)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-07 10:33:59 +02:00
renovate[bot]
0d3802998b Update taiki-e/install-action action to v2.56.7 (#19169)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-07 10:25:43 +02:00
renovate[bot]
1a03b5841b Update pre-commit dependencies (#19162)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[astral-sh/ruff-pre-commit](https://redirect.github.com/astral-sh/ruff-pre-commit)
| repository | patch | `v0.12.1` -> `v0.12.2` |
| [crate-ci/typos](https://redirect.github.com/crate-ci/typos) |
repository | minor | `v1.33.1` -> `v1.34.0` |
|
[python-jsonschema/check-jsonschema](https://redirect.github.com/python-jsonschema/check-jsonschema)
| repository | patch | `0.33.1` -> `0.33.2` |
|
[woodruffw/zizmor-pre-commit](https://redirect.github.com/woodruffw/zizmor-pre-commit)
| repository | minor | `v1.10.0` -> `v1.11.0` |

---

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

Note: The `pre-commit` manager in Renovate is not supported by the
`pre-commit` maintainers or community. Please do not report any problems
there, instead [create a Discussion in the Renovate
repository](https://redirect.github.com/renovatebot/renovate/discussions/new)
if you have any questions.

---

### Release Notes

<details>
<summary>astral-sh/ruff-pre-commit (astral-sh/ruff-pre-commit)</summary>

###
[`v0.12.2`](https://redirect.github.com/astral-sh/ruff-pre-commit/releases/tag/v0.12.2)

[Compare
Source](https://redirect.github.com/astral-sh/ruff-pre-commit/compare/v0.12.1...v0.12.2)

See: https://github.com/astral-sh/ruff/releases/tag/0.12.2

</details>

<details>
<summary>crate-ci/typos (crate-ci/typos)</summary>

###
[`v1.34.0`](https://redirect.github.com/crate-ci/typos/releases/tag/v1.34.0)

[Compare
Source](https://redirect.github.com/crate-ci/typos/compare/v1.33.1...v1.34.0)

#### \[1.34.0] - 2025-06-30

##### Features

- Updated the dictionary with the [June
2025](https://redirect.github.com/crate-ci/typos/issues/1309) changes

</details>

<details>
<summary>python-jsonschema/check-jsonschema
(python-jsonschema/check-jsonschema)</summary>

###
[`v0.33.2`](https://redirect.github.com/python-jsonschema/check-jsonschema/blob/HEAD/CHANGELOG.rst#0332)

[Compare
Source](https://redirect.github.com/python-jsonschema/check-jsonschema/compare/0.33.1...0.33.2)

- Update vendored schemas: bitbucket-pipelines, mergify, renovate
(2025-06-29)
- Fix a bug in the evaluation of the `date-time` format on non-string
data,
which incorrectly rejected values for which `string` was one of several
  valid types. Thanks :user:`katylava`! (:issue:`571`)

</details>

<details>
<summary>woodruffw/zizmor-pre-commit
(woodruffw/zizmor-pre-commit)</summary>

###
[`v1.11.0`](https://redirect.github.com/zizmorcore/zizmor-pre-commit/releases/tag/v1.11.0)

[Compare
Source](https://redirect.github.com/woodruffw/zizmor-pre-commit/compare/v1.10.0...v1.11.0)

See: https://github.com/zizmorcore/zizmor/releases/tag/v1.11.0

</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.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- 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:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNy4yIiwidXBkYXRlZEluVmVyIjoiNDEuMTcuMiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Dhruv Manilawala <dhruvmanila@gmail.com>
2025-07-07 04:07:44 +00:00
renovate[bot]
a7fafb96be Update dependency smol-toml to v1.4.1 (#19161)
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
| [smol-toml](https://redirect.github.com/squirrelchat/smol-toml) |
[`1.4.0` ->
`1.4.1`](https://renovatebot.com/diffs/npm/smol-toml/1.4.0/1.4.1) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/smol-toml/1.4.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/smol-toml/1.4.0/1.4.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

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

---

### Release Notes

<details>
<summary>squirrelchat/smol-toml (smol-toml)</summary>

###
[`v1.4.1`](https://redirect.github.com/squirrelchat/smol-toml/releases/tag/v1.4.1)

[Compare
Source](https://redirect.github.com/squirrelchat/smol-toml/compare/v1.4.0...v1.4.1)

A little fix for `asNeeded` not being implemented correctly.

#### What's Changed

fix: properly implement asNeeded by
[@&#8203;cyyynthia](https://redirect.github.com/cyyynthia)

**Full Changelog**:
https://github.com/squirrelchat/smol-toml/compare/v1.4.0...v1.4.1

</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 these
updates 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:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNy4yIiwidXBkYXRlZEluVmVyIjoiNDEuMTcuMiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-07 08:54:52 +05:30
renovate[bot]
22d809b8ce Update dependency ruff to v0.12.2 (#19160)
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
| [ruff](https://docs.astral.sh/ruff)
([source](https://redirect.github.com/astral-sh/ruff),
[changelog](https://redirect.github.com/astral-sh/ruff/blob/main/CHANGELOG.md))
| `==0.12.1` -> `==0.12.2` |
[![age](https://developer.mend.io/api/mc/badges/age/pypi/ruff/0.12.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/ruff/0.12.1/0.12.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

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

---

### Release Notes

<details>
<summary>astral-sh/ruff (ruff)</summary>

###
[`v0.12.2`](https://redirect.github.com/astral-sh/ruff/blob/HEAD/CHANGELOG.md#0122)

[Compare
Source](https://redirect.github.com/astral-sh/ruff/compare/0.12.1...0.12.2)

##### Preview features

- \[`flake8-pyi`] Expand `Optional[A]` to `A | None` (`PYI016`)
([#&#8203;18572](https://redirect.github.com/astral-sh/ruff/pull/18572))
- \[`pyupgrade`] Mark `UP008` fix safe if no comments are in range
([#&#8203;18683](https://redirect.github.com/astral-sh/ruff/pull/18683))

##### Bug fixes

- \[`flake8-comprehensions`] Fix `C420` to prepend whitespace when
needed
([#&#8203;18616](https://redirect.github.com/astral-sh/ruff/pull/18616))
- \[`perflint`] Fix `PERF403` panic on attribute or subscription loop
variable
([#&#8203;19042](https://redirect.github.com/astral-sh/ruff/pull/19042))
- \[`pydocstyle`] Fix `D413` infinite loop for parenthesized docstring
([#&#8203;18930](https://redirect.github.com/astral-sh/ruff/pull/18930))
- \[`pylint`] Fix `PLW0108` autofix introducing a syntax error when the
lambda's body contains an assignment expression
([#&#8203;18678](https://redirect.github.com/astral-sh/ruff/pull/18678))
- \[`refurb`] Fix false positive on empty tuples (`FURB168`)
([#&#8203;19058](https://redirect.github.com/astral-sh/ruff/pull/19058))
- \[`ruff`] Allow more `field` calls from `attrs` (`RUF009`)
([#&#8203;19021](https://redirect.github.com/astral-sh/ruff/pull/19021))
- \[`ruff`] Fix syntax error introduced for an empty string followed by
a u-prefixed string (`UP025`)
([#&#8203;18899](https://redirect.github.com/astral-sh/ruff/pull/18899))

##### Rule changes

- \[`flake8-executable`] Allow `uvx` in shebang line (`EXE003`)
([#&#8203;18967](https://redirect.github.com/astral-sh/ruff/pull/18967))
- \[`pandas`] Avoid flagging `PD002` if `pandas` is not imported
([#&#8203;18963](https://redirect.github.com/astral-sh/ruff/pull/18963))
- \[`pyupgrade`] Avoid PEP-604 unions with `typing.NamedTuple` (`UP007`,
`UP045`)
([#&#8203;18682](https://redirect.github.com/astral-sh/ruff/pull/18682))

##### Documentation

- Document link between `import-outside-top-level (PLC0415)` and
`lint.flake8-tidy-imports.banned-module-level-imports`
([#&#8203;18733](https://redirect.github.com/astral-sh/ruff/pull/18733))
- Fix description of the `format.skip-magic-trailing-comma` example
([#&#8203;19095](https://redirect.github.com/astral-sh/ruff/pull/19095))
- \[`airflow`] Make `AIR302` example error out-of-the-box
([#&#8203;18988](https://redirect.github.com/astral-sh/ruff/pull/18988))
- \[`airflow`] Make `AIR312` example error out-of-the-box
([#&#8203;18989](https://redirect.github.com/astral-sh/ruff/pull/18989))
- \[`flake8-annotations`] Make `ANN401` example error out-of-the-box
([#&#8203;18974](https://redirect.github.com/astral-sh/ruff/pull/18974))
- \[`flake8-async`] Make `ASYNC100` example error out-of-the-box
([#&#8203;18993](https://redirect.github.com/astral-sh/ruff/pull/18993))
- \[`flake8-async`] Make `ASYNC105` example error out-of-the-box
([#&#8203;19002](https://redirect.github.com/astral-sh/ruff/pull/19002))
- \[`flake8-async`] Make `ASYNC110` example error out-of-the-box
([#&#8203;18975](https://redirect.github.com/astral-sh/ruff/pull/18975))
- \[`flake8-async`] Make `ASYNC210` example error out-of-the-box
([#&#8203;18977](https://redirect.github.com/astral-sh/ruff/pull/18977))
- \[`flake8-async`] Make `ASYNC220`, `ASYNC221`, and `ASYNC222` examples
error out-of-the-box
([#&#8203;18978](https://redirect.github.com/astral-sh/ruff/pull/18978))
- \[`flake8-async`] Make `ASYNC251` example error out-of-the-box
([#&#8203;18990](https://redirect.github.com/astral-sh/ruff/pull/18990))
- \[`flake8-bandit`] Make `S201` example error out-of-the-box
([#&#8203;19017](https://redirect.github.com/astral-sh/ruff/pull/19017))
- \[`flake8-bandit`] Make `S604` and `S609` examples error
out-of-the-box
([#&#8203;19049](https://redirect.github.com/astral-sh/ruff/pull/19049))
- \[`flake8-bugbear`] Make `B028` example error out-of-the-box
([#&#8203;19054](https://redirect.github.com/astral-sh/ruff/pull/19054))
- \[`flake8-bugbear`] Make `B911` example error out-of-the-box
([#&#8203;19051](https://redirect.github.com/astral-sh/ruff/pull/19051))
- \[`flake8-datetimez`] Make `DTZ011` example error out-of-the-box
([#&#8203;19055](https://redirect.github.com/astral-sh/ruff/pull/19055))
- \[`flake8-datetimez`] Make `DTZ901` example error out-of-the-box
([#&#8203;19056](https://redirect.github.com/astral-sh/ruff/pull/19056))
- \[`flake8-pyi`] Make `PYI032` example error out-of-the-box
([#&#8203;19061](https://redirect.github.com/astral-sh/ruff/pull/19061))
- \[`flake8-pyi`] Make example error out-of-the-box (`PYI014`, `PYI015`)
([#&#8203;19097](https://redirect.github.com/astral-sh/ruff/pull/19097))
- \[`flake8-pyi`] Make example error out-of-the-box (`PYI042`)
([#&#8203;19101](https://redirect.github.com/astral-sh/ruff/pull/19101))
- \[`flake8-pyi`] Make example error out-of-the-box (`PYI059`)
([#&#8203;19080](https://redirect.github.com/astral-sh/ruff/pull/19080))
- \[`flake8-pyi`] Make example error out-of-the-box (`PYI062`)
([#&#8203;19079](https://redirect.github.com/astral-sh/ruff/pull/19079))
- \[`flake8-pytest-style`] Make example error out-of-the-box (`PT023`)
([#&#8203;19104](https://redirect.github.com/astral-sh/ruff/pull/19104))
- \[`flake8-pytest-style`] Make example error out-of-the-box (`PT030`)
([#&#8203;19105](https://redirect.github.com/astral-sh/ruff/pull/19105))
- \[`flake8-quotes`] Make example error out-of-the-box (`Q003`)
([#&#8203;19106](https://redirect.github.com/astral-sh/ruff/pull/19106))
- \[`flake8-simplify`] Make example error out-of-the-box (`SIM110`)
([#&#8203;19113](https://redirect.github.com/astral-sh/ruff/pull/19113))
- \[`flake8-simplify`] Make example error out-of-the-box (`SIM113`)
([#&#8203;19109](https://redirect.github.com/astral-sh/ruff/pull/19109))
- \[`flake8-simplify`] Make example error out-of-the-box (`SIM401`)
([#&#8203;19110](https://redirect.github.com/astral-sh/ruff/pull/19110))
- \[`pyflakes`] Fix backslash in docs (`F621`)
([#&#8203;19098](https://redirect.github.com/astral-sh/ruff/pull/19098))
- \[`pylint`] Fix `PLC0415` example
([#&#8203;18970](https://redirect.github.com/astral-sh/ruff/pull/18970))

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-07 08:54:35 +05:30
renovate[bot]
cc190a550c Update Rust crate serde_with to v3.14.0 (#19167)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [serde_with](https://redirect.github.com/jonasbb/serde_with) |
workspace.dependencies | minor | `3.12.0` -> `3.14.0` |

---

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

---

### Release Notes

<details>
<summary>jonasbb/serde_with (serde_with)</summary>

###
[`v3.14.0`](https://redirect.github.com/jonasbb/serde_with/releases/tag/v3.14.0):
serde_with v3.14.0

[Compare
Source](https://redirect.github.com/jonasbb/serde_with/compare/v3.13.0...v3.14.0)

##### Added

- Add support for `Range`, `RangeFrom`, `RangeTo`, `RangeInclusive`
([#&#8203;851](https://redirect.github.com/jonasbb/serde_with/issues/851))
  `RangeToInclusive` is currently unsupported by serde.
- Add `schemars` implementations for `Bound`, `Range`, `RangeFrom`,
`RangeTo`, `RangeInclusive`.
- Added support for `schemars` v1 under the `schemars_1` feature flag

###
[`v3.13.0`](https://redirect.github.com/jonasbb/serde_with/releases/tag/v3.13.0):
serde_with v3.13.0

[Compare
Source](https://redirect.github.com/jonasbb/serde_with/compare/v3.12.0...v3.13.0)

##### Added

- Added support for `schemars` v0.9.0 under the `schemars_0_9` feature
flag by [@&#8203;swlynch99](https://redirect.github.com/swlynch99)
([#&#8203;849](https://redirect.github.com/jonasbb/serde_with/issues/849))
- Introduce `SerializeDisplayAlt` derive macro
([#&#8203;833](https://redirect.github.com/jonasbb/serde_with/issues/833))
An alternative to the `SerializeDisplay` macro except instead of using
the
  plain formatting like `format!("{}", ...)`, it serializes with the
  `Formatter::alternate` flag set to true, like `format!("{:#}", ...)`

##### Changed

- Generalize `serde_with::rust::unwrap_or_skip` to support deserializing
references by [@&#8203;beroal](https://redirect.github.com/beroal)
([#&#8203;832](https://redirect.github.com/jonasbb/serde_with/issues/832))
- Bump MSRV to 1.71, since that is required for the `jsonschema`
dev-dependency.
- Make `serde_conv` available without the `std` feature by
[@&#8203;arilou](https://redirect.github.com/arilou)
([#&#8203;839](https://redirect.github.com/jonasbb/serde_with/issues/839))
- Bump MSRV to 1.74, since that is required for `schemars` v0.9.0 by
[@&#8203;swlynch99](https://redirect.github.com/swlynch99)
([#&#8203;849](https://redirect.github.com/jonasbb/serde_with/issues/849))

##### Fixed

- Make the `DurationSeconds` types and other variants more accessible
even without `std`
([#&#8203;845](https://redirect.github.com/jonasbb/serde_with/issues/845))

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-07 08:52:38 +05:30
renovate[bot]
ab024f9de1 Update Rust crate notify to v8.1.0 (#19166)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [notify](https://redirect.github.com/notify-rs/notify) |
workspace.dependencies | minor | `8.0.0` -> `8.1.0` |

---

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

---

### Release Notes

<details>
<summary>notify-rs/notify (notify)</summary>

###
[`v8.1.0`](https://redirect.github.com/notify-rs/notify/blob/HEAD/CHANGELOG.md#notify-810-2025-07-03)

[Compare
Source](https://redirect.github.com/notify-rs/notify/compare/notify-8.0.0...notify-8.1.0)

- FEATURE: added support for the [`flume`](https://docs.rs/flume) crate
- FIX: kqueue-backend: do not double unwatch top-level directory when
recursively unwatching
\[[#&#8203;683](https://redirect.github.com/notify-rs/notify/issues/683)]
- FIX: Return the crate error `PathNotFound` instead bubbling up the
std::io error
\[[#&#8203;685](https://redirect.github.com/notify-rs/notify/issues/685)]
- FIX: fix server hangs when trashing folders on Windows
\[[#&#8203;674](https://redirect.github.com/notify-rs/notify/issues/674)]

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-07 08:51:09 +05:30
renovate[bot]
3d2a0c3cd6 Update Swatinem/rust-cache action to v2.8.0 (#19168)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [Swatinem/rust-cache](https://redirect.github.com/Swatinem/rust-cache)
| action | minor | `v2.7.8` -> `v2.8.0` |

---

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

---

### Release Notes

<details>
<summary>Swatinem/rust-cache (Swatinem/rust-cache)</summary>

###
[`v2.8.0`](https://redirect.github.com/Swatinem/rust-cache/releases/tag/v2.8.0)

[Compare
Source](https://redirect.github.com/Swatinem/rust-cache/compare/v2.7.8...v2.8.0)

##### What's Changed

- Add cache-workspace-crates feature by
[@&#8203;jbransen](https://redirect.github.com/jbransen) in
[https://github.com/Swatinem/rust-cache/pull/246](https://redirect.github.com/Swatinem/rust-cache/pull/246)
- Feat: support warpbuild cache provider by
[@&#8203;stegaBOB](https://redirect.github.com/stegaBOB) in
[https://github.com/Swatinem/rust-cache/pull/247](https://redirect.github.com/Swatinem/rust-cache/pull/247)

##### New Contributors

- [@&#8203;jbransen](https://redirect.github.com/jbransen) made their
first contribution in
[https://github.com/Swatinem/rust-cache/pull/246](https://redirect.github.com/Swatinem/rust-cache/pull/246)
- [@&#8203;stegaBOB](https://redirect.github.com/stegaBOB) made their
first contribution in
[https://github.com/Swatinem/rust-cache/pull/247](https://redirect.github.com/Swatinem/rust-cache/pull/247)

**Full Changelog**:
https://github.com/Swatinem/rust-cache/compare/v2.7.8...v2.8.0

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-07 08:50:29 +05:30
Alex Waygood
08d8819c8a [ty] Fix descriptor lookups for most types that overlap with None (#19120) 2025-07-05 19:34:23 +01:00
Alex Waygood
44f2f77748 [ty] Add a DateType benchmark (#19148)
## Summary

The [`DateType`](https://github.com/glyph/DateType) library has some
very large protocols in it. Currently we type-check it quite quickly,
but the current version of https://github.com/astral-sh/ruff/pull/18659
makes our execution time on this library pathologically slow. That PR
doesn't seem to have a big impact on any of our current benchmarks,
however, so it seems we have some missing coverage in this area; I
therefore propose that we add `DateType` as a benchmark.

Currently the benchmark runs pretty quickly (about half the runtime of
attrs, which is our fastest real-world benchmark currently), and the
library has 0 third-party dependencies, so the benchmark is quick to
setup.

## Test Plan

`cargo bench -p ruff_benchmark --bench=ty`
2025-07-04 21:11:47 +01:00
NamelessGO
1c710c2840 Add Weblate to Who's Using Ruff (#19124)
https://github.com/WeblateOrg/weblate/issues/8867
2025-07-04 15:24:44 -04:00
Abhijeet Prasad Bodas
f4bd74ab6a [ty] Correctly handle calls to functions marked as returning Never / NoReturn (#18333)
## Summary

`ty` does not understand that calls to functions which have been
annotated as having a return type of `Never` / `NoReturn` are terminal.

This PR fixes that, by adding new reachability constraints when call
expressions are seen. If the call expression evaluates to `Never`, the
code following it will be considered to be unreachable. Note that, for
adding these constraints, we only consider call expressions at the
statement level, and that too only inside function scopes. This is
because otherwise, the number of such constraints becomes too high, and
evaluating them later on during type inference results in a major
performance degradation.

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

## Test Plan

New mdtests.

## Ecosystem changes

This PR removes the following false-positives:
- "Function can implicitly return `None`, which is not assignable to
...".
- "Name `foo` used when possibly not defind" - because the branch in
which it is not defined has a `NoReturn` call, or when `foo` was
imported in a `try`, and the except had a `NoReturn` call.

---------

Co-authored-by: David Peter <mail@david-peter.de>
2025-07-04 11:52:52 -07:00
GiGaGon
a33cff2b12 Fix F701 to F707 errors in tests (#19125)
## Summary

Per @ntBre in https://github.com/astral-sh/ruff/pull/19111, it would be
a good idea to make the tests no longer have these syntax errors, so
this PR updates the tests and snapshots.

`B031` gave me a lot of trouble since the ending test of declaring a
function named `groupby` makes it so that inside other functions, it's
unclear which `groupby` is referred to since it depends on when the
function is called. To fix it I made each function have it's own `from
itertools import groupby` so there's no more ambiguity.
2025-07-04 13:43:18 -05:00
GiGaGon
f48a34fbab [pylint, pyupgrade] Fix syntax errors in examples (PLW1501, UP028) (#19127)
## Summary

From me and @ntBre's discussion in #19111.

This PR makes these two examples into valid code, since they previously
had `F701`-`F707` syntax errors. `SIM110` was already fixed in a
different PR, I just forgot to pull.
2025-07-04 13:38:37 -05:00
Carl Meyer
411cccb35e [ty] detect cycles in Type::is_disjoint_from (#19139) 2025-07-04 06:31:44 -07:00
Carl Meyer
7712c2fd15 [ty] don't allow first-party code to shadow stdlib types module (#19128) 2025-07-04 10:36:26 +00:00
David Peter
25bdb67d9a [ty] Remove TODOs regarding legacy generics (#19141) 2025-07-04 10:45:06 +02:00
Matthew Mckee
3be83d36a5 [ty] Add into_callable method for Type (#19130)
## Summary

Was just playing around with this, there's definitely more to do with
this function, but it seems like maybe a better option than having so
many arms in has_relation_to for (_, Callable).

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Co-authored-by: Carl Meyer <carl@astral.sh>
2025-07-03 19:04:03 -07:00
Alex Waygood
333191b7f7 [ty] Rewrite Type::any_over_type using a new generalised TypeVisitor trait (#19094) 2025-07-03 18:19:23 +00:00
Brent Westbrook
77a5c5ac80 Combine OldDiagnostic and Diagnostic (#19053)
## Summary

This PR is a collaboration with @AlexWaygood from our pairing session
last Friday.

The main goal here is removing `ruff_linter::message::OldDiagnostic` in
favor of
using `ruff_db::diagnostic::Diagnostic` directly. This involved a few
major steps:

- Transferring the fields
- Transferring the methods and trait implementations, where possible
- Converting some constructor methods to free functions
- Moving the `SecondaryCode` struct
- Updating the method names

I'm hoping that some of the methods, especially those in the
`expect_ruff_*`
family, won't be necessary long-term, but I avoided trying to replace
them
entirely for now to keep the already-large diff a bit smaller.

### Related refactors

Alex and I noticed a few refactoring opportunities while looking at the
code,
specifically the very similar implementations for
`create_parse_diagnostic`,
`create_unsupported_syntax_diagnostic`, and
`create_semantic_syntax_diagnostic`.
We combined these into a single generic function, which I then copied
into
`ruff_linter::message` with some small changes and a TODO to combine
them in the
future.

I also deleted the `DisplayParseErrorType` and `TruncateAtNewline` types
for
reporting parse errors. These were added in #4124, I believe to work
around the
error messages from LALRPOP. Removing these didn't affect any tests, so
I think
they were unnecessary now that we fully control the error messages from
the
parser.

On a more minor note, I factored out some calls to the
`OldDiagnostic::filename`
(now `Diagnostic::expect_ruff_filename`) function to avoid repeatedly
allocating
`String`s in some places.

### Snapshot changes

The `show_statistics_syntax_errors` integration test changed because the
`OldDiagnostic::name` method used `syntax-error` instead of
`invalid-syntax`
like in ty. I think this (`--statistics`) is one of the only places we
actually
use this name for syntax errors, so I hope this is okay. An alternative
is to
use `syntax-error` in ty too.

The other snapshot changes are from removing this code, as discussed on

[Discord](https://discord.com/channels/1039017663004942429/1228460843033821285/1388252408848847069):


34052a1185/crates/ruff_linter/src/message/mod.rs (L128-L135)

I think both of these are technically breaking changes, but they only
affect
syntax errors and are very narrow in scope, while also pretty
substantially
simplifying the refactor, so I hope they're okay to include in a patch
release.

## Test plan

Existing tests, with the adjustments mentioned above

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-07-03 13:01:09 -04:00
Brent Westbrook
9bee8376a1 Bump 0.12.2 (#19126) 2025-07-03 12:27:24 -04:00
Zanie Blue
1c6717b149 Filter private symbols from stubs if they are internal types (#19121)
This implements filtering of private symbols from stub files based on
type information as discussed in
https://github.com/astral-sh/ruff/pull/19102. It extends the previous
implementation to apply to all stub files, instead of just the
`builtins` module, and uses type information to retain private names
that are may be relevant at runtime.
2025-07-03 10:19:21 -05:00
Leander Cain Slotosch
1b813cd5f1 Fix description of the format.skip-magic-trailing-comma example (#19095)
## Summary

This PR fixes a typo in the docs, where both variants of a config have
the same description.
2025-07-03 10:39:59 -04:00
Brent Westbrook
b00f68a23c [ruff] Allow more field calls from attrs (RUF009) (#19021)
Summary
--

Closes #19014 by identifying more `field` functions from `attrs`. We
already detected these when imported from `attrs` but not the `attr`
module from the same package. These functions are identical to the
`attrs` versions:

```pycon
>>> import attrs, attr
>>> attrs.field is attr.field
True
>>> attrs.Factory is attr.Factory
True
>>>
```

Test Plan
--

Regression tests based on the issue
2025-07-03 10:29:55 -04:00
GiGaGon
710c60f713 [flake8-pytest-style] Make example error out-of-the-box (PT023) (#19104)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

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

Part of #18972

This PR makes [pytest-incorrect-mark-parentheses-style
(PT023)](https://docs.astral.sh/ruff/rules/pytest-incorrect-mark-parentheses-style/#pytest-incorrect-mark-parentheses-style-pt023)'s
example error out-of-the-box

[Old example](https://play.ruff.rs/48989153-6d4a-493a-a287-07f330f270bc)
```py
import pytest


@pytest.mark.foo
def test_something(): ...
```

[New example](https://play.ruff.rs/741f4d19-4607-4777-a77e-4ea6c62845e1)
```py
import pytest


@pytest.mark.foo()
def test_something(): ...
```

This just swaps the parenthesis in the "Example" and "Use instead"
sections since the default configuration is no parenthesis

## Test Plan

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

N/A, no functionality/tests affected
2025-07-03 10:29:26 -04:00
GiGaGon
811e25d16e [flake8-pytest-style] Make example error out-of-the-box (PT030) (#19105)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

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

Part of #18972

This PR makes [pytest-warns-too-broad
(PT030)](https://docs.astral.sh/ruff/rules/pytest-warns-too-broad/#pytest-warns-too-broad-pt030)'s
example error out-of-the-box

[Old example](https://play.ruff.rs/2296ae7e-c775-427a-a020-6fb25321f3f7)
```py
import pytest


def test_foo():
    with pytest.warns(RuntimeWarning):
        ...

    # empty string is also an error
    with pytest.warns(RuntimeWarning, match=""):
        ...
```

[New example](https://play.ruff.rs/af35a482-1c2f-47ee-aff3-ff1e9fa447de)
```py
import pytest


def test_foo():
    with pytest.warns(Warning):
        ...

    # empty string is also an error
    with pytest.warns(Warning, match=""):
        ...
```

`RuntimeWarning` is not in the default
[warns-require-match-for](https://docs.astral.sh/ruff/settings/#lint_flake8-pytest-style_warns-require-match-for)
list, while `Warning` is. The "Use instead" section was also updated
similarly

## Test Plan

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

N/A, no functionality/tests affected
2025-07-03 10:27:31 -04:00
GiGaGon
b78af2db48 [flake8-quotes] Make example error out-of-the-box (Q003) (#19106)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

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

Part of #18972

This PR makes [avoidable-escaped-quote
(Q003)](https://docs.astral.sh/ruff/rules/avoidable-escaped-quote/#avoidable-escaped-quote-q003)'s
example error out-of-the-box

[Old example](https://play.ruff.rs/fb319d0f-8016-46a1-b6bb-42b1b054feea)
```py
foo = 'bar\'s'
```

[New example](https://play.ruff.rs/d9626561-0646-448f-9282-3f0691b90831)
```py
foo = "bar\"s"
```

The original example got overwritten by `Q000`, since double quotes is
the default config. The quotes were also switched in the "Use instead"
section.

## Test Plan

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

N/A, no functionality/tests affected
2025-07-03 10:25:46 -04:00
Avasam
4f36f0677f Document link between import-outside-top-level (PLC0415) and lint.flake8-tidy-imports.banned-module-level-imports (#18733)
<!--
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

As mentioned in
https://github.com/astral-sh/ruff/issues/18728#issuecomment-2981330666
CC @ntBre


![image](https://github.com/user-attachments/assets/ac0e9ea6-6510-48be-b775-47b30bdf7efe)

![image](https://github.com/user-attachments/assets/2a69df6f-1973-4d81-8985-9e0ce70f8175)


## Test Plan

Run the docs locally as per
a2cd6df429/CONTRIBUTING.md (mkdocs)

---------

Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2025-07-03 14:11:53 +00:00
GiGaGon
2589a2938e [flake8-simplify] Make example error out-of-the-box (SIM113) (#19109)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

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

Part of #18972

This PR makes [enumerate-for-loop
(SIM113)](https://docs.astral.sh/ruff/rules/enumerate-for-loop/#enumerate-for-loop-sim113)'s
example error out-of-the-box

[Old example](https://play.ruff.rs/a6ef6fec-eb6b-477c-a962-616f0b8e1491)
```py
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
    print(f"{i + 1}. {fruit}")
    i += 1
```

[New example](https://play.ruff.rs/1811d608-1aa0-45d8-96dc-18105e74b8cc)
```py
fruits = ["apple", "banana", "cherry"]
i = 0
for fruit in fruits:
    print(f"{i + 1}. {fruit}")
    i += 1
```

## Test Plan

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

N/A, no functionality/tests affected
2025-07-03 10:08:17 -04:00
GiGaGon
26bb8f7b71 [flake8-simplify] Make example error out-of-the-box (SIM401) (#19110)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

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

Part of #18972

This PR makes [enumerate-for-loop [if-else-block-instead-of-dict-get
(SIM401)](https://docs.astral.sh/ruff/rules/if-else-block-instead-of-dict-get/#if-else-block-instead-of-dict-get-sim401)'s
example error out-of-the-box

[Old example](https://play.ruff.rs/635629eb-7146-45a8-9e0c-4a0aa9446ded)
```py
if "bar" in foo:
    value = foo["bar"]
else:
    value = 0
```

[New example](https://play.ruff.rs/a1227ec9-05c2-4a22-800d-c76cb7abe249)
```py
foo = {}
if "bar" in foo:
    value = foo["bar"]
else:
    value = 0
```

The "Use instead" section was also updated similarly.

The docs for `SIM401` also has another section on the preview ternary
version, but it does not seem to check that the variable is a dict
(bug?) https://play.ruff.rs/c0feada8-a7fe-43f7-b57e-c10520fdcdca

## Test Plan

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

N/A, no functionality/tests affected
2025-07-03 10:00:08 -04:00
GiGaGon
bf88fee428 [flake8-simplify] Make example error out-of-the-box (SIM110) (#19113)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

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

Part of #18972

This PR makes [reimplemented-builtin
(SIM110)](https://docs.astral.sh/ruff/rules/reimplemented-builtin/#reimplemented-builtin-sim110)'s
example error out-of-the-box

[Old example](https://play.ruff.rs/1c192e8b-13f8-4f07-8c35-9dcd516a4a02)
```py
for item in iterable:
    if predicate(item):
        return True
return False
```

[New example](https://play.ruff.rs/f77393ad-20b1-436f-a872-d3bccec7c829)
```py
def foo():
    for item in iterable:
        if predicate(item):
            return True
    return False
```

The "Use instead" section was also updated to reflect the change.

## Test Plan

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

N/A, no functionality/tests affected
2025-07-03 09:57:35 -04:00
David Peter
fc43d3c83e [ty] Temporarily disable the multithreaded pydantic benchmark (#19119)
The benchmark is currently very noisy (± 10%). This leads to codspeed
reports on PRs, because we often exceed the trigger threshold. This is
confusing to ty contributors who are not aware about the flakiness.
Let's disable it for now.
2025-07-03 14:34:52 +02:00
GiGaGon
d0f0577ac7 [flake8-pyi] Make example error out-of-the-box (PYI014, PYI015) (#19097) 2025-07-03 12:54:35 +01:00
Dhruv Manilawala
dc56c33618 [ty] Initial support for workspace diagnostics (#18939)
## Summary

This PR adds initial support for workspace diagnostics in the ty server.

Reference spec:
https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_diagnostic

This is currently implemented via the **pull diagnostics method** which
was added in the current version (3.17) and the server advertises it via
the `diagnosticProvider.workspaceDiagnostics` server capability.

**Note:** This might be a bit confusing but a workspace diagnostics is
not for a single workspace but for all the workspaces that the server
handles. These are the ones that the server received during
initialization. Currently, the ty server doesn't support multiple
workspaces so this capability is also limited to provide diagnostics
only for a single workspace (the first one if the client provided
multiple).

A new `ty.diagnosticMode` server setting is added which can be either
`workspace` (for workspace diagnostics) or `openFilesOnly` (for checking
only open files) (default). This is same as
`python.analysis.diagnosticMode` that Pyright / Pylance utilizes. In the
future, we could use the value under `python.*` namespace as fallback to
improve the experience on user side to avoid setting the value multiple
times.

Part of: astral-sh/ty#81

## Test Plan

This capability was introduced in the current LSP version (~3 years) and
the way it's implemented by various clients are a bit different. I've
provided notes on what I've noticed and what would need to be done on
our side to further improve the experience.

### VS Code

VS Code sends the `workspace/diagnostic` requests every ~2 second:

```
[Trace - 12:12:32 PM] Sending request 'workspace/diagnostic - (403)'.
[Trace - 12:12:32 PM] Received response 'workspace/diagnostic - (403)' in 2ms.
[Trace - 12:12:34 PM] Sending request 'workspace/diagnostic - (404)'.
[Trace - 12:12:34 PM] Received response 'workspace/diagnostic - (404)' in 2ms.
[Trace - 12:12:36 PM] Sending request 'workspace/diagnostic - (405)'.
[Trace - 12:12:36 PM] Received response 'workspace/diagnostic - (405)' in 2ms.
[Trace - 12:12:38 PM] Sending request 'workspace/diagnostic - (406)'.
[Trace - 12:12:38 PM] Received response 'workspace/diagnostic - (406)' in 3ms.
[Trace - 12:12:40 PM] Sending request 'workspace/diagnostic - (407)'.
[Trace - 12:12:40 PM] Received response 'workspace/diagnostic - (407)' in 2ms.
...
```

I couldn't really find any resource that explains this behavior. But,
this does mean that we'd need to implement the caching layer via the
previous result ids sooner. This will allow the server to avoid sending
all the diagnostics on every request and instead just send a response
stating that the diagnostics hasn't changed yet. This could possibly be
achieved by using the salsa ID.

If we switch from workspace diagnostics to open-files diagnostics, the
server would send the diagnostics only via the `textDocument/diagnostic`
endpoint. Here, when a document containing the diagnostic is closed, the
server would send a publish diagnostics notification with an empty list
of diagnostics to clear the diagnostics from that document. The issue is
the VS Code doesn't seem to be clearing the diagnostics in this case
even though it receives the notification. (I'm going to open an issue on
VS Code side for this today.)


https://github.com/user-attachments/assets/b0c0833d-386c-49f5-8a15-0ac9133e15ed

### Zed

Zed's implementation works by refreshing the workspace diagnostics
whenever the content of the documents are changed. This seems like a
very reasonable behavior and I was a bit surprised that VS Code didn't
use this heuristic.


https://github.com/user-attachments/assets/71c7b546-7970-434a-9ba0-4fa620647f6c

### Neovim

Neovim only recently added support for workspace diagnostics
(https://github.com/neovim/neovim/pull/34262, merged ~3 weeks ago) so
it's only available on nightly versions.

The initial support is limited and requires fetching the workspace
diagnostics manually as demonstrated in the video. It doesn't support
refreshing the workspace diagnostics either, so that would need to be
done manually as well. I'm assuming that these are just a temporary
limitation and will be implemented before the stable release.


https://github.com/user-attachments/assets/25b4a0e5-9833-4877-88ad-279904fffaf9
2025-07-03 11:04:54 +00:00
Dhruv Manilawala
a95c18a8e1 [ty] Add background request task support (#19041)
## Summary

This PR adds a new trait to support running a request in the background.

Currently, there exists a `BackgroundDocumentRequestHandler` trait which
is similar but is scoped to a specific document (file in an editor
context). The new trait `BackgroundRequestHandler` is not tied to a
specific document nor a specific project but it's for the entire
workspace.

This is added to support running workspace wide requests like computing
the [workspace
diagnostics](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_diagnostic)
or [workspace
symbols](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_symbol).

**Note:** There's a slight difference with what a "workspace" means
between the server and ty. Currently, there's a 1-1 relationship between
a workspace in an editor and the project database corresponding to that
workspace in ty but this could change in the future when Micha adds
support for multiple workspaces or multi-root workspaces.

The data that would be required by the request handler (based on
implementing workspace diagnostics) is the list of databases
(`ProjectDatabse`) corresponding to the projects in the workspace and
the index (`Index`) that contains the open documents. The
`WorkspaceSnapshot` represents this and is passed to the handler similar
to `DocumentSnapshot`.

## Test Plan

This is used in implementing the workspace diagnostics which is where
this is tested.
2025-07-03 11:01:10 +00:00
David Peter
e212dc2e8e [ty] Restructure/move dataclass tests (#19117)
Before I'm adding even more dataclass-related files, let's organize them
in a separate folder.
2025-07-03 10:36:14 +00:00
Aria Desires
c4f2eec865 [ty] Remove last vestiges of std::path from ty_server (#19088)
Fixes https://github.com/astral-sh/ty/issues/603
2025-07-03 15:18:30 +05:30
Zanie Blue
9fc04d6bf0 Use "python" for markdown code fences in on-hover content (#19082)
Instead of "text".

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

We may not want this because the type display implementations are not
guaranteed to be valid Python, however, unless they're going to
highlight invalid syntax this seems like a better interim value than
"text"? I'm not the expert though. See
https://github.com/astral-sh/ty/issues/749#issuecomment-3026201114 for
prior commentary.

edit: Going back further to
https://github.com/astral-sh/ruff/pull/17057#discussion_r2028151621 for
prior context, it turns out they _do_ highlight invalid syntax in red
which is quite unfortunate and probably a blocker here.
2025-07-03 10:50:34 +05:30
Matthew Mckee
352b896c89 [ty] Add subtyping between SubclassOf and CallableType (#19026)
## Summary

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

There were previously some false positives here.

## Test Plan

Updated `is_subtype_of.md` and `is_assignable_to.md`
2025-07-02 19:22:31 -07:00
GiGaGon
321575e48f [flake8-pyi] Make example error out-of-the-box (PYI042) (#19101)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

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

Part of #18972

This PR makes [snake-case-type-alias
(PYI042)](https://docs.astral.sh/ruff/rules/snake-case-type-alias/#snake-case-type-alias-pyi042)'s
example error out-of-the-box

[Old example](https://play.ruff.rs/8fafec81-2228-4ffe-81e8-1989b724cb47)
```py
type_alias_name: TypeAlias = int
```

[New example](https://play.ruff.rs/b396746c-e6d2-423c-bc13-01a533bb0747)
```py
from typing import TypeAlias

type_alias_name: TypeAlias = int
```

Imports were also added to the "use instead" section.

## Test Plan

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

N/A, no functionality/tests affected
2025-07-02 22:31:15 +01:00
GiGaGon
066018859f [pyflakes] Fix backslash in docs (F621) (#19098)
<!--
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 fixes the docs for [expressions-in-star-assignment
(F621)](https://docs.astral.sh/ruff/rules/expressions-in-star-assignment/#expressions-in-star-assignment-f621)
having a backslash `\` before the left shifts `<<`. I'm not sure why
this happened in the first place, as the docstring looks fine, but
putting the `<<` inside a code block fixes it. I was not able to track
down the source of the issue either. The only other rule with a `<<` is
[missing-whitespace-around-bitwise-or-shift-operator
(E227)](https://docs.astral.sh/ruff/rules/missing-whitespace-around-bitwise-or-shift-operator/#missing-whitespace-around-bitwise-or-shift-operator-e227),
which already has it in a code block.

Old docs page:

![image](https://github.com/user-attachments/assets/993106c6-5d83-4aed-836b-e252f5b64916)
> In Python 3, no more than 1 \\<< 8 assignments are allowed before a
starred expression, and no more than 1 \\<< 24 expressions are allowed
after a starred expression.

New docs page:

![image](https://github.com/user-attachments/assets/3b40b35f-f39e-49f1-8b2e-262dda4085b4)
> In Python 3, no more than `1 << 8` assignments are allowed before a
starred expression, and no more than `1 << 24` expressions are allowed
after a starred expression.

## Test Plan

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

N/A, no tests/functionality affected.
2025-07-02 15:00:33 -04:00
David Peter
f76d3f87cf [ty] Allow declared-only class-level attributes to be accessed on the class (#19071)
## Summary

Allow declared-only class-level attributes to be accessed on the class:
```py
class C:
    attr: int

C.attr  # this is now allowed
``` 

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

## Ecosystem analysis


* We see many removed `unresolved-attribute` false-positives for code
that makes use of sqlalchemy, as expected (see changes for `prefect`)
* We see many removed `call-non-callable` false-positives for uses of
`pytest.skip` and similar, as expected
* Most new diagnostics seem to be related to cases like the following,
where we previously inferred `int` for `Derived().x`, but now we infer
`int | None`. I think this should be a
conflicting-declarations/bad-override error anyway? The new behavior may
even be preferred here?
  ```py
  class Base:
      x: int | None
  
  
  class Derived(Base):
      def __init__(self):
          self.x: int = 1
  ```
2025-07-02 18:03:56 +02:00
Micha Reiser
5f426b9f8b [ty] Remove ScopedExpressionId (#19019)
## Summary

The motivation of `ScopedExpressionId` was that we have an expression
identifier that's local to a scope and, therefore, unlikely to change if
a user makes changes in another scope. A local identifier like this has
the advantage that query results may remain unchanged even if other
parts of the file change, which in turn allows Salsa to short-circuit
dependent queries.

However, I noticed that we aren't using `ScopedExpressionId` in a place
where it's important that the identifier is local. It's main use is
inside `infer` which we always run for the entire file. The one
exception to this is `Unpack` but unpack runs as part of `infer`.

Edit: The above isn't entirely correct. We used ScopedExpressionId in
TypeInference which is a query result. Now using ExpressionNodeKey does
mean that a change to the AST invalidates most if not all TypeInference
results of a single file. Salsa then has to run all dependent queries to
see if they're affected by this change even if the change was local to
another scope.

If this locality proves to be important I suggest that we create two
queries on top of TypeInference: one that returns the expression map
which is mainly used in the linter and type inference and a second that
returns all remaining fields. This should give us a similar optimization
at a much lower cost

I also considered remove `ScopedUseId` but I believe that one is still
useful because using `ExpressionNodeKey` for it instead would mean that
all `UseDefMap` change when a single AST node changes. Whether this is
important is something difficult to assess. I'm simply not familiar
enough with the `UseDefMap`. If the locality doesn't matter for the
`UseDefMap`, then a similar change could be made and `bindings_by_use`
could be changed to an `FxHashMap<UseId, Bindings>` where `UseId` is a
thin wrapper around `NodeKey`.

Closes https://github.com/astral-sh/ty/issues/721
2025-07-02 17:57:32 +02:00
GiGaGon
37ba185c04 [flake8-pyi] Make example error out-of-the-box (PYI059) (#19080)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-07-02 16:49:54 +01:00
David Peter
93413d3631 [ty] Update docs links (#19092)
Point everything to the new documentation at https://docs.astral.sh/ty/
2025-07-02 17:34:56 +02:00
Zanie Blue
efd9b75352 Avoid reformatting comments in rules reference documentation (#19093)
closes https://github.com/astral-sh/ty/issues/754
2025-07-02 17:16:44 +02:00
David Peter
4cf56d7ad4 [ty] Fix lint summary wording (#19091) 2025-07-02 16:32:11 +02:00
David Peter
4e4e428a95 [ty] Fix link in generate_ty_rules (#19090) 2025-07-02 14:21:32 +00:00
Zanie Blue
522fd4462e Fix header levels in generated settings reference (#19089)
The headers were one level too deep for child items, and the top-level
`rules` header was way off.
2025-07-02 16:01:23 +02:00
David Peter
e599c9d0d3 [ty] Adapt generate_ty_rules for MkDocs (#19087)
## Summary

Adapts the Markdown for the rules-reference documentation page for
MkDocs.
2025-07-02 16:01:10 +02:00
Micha Reiser
e9b5ea71b3 Update Salsa (#19020)
## Summary

This PR updates Salsa to pull in Ibraheem's multithreading improvements (https://github.com/salsa-rs/salsa/pull/921).

## Performance

A small regression for single-threaded benchmarks is expected because
papaya is slightly slower than a `Mutex<FxHashMap>` in the uncontested
case (~10%). However, this shouldn't matter as much in practice because:

1. Salsa has a fast-path when only using 1 DB instance which is the
common case in production. This fast-path is not impacted by the changes
but we measure the slow paths in our benchmarks (because we use multiple
db instances)
2. Fixing the 10x slowdown for the congested case (multi threading)
outweights the downsides of a 10% perf regression for single threaded
use cases, especially considering that ty is heavily multi threaded.

## Test Plan

`cargo test`
2025-07-02 09:55:37 -04:00
Ibraheem Ahmed
ebc70a4002 [ty] Support LSP go-to with vendored typeshed stubs (#19057)
## Summary

Extracts the vendored typeshed stubs lazily and caches them on the local
filesystem to support go-to in the LSP.

Resolves https://github.com/astral-sh/ty/issues/77.
2025-07-02 07:58:58 -04:00
Micha Reiser
f7fc8fb084 [ty] Request configuration from client (#18984)
## Summary

This PR makes the necessary changes to the server that it can request
configurations from the client using the `configuration` request.
This PR doesn't make use of the request yet. It only sets up the
foundation (mainly the coordination between client and server)
so that future PRs could pull specific settings. 

I plan to use this for pulling the Python environment from the Python
extension.

Deno does something very similar to this.

## Test Plan

Tested that diagnostics are still shown.
2025-07-02 14:31:41 +05:30
GiGaGon
cdf91b8b74 [flake8-pyi] Make example error out-of-the-box (PYI062) (#19079)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

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

Part of #18972

This PR makes [duplicate-literal-member
(PYI062)](https://docs.astral.sh/ruff/rules/duplicate-literal-member/#duplicate-literal-member-pyi062)'s
example error out-of-the-box

[Old example](https://play.ruff.rs/6b00b41c-c1c5-4421-873d-fc2a143e7337)
```py
foo: Literal["a", "b", "a"]
```

[New example](https://play.ruff.rs/1aea839b-9ae8-4848-bb83-2637e1a68ce4)
```py
from typing import Literal

foo: Literal["a", "b", "a"]
```

Imports were also added to the "use instead" section.

## Test Plan

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

N/A, no functionality/tests affected
2025-07-02 08:21:39 +01:00
Dhruv Manilawala
d1e705738e [ty] Log target names at trace level (#19084)
Follow-up to https://github.com/astral-sh/ruff/pull/19083, also log the
target names like `ty_python_semantic::module_resolver::resolver` in
`2025-07-02 10:12:20.188697000 DEBUG
ty_python_semantic::module_resolver::resolver: Adding first-party search
path '/Users/dhruv/playground/ty_server'` at trace level.
2025-07-02 04:49:36 +00:00
Dhruv Manilawala
c3d9b21db5 [ty] Use better datetime format for server logs (#19083)
This PR improves the timer format for ty server logs to be same as Ruff.

Ref: https://github.com/astral-sh/ruff/pull/16389
2025-07-02 04:39:12 +00:00
Alex Waygood
316c1b21e2 [ty] Add some missing calls to normalized_impl (#19074)
## Summary

I hoped this might fix the latest stack overflows on
https://github.com/astral-sh/ruff/pull/18659... it doesn't look like it
does, but these changes seem like they're probably correct anyway...?

## Test Plan

<!-- How was it tested? -->
2025-07-01 17:57:52 +01:00
Brent Westbrook
47733c0647 Remove new codspeed dependencies (#19073)
Summary
--

Updates to codspeed 3.0.2, removing some of the new dependencies
introduced in 3.0. See https://github.com/astral-sh/uv/pull/14396 and
https://github.com/CodSpeedHQ/codspeed-rust/pull/108 for the similar PR
in uv and the upstream fix, respectively.

Test Plan
--

N/a
2025-07-01 12:20:31 -04:00
NamelessGO
48366a7bbb Docs: Add Anki to Who's Using Ruff (#19072)
https://github.com/ankitects/anki/pull/4119

<!--
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? -->

Add Anki to Who's Using Ruff (README)

---------

Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
2025-07-01 15:44:04 +00:00
GiGaGon
cc736c3a51 [refurb] Fix false positive on empty tuples (FURB168) (#19058)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

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

This PR fixes #19047 / the [isinstance-type-none
(FURB168)](https://docs.astral.sh/ruff/rules/isinstance-type-none/#isinstance-type-none-furb168)
tuple false positive by adding a check if the tuple is empty to the
code. I also noticed there was another false positive with the other
tuple check in the same function, so I fixed it the same way.
`Union[()]` is invalid at runtime with `TypeError: Cannot take a Union
of no types.`, but it is accepted by `basedpyright`
[playground](https://basedpyright.com/?pythonVersion=3.8&typeCheckingMode=all&code=GYJw9gtgBALgngBwJYDsDmUkQWEMoCqKSYKAsAFAgCmAbtQIYA2A%2BvAtQBREkoDanAJQBdQUA)
and is equivalent to `Never`, so I fixed it anyways. I'm getting on a
side tangent here, but it looks like MyPy doesn't accept it, and ty
[playground](https://play.ty.dev/c2c468b6-38e4-4dd9-a9fa-0276e843e395)
gives `@Todo`.

## Test Plan

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

Added two test cases for the two false positives.
[playground](https://play.ruff.rs/a53afc21-9a1d-4b9b-9346-abfbeabeb449)
2025-07-01 10:26:41 -04:00
GiGaGon
8cc14ad02d [flake8-datetimez] Make DTZ901 example error out-of-the-box (#19056)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

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

Part of #18972

This PR makes [datetime-min-max
(DTZ901)](https://docs.astral.sh/ruff/rules/datetime-min-max/#datetime-min-max-dtz901)'s
example error out-of-the-box

[Old example](https://play.ruff.rs/c1202727-1a18-4d3f-92a4-334ede07ed3e)
```py
datetime.max
```

[New example](https://play.ruff.rs/af2c76aa-9beb-46bc-8e27-faf53ecdbe8c)
```py
import datetime

datetime.datetime.max
```

I also added imports to the problem demonstration and use instead.

## Test Plan

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

N/A, no functionality/tests affected
2025-07-01 09:57:34 -04:00
Илья Любавский
667dc62038 [ruff] Fix syntax error introduced for an empty string followed by a u-prefixed string (UP025) (#18899)
## Summary
/closes #18895
## Test Plan

---------

Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
2025-07-01 09:34:08 -04:00
David Peter
dac4e356eb [ty] Use all reachable bindings for instance attributes and deferred lookups (#18955)
## Summary

Remove a hack in control flow modeling that was treating `return`
statements at the end of function bodies in a special way (basically
considering the state *just before* the `return` statement as the
end-of-scope state). This is not needed anymore now that #18750 has been
merged.

In order to make this work, we now use *all reachable bindings* for
purposes of finding implicit instance attribute assignments as well as
for deferred lookups of symbols. Both would otherwise be affected by
this change:
```py
def C:
    def f(self):
        self.x = 1  # a reachable binding that is not visible at the end of the scope
        return
```

```py
def f():
    class X: ...  # a reachable binding that is not visible at the end of the scope
    x: "X" = X()  # deferred use of `X`
    return
```

Implicit instance attributes also required another change. We previously
kept track of possibly-unbound instance attributes in some cases, but we
now give up on that completely and always consider *implicit* instance
attributes to be bound if we see a reachable binding in a reachable
method. The previous behavior was somewhat inconsistent anyway because
we also do not consider attributes possibly-unbound in other scenarios:
we do not (and can not) keep track of whether or not methods are called
that define these attributes.

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

## Ecosystem analysis

I think this looks very positive!

* We see an unsurprising drop in `possibly-unbound-attribute`
diagnostics (599), mostly for classes that define attributes in `try …
except` blocks, `for` loops, or `if … else: raise …` constructs. There
might obviously also be true positives that got removed, but the vast
majority should be false positives.
* There is also a drop in `possibly-unresolved-reference` /
`unresolved-reference` diagnostics (279+13) from the change to deferred
lookups.
* Some `invalid-type-form` false positives got resolved (13), because we
can now properly look up the names in the annotations.
* There are some new *true* positives in `attrs`, since we understand
the `Attribute` annotation that was previously inferred as `Unknown`
because of a re-assignment after the class definition.


## Test Plan

The existing attributes.md test suite has sufficient coverage here.
2025-07-01 14:38:36 +02:00
Alex Waygood
ebf59e2bef [ty] Rework disjointness of protocol instances vs types with possibly unbound attributes (#19043) 2025-07-01 12:47:27 +01:00
Alex Waygood
c6fd11fe36 [ty] Eagerly evaluate more constraints based on the raw AST (#19068) 2025-07-01 10:17:22 +00:00
David Peter
7d468ee58a [ty] Model reachability of star import definitions for nonlocal lookups (#19066)
## Summary

Temporarily modify `UseDefMapBuilder::reachability` for star imports in
order for new definitions to pick up the right reachability. This was
already working for `UseDefMapBuilder::place_states`, but not for
`UseDefMapBuilder::reachable_definitions`.

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

## Test Plan

Regression test
2025-07-01 11:06:37 +02:00
David Peter
4016521bf6 [ty] Eagerly evaluate TYPE_CHECKING constraints (#19044)
## Summary

Evaluate `TYPE_CHECKING` to `ALWAYS_TRUE` and `not TYPE_CHECKING` to
`ALWAYS_FALSE` during semantic index building. This is a follow-up to
https://github.com/astral-sh/ruff/pull/18998 and is in principle just a
performance optimization. We see some (favorable) ecosystem changes
because we can eliminate definitely-unreachable branches early now and
retain narrowing constraints without solving
https://github.com/astral-sh/ty/issues/690 first.
2025-07-01 11:05:52 +02:00
GiGaGon
b8653a9d3a [flake8-pyi] Make PYI032 example error out-of-the-box (#19061) 2025-07-01 07:50:58 +01:00
github-actions[bot]
966adca6f6 [ty] Sync vendored typeshed stubs (#19060)
Close and reopen this PR to trigger CI

Co-authored-by: typeshedbot <>
2025-07-01 07:45:06 +01:00
renovate[bot]
77941af1c6 Update Rust crate get-size2 to v0.5.1 (#19035)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [get-size2](https://redirect.github.com/bircni/get-size2) |
workspace.dependencies | patch | `0.5.0` -> `0.5.1` |

---

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

---

### Release Notes

<details>
<summary>bircni/get-size2 (get-size2)</summary>

###
[`v0.5.1`](https://redirect.github.com/bircni/get-size2/blob/HEAD/CHANGELOG.md#051---2025-06-25)

[Compare
Source](https://redirect.github.com/bircni/get-size2/compare/0.5.0...0.5.1)

##### Bug Fixes

- correctly determine size for enums
([#&#8203;24](https://redirect.github.com/bircni/get-size2/issues/24)) -
([3c5bd18](3c5bd18cac))
- Nicolas

##### Miscellaneous Chores

- add top-level `heap_size` function
([#&#8203;25](https://redirect.github.com/bircni/get-size2/issues/25)) -
([f3b5e6e](f3b5e6e38c))
- Ibraheem Ahmed

##### Build

- update to newer cargo-verset to set dependency version automatically -
([b1154e4](b1154e4572))
- Nicolas

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 23:17:36 -04:00
Dylan
4bc170a5c1 Make dependency get-size2 truly optional in ruff_python_ast (#19052)
Gates all uses of `get-size2` behind the feature `get-size` in the crate
`ruff_python_ast`. Also requires that `ruff_text_size` is pulled in with
the feature `get-size` enabled if we enable the same-named feature for
`ruff_python_ast`.
2025-06-30 21:50:59 -05:00
Robsdedude
28ab61d885 [pyupgrade] Avoid PEP-604 unions with typing.NamedTuple (UP007, UP045) (#18682)
<!--
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
Make `UP045` ignore `Optional[NamedTuple]` as `NamedTuple` is a function
(not a proper type). Rewriting it to `NamedTuple | None` breaks at
runtime. While type checkers currently accept `NamedTuple` as a type,
they arguably shouldn't. Therefore, we outright ignore it and don't
touch or lint on it.

For a more detailed discussion, see the linked issue.

## Test Plan
Added examples to the existing tests.

## Related Issues
Fixes: https://github.com/astral-sh/ruff/issues/18619
2025-06-30 17:22:23 -04:00
GiGaGon
4963835d0d [flake8-bandit] Make S604 and S609 examples error out-of-the-box (#19049)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

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

Part of #18972

Both in one PR since they are in the same file.

S604
---

This PR makes [call-with-shell-equals-true
(S604)](https://docs.astral.sh/ruff/rules/call-with-shell-equals-true/#call-with-shell-equals-true-s604)'s
example error out-of-the-box

[Old example](https://play.ruff.rs/a054fb79-7653-47f7-9ab5-3d8b7540c810)
```py
import subprocess

user_input = input("Enter a command: ")
subprocess.run(user_input, shell=True)
```

[New example](https://play.ruff.rs/6fea81b4-e745-4b85-8bea-faaabea5c86d)
```py
import my_custom_subprocess

user_input = input("Enter a command: ")
my_custom_subprocess.run(user_input, shell=True)
```

The old example doesn't raise `S604` because it gets overwritten by
[subprocess-popen-with-shell-equals-true
(S602)](https://docs.astral.sh/ruff/rules/subprocess-popen-with-shell-equals-true/#subprocess-popen-with-shell-equals-true-s602)
(which is a good idea to prevent two lints saying the same thing from
being raised)

S609
---

This PR makes [unix-command-wildcard-injection
(S609)](https://docs.astral.sh/ruff/rules/unix-command-wildcard-injection/#unix-command-wildcard-injection-s609)'s
example error out-of-the-box

[Old example](https://play.ruff.rs/849860fa-0d12-4916-bdbc-64a0fa14cd9b)
```py
import subprocess

subprocess.Popen(["chmod", "777", "*.py"])
```

[New example](https://play.ruff.rs/77a54d7c-cf78-4158-bcf8-96dd698cf366)
```py
import subprocess

subprocess.Popen(["chmod", "777", "*.py"], shell=True)
```

I'm not familiar enough with `subprocess` to know why `shell=True` is
required to make `S609` raise here, but it works.

## Test Plan

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

N/A, no functionality/tests affected
2025-06-30 16:10:14 -05:00
GiGaGon
09fa80f94c [flake8-datetimez] Make DTZ011 example error out-of-the-box (#19055)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

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

Part of #18972

This PR makes [call-date-today
(DTZ011)](https://docs.astral.sh/ruff/rules/call-date-today/#call-date-today-dtz011)'s
example error out-of-the-box

[Old example](https://play.ruff.rs/b42d6aef-7777-4b3b-9f96-19132000b765)
```py
import datetime

datetime.datetime.today()
```

[New example](https://play.ruff.rs/8577c3c1-cfa8-425b-b1e1-4c53b2a48375)
```py
import datetime

datetime.date.today()
```

## Test Plan

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

N/A, no functionality/tests affected
2025-06-30 15:54:04 -05:00
GiGaGon
fde82fc563 [flake8-bugbear] Make B028 example error out-of-the-box (#19054)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

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

Part of #18972

This PR makes [no-explicit-stacklevel
(B028)](https://docs.astral.sh/ruff/rules/no-explicit-stacklevel/#no-explicit-stacklevel-b028)'s
example error out-of-the-box

[Old example](https://play.ruff.rs/1ee80aec-2d6e-4a3f-8e98-da82b6a9f544)
```py
warnings.warn("This is a warning")
```

[New example](https://play.ruff.rs/343593aa-38a0-4d76-a32b-5abd0a4306cc)
```py
import warnings

warnings.warn("This is a warning")
```

Imports were also added to the "use instead" section

## Test Plan

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

N/A, no functionality/tests affected
2025-06-30 15:49:40 -05:00
GiGaGon
96decb17a9 [flake8-bugbear] Make B911 example error out-of-the-box (#19051)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

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

Part of #18972

This PR makes [batched-without-explicit-strict
(B911)](https://docs.astral.sh/ruff/rules/batched-without-explicit-strict/#batched-without-explicit-strict-b911)'s
example error out-of-the-box

[Old example](https://play.ruff.rs/a897d96b-0749-4291-8a62-dfd4caf290a0)
```py
itertools.batched(iterable, n)
```

[New example](https://play.ruff.rs/1c1e0ab7-014c-4dc2-abed-c2cb6cd01f70)
```py
import itertools

itertools.batched(iterable, n)
```

Imports were also added to the "use instead" sections

## Test Plan

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

N/A, no functionality/tests affected
2025-06-30 15:48:02 -05:00
Carl Meyer
2ae0bd9464 [ty] Normalize recursive types using Any (#19003)
## Summary

This just replaces one temporary solution to recursive protocols (the
`SelfReference` mechanism) with another one (track seen types when
recursively descending in `normalize` and replace recursive references
with `Any`). But this temporary solution can handle mutually-recursive
types, not just self-referential ones, and it's sufficient for the
primer ecosystem and some other projects we are testing on to no longer
stack overflow.

The follow-up here will be to properly handle these self-references
instead of replacing them with `Any`.

We will also eventually need cycle detection on more recursive-descent
type transformations and tests.

## Test Plan

Existing tests (including recursive-protocol tests) and primer.

Added mdtest for mutually-recursive protocols that stack-overflowed
before this PR.
2025-06-30 12:07:57 -07:00
Robsdedude
34052a1185 [flake8-comprehensions] Fix C420 to prepend whitespace when needed (#18616)
<!--
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
This PR fixes rule C420's fix. The fix replaces `{...}` with
`dict....(...)`. Therefore, if there is any identifier or such right
before the fix, the fix will fuse that previous token with `dict...`.

The example in the issue is
```python
0 or{x: None for x in "x"}
# gets "fixed" to
0 ordict.fromkeys(iterable)
```

## Related Issues

Fixes: https://github.com/astral-sh/ruff/issues/18599
2025-06-30 12:38:26 -04:00
Dan Parizher
9f0d3cca89 [pydocstyle] Fix D413 infinite loop for parenthesized docstring (#18930)
<!--
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 #18908
2025-06-30 10:49:13 -04:00
Robsdedude
eb9d9c3646 [perflint] Fix PERF403 panic on attribute or subscription loop variable (#19042)
## Summary

Fixes: https://github.com/astral-sh/ruff/issues/19005

## Test Plan

Reproducer from issue report plus some extra cases that would cause the
panic were added.
2025-06-30 10:47:49 -04:00
Adrien Cacciaguerra
4fbf7e9de8 Bump CodSpeed to v3 (#19046)
## Summary

<!-- What's the purpose of the change? What does it do, and why? -->
As discussed on Slack, there was an issue with the walltime metrics with
`divan`. The issue was fixed with the latest version of `cargo-codspeed`
and `codpseed-divan-compat`:
https://github.com/CodSpeedHQ/codspeed-rust/releases/tag/v3.0.0.

This PR updates all crates related to CodSpeed. A performance increase
of the following benchmarks is expected, as now the correct metric will
be used.

```
crates/ruff_benchmark/benches/ty_walltime.rs::multithreaded[pydantic]
crates/ruff_benchmark/benches/ty_walltime.rs::small[altair]
crates/ruff_benchmark/benches/ty_walltime.rs::small[freqtrade]
crates/ruff_benchmark/benches/ty_walltime.rs::small[pydantic]
crates/ruff_benchmark/benches/ty_walltime.rs::small[tanjun]
```

Once this is merged, we will update the historic data of the affected
benchmark on the CodSpeed UI, so that no false positives will appear.
2025-06-30 10:11:23 -04:00
GiGaGon
b23b4071eb [flake8-async] Make ASYNC220, ASYNC221, and ASYNC222 examples error out-of-the-box (#18978)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

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

Part of #18972

All three in one PR since they are in the same file.

This PR makes [create-subprocess-in-async-function
(ASYNC220)](https://docs.astral.sh/ruff/rules/create-subprocess-in-async-function/#create-subprocess-in-async-function-async220)'s
example error out-of-the-box

[Old example](https://play.ruff.rs/465036af-d75f-4bda-ba24-e50e8618bf16)
```py
async def foo():
    os.popen(cmd)
```

[New example](https://play.ruff.rs/8cf43d50-f9e1-45d6-b711-968c7135f2e0)
```py
import os


async def foo():
    os.popen(cmd)
```

Imports were also added to the `Use instead:` section to make it valid
code out-of-the-box.

This PR makes [run-process-in-async-function
(ASYNC221)](https://docs.astral.sh/ruff/rules/run-process-in-async-function/#run-process-in-async-function-async221)'s
example error out-of-the-box

[Old example](https://play.ruff.rs/0698aaa1-c722-4f04-b56c-61edec06945c)
```py
async def foo():
    subprocess.run(cmd)
```

[New example](https://play.ruff.rs/e05bfcbc-e681-4a28-8f50-2c0c2537d038)
```py
import subprocess


async def foo():
    subprocess.run(cmd)
```

Imports were also added to the `Use instead:` section to make it valid
code out-of-the-box.

This PR makes [wait-for-process-in-async-function
(ASYNC222)](https://docs.astral.sh/ruff/rules/wait-for-process-in-async-function/#wait-for-process-in-async-function-async222)'s
example error out-of-the-box

[Old example](https://play.ruff.rs/4305d477-8995-462d-83ae-435731d71e67)
```py
async def foo():
    os.waitpid(0)
```

[New example](https://play.ruff.rs/ad10c042-3b18-49ca-8f5c-5ab720516da1)
```py
import os


async def foo():
    os.waitpid(0)
```

Imports were also added to the `Use instead:` section to make it valid
code out-of-the-box.

## Test Plan

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

N/A, no functionality/tests affected
2025-06-30 09:47:29 -04:00
GiGaGon
462dbadee4 [Airflow] Make AIR302 example error out-of-the-box (#18988)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

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

Part of #18972

This PR makes [airflow3-moved-to-provider
(AIR302)](https://docs.astral.sh/ruff/rules/airflow3-moved-to-provider/#airflow3-moved-to-provider-air302)'s
example error out-of-the-box

[Old example](https://play.ruff.rs/1026c008-57bc-4330-93b9-141444f2a611)
```py
from airflow.auth.managers.fab.fab_auth_manage import FabAuthManager
```

[New example](https://play.ruff.rs/b690e809-a81d-4265-9fde-1494caa0b7fd)
```py
from airflow.auth.managers.fab.fab_auth_manager import FabAuthManager

fab_auth_manager_app = FabAuthManager().get_fastapi_app()
```

## Test Plan

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

N/A, no functionality/tests affected
2025-06-30 09:45:15 -04:00
Robsdedude
a3638b3adc [pyupgrade] Mark UP008 fix safe if no comments in range (#18683)
<!--
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
Mark `UP008`'s fix safe if it won't delete comments.

## Relevant Issues
Fixes: https://github.com/astral-sh/ruff/issues/18533

---------

Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
2025-06-30 09:42:05 -04:00
GiGaGon
f857546aeb [flake8-bandit] Make S201 example error out-of-the-box (#19017)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

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

Part of #18972

This PR makes [flask-debug-true
(S201)](https://docs.astral.sh/ruff/rules/flask-debug-true/#flask-debug-true-s201)'s
example error out-of-the-box

[Old example](https://play.ruff.rs/d5e1a013-1107-4223-9094-0e8393ad3c64)
```py
import flask

app = Flask()

app.run(debug=True)
```

[New example](https://play.ruff.rs/c4aebd2c-0448-4471-8bad-3e38ace68367)
```py
from flask import Flask

app = Flask()

app.run(debug=True)
```

Imports were also added to the `Use instead:` section to make it valid
code out-of-the-box.

## Test Plan

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

N/A, no functionality/tests affected
2025-06-30 09:39:59 -04:00
हिमांशु
d78f18cda9 [flake8-executable] Allow uvx in shebang line (EXE003) (#18967)
## Summary
closes #18902 

## Test Plan
I have added a test case
2025-06-30 09:38:18 -04:00
David Peter
db3dcd8ad6 [ty] Eagerly simplify 'True' and 'False' constraints (#18998)
## Summary

Simplifies literal `True` and `False` conditions to `ALWAYS_TRUE` /
`ALWAYS_FALSE` during semantic index building. This allows us to eagerly
evaluate more constraints, which should help with performance (looks
like there is a tiny 1% improvement in instrumented benchmarks), but
also allows us to eliminate definitely-unreachable branches in
control-flow merging. This can lead to better type inference in some
cases because it allows us to retain narrowing constraints without
solving https://github.com/astral-sh/ty/issues/690 first:
```py
def _(c: int | None):
    if c is None:
        assert False
    
    reveal_type(c)  # int, previously: int | None
```

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

## Test Plan

* Regression test for https://github.com/astral-sh/ty/issues/713
* Made sure that all ecosystem diffs trace back to removed false
positives
2025-06-30 13:11:52 +02:00
David Peter
54769ac9f9 [ty] While loop modeling cleanup (#18994)
## Summary

I found the previous code here very confusing, and it also did some
unnecessary work. Hopefully this is a bit easier to understand.
2025-06-30 11:38:25 +02:00
renovate[bot]
c80762debd Update pre-commit dependencies (#19038)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
|
[astral-sh/ruff-pre-commit](https://redirect.github.com/astral-sh/ruff-pre-commit)
| repository | minor | `v0.11.13` -> `v0.12.1` |
|
[python-jsonschema/check-jsonschema](https://redirect.github.com/python-jsonschema/check-jsonschema)
| repository | patch | `0.33.0` -> `0.33.1` |
|
[rbubley/mirrors-prettier](https://redirect.github.com/rbubley/mirrors-prettier)
| repository | minor | `v3.5.3` -> `v3.6.2` |
|
[woodruffw/zizmor-pre-commit](https://redirect.github.com/woodruffw/zizmor-pre-commit)
| repository | minor | `v1.9.0` -> `v1.10.0` |

---

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

Note: The `pre-commit` manager in Renovate is not supported by the
`pre-commit` maintainers or community. Please do not report any problems
there, instead [create a Discussion in the Renovate
repository](https://redirect.github.com/renovatebot/renovate/discussions/new)
if you have any questions.

---

### Release Notes

<details>
<summary>astral-sh/ruff-pre-commit (astral-sh/ruff-pre-commit)</summary>

###
[`v0.12.1`](https://redirect.github.com/astral-sh/ruff-pre-commit/releases/tag/v0.12.1)

[Compare
Source](https://redirect.github.com/astral-sh/ruff-pre-commit/compare/v0.12.0...v0.12.1)

See: https://github.com/astral-sh/ruff/releases/tag/0.12.1

###
[`v0.12.0`](https://redirect.github.com/astral-sh/ruff-pre-commit/releases/tag/v0.12.0)

[Compare
Source](https://redirect.github.com/astral-sh/ruff-pre-commit/compare/v0.11.13...v0.12.0)

See: https://github.com/astral-sh/ruff/releases/tag/0.12.0

</details>

<details>
<summary>python-jsonschema/check-jsonschema
(python-jsonschema/check-jsonschema)</summary>

###
[`v0.33.1`](https://redirect.github.com/python-jsonschema/check-jsonschema/blob/HEAD/CHANGELOG.rst#0331)

[Compare
Source](https://redirect.github.com/python-jsonschema/check-jsonschema/compare/0.33.0...0.33.1)

- Update vendored schemas: bamboo-spec, bitbucket-pipelines, circle-ci,
cloudbuild,
compose-spec, dependabot, drone-ci, github-actions, github-workflows,
gitlab-ci,
mergify, readthedocs, renovate, taskfile, travis, woodpecker-ci
(2025-06-22)
- Fix: support `click==8.2.0`
- Fix a bug in `Last-Modified` header parsing which used local time and
could
  result in improper caching. Thanks :user:`fenuks`! (:pr:`565`)

</details>

<details>
<summary>rbubley/mirrors-prettier (rbubley/mirrors-prettier)</summary>

###
[`v3.6.2`](https://redirect.github.com/rbubley/mirrors-prettier/compare/v3.6.1...v3.6.2)

[Compare
Source](https://redirect.github.com/rbubley/mirrors-prettier/compare/v3.6.1...v3.6.2)

###
[`v3.6.1`](https://redirect.github.com/rbubley/mirrors-prettier/compare/v3.6.0...v3.6.1)

[Compare
Source](https://redirect.github.com/rbubley/mirrors-prettier/compare/v3.6.0...v3.6.1)

###
[`v3.6.0`](https://redirect.github.com/rbubley/mirrors-prettier/compare/v3.5.3...v3.6.0)

[Compare
Source](https://redirect.github.com/rbubley/mirrors-prettier/compare/v3.5.3...v3.6.0)

</details>

<details>
<summary>woodruffw/zizmor-pre-commit
(woodruffw/zizmor-pre-commit)</summary>

###
[`v1.10.0`](https://redirect.github.com/zizmorcore/zizmor-pre-commit/releases/tag/v1.10.0)

[Compare
Source](https://redirect.github.com/woodruffw/zizmor-pre-commit/compare/v1.9.0...v1.10.0)

See: https://github.com/zizmorcore/zizmor/releases/tag/v1.10.0

</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.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get
[config
help](https://redirect.github.com/renovatebot/renovate/discussions) if
that's undesired.

---

- [ ] <!-- 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:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Dhruv Manilawala <dhruvmanila@gmail.com>
2025-06-30 08:43:39 +00:00
Robsdedude
4103d73224 Minor code simplification (#19022)
When inside a typing only annotation, the code is always inside an
annotation, too.
2025-06-30 13:42:59 +05:30
renovate[bot]
bedb53daec Update dependency smol-toml to v1.4.0 (#19037)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [smol-toml](https://redirect.github.com/squirrelchat/smol-toml) |
[`1.3.4` ->
`1.4.0`](https://renovatebot.com/diffs/npm/smol-toml/1.3.4/1.4.0) |
[![age](https://developer.mend.io/api/mc/badges/age/npm/smol-toml/1.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/smol-toml/1.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/smol-toml/1.3.4/1.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/smol-toml/1.3.4/1.4.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

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

---

### Release Notes

<details>
<summary>squirrelchat/smol-toml (smol-toml)</summary>

###
[`v1.4.0`](https://redirect.github.com/squirrelchat/smol-toml/releases/tag/v1.4.0)

[Compare
Source](https://redirect.github.com/squirrelchat/smol-toml/compare/v1.3.4...v1.4.0)

This release introduces better support for integers, courtesy of
[@&#8203;Gouvernathor](https://redirect.github.com/Gouvernathor)! It is
now possible to parse integers that are larger than 53 bits as BigInts.
It is also possible to parse all integers as BigInts, enabling full type
preservation of TOML documents.

The project is now tested against Node 24 as well, ensuring stability on
the latest versions of Node.js.

##### What's Changed

- Use more detailed TOML types by
[@&#8203;Gouvernathor](https://redirect.github.com/Gouvernathor) in
[https://github.com/squirrelchat/smol-toml/pull/40](https://redirect.github.com/squirrelchat/smol-toml/pull/40)
- Serialize all numbers as floats by
[@&#8203;Gouvernathor](https://redirect.github.com/Gouvernathor) in
[https://github.com/squirrelchat/smol-toml/pull/42](https://redirect.github.com/squirrelchat/smol-toml/pull/42)
- Use bigint for large numbers by
[@&#8203;Gouvernathor](https://redirect.github.com/Gouvernathor) in
[https://github.com/squirrelchat/smol-toml/pull/41](https://redirect.github.com/squirrelchat/smol-toml/pull/41)

##### New Contributors

- [@&#8203;Gouvernathor](https://redirect.github.com/Gouvernathor) made
their first contribution in
[https://github.com/squirrelchat/smol-toml/pull/40](https://redirect.github.com/squirrelchat/smol-toml/pull/40)

**Full Changelog**:
https://github.com/squirrelchat/smol-toml/compare/v1.3.4...v1.4.0

</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 these
updates 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:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 13:37:20 +05:30
med1844
0ec2ad2fa5 [ty] Emit error for invalid binary operations in type expressions (#18991)
## Summary

This PR adds diagnostic for invalid binary operators in type
expressions. It should close https://github.com/astral-sh/ty/issues/706
if merged.

Please feel free to suggest better wordings for the diagnostic message.

## Test Plan

I modified `mdtest/annotations/invalid.md` and added a test for each
binary operator, and fixed tests that was broken by the new diagnostic.
2025-06-30 10:06:01 +02:00
renovate[bot]
9469a982cc Update dependency ruff to v0.12.1 (#19032)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [ruff](https://docs.astral.sh/ruff)
([source](https://redirect.github.com/astral-sh/ruff),
[changelog](https://redirect.github.com/astral-sh/ruff/blob/main/CHANGELOG.md))
| `==0.12.0` -> `==0.12.1` |
[![age](https://developer.mend.io/api/mc/badges/age/pypi/ruff/0.12.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/pypi/ruff/0.12.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/pypi/ruff/0.12.0/0.12.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/ruff/0.12.0/0.12.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

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

---

### Release Notes

<details>
<summary>astral-sh/ruff (ruff)</summary>

###
[`v0.12.1`](https://redirect.github.com/astral-sh/ruff/blob/HEAD/CHANGELOG.md#0121)

[Compare
Source](https://redirect.github.com/astral-sh/ruff/compare/0.12.0...0.12.1)

##### Preview features

- \[`flake8-errmsg`] Extend `EM101` to support byte strings
([#&#8203;18867](https://redirect.github.com/astral-sh/ruff/pull/18867))
- \[`flake8-use-pathlib`] Add autofix for `PTH202`
([#&#8203;18763](https://redirect.github.com/astral-sh/ruff/pull/18763))
- \[`pygrep-hooks`] Add `AsyncMock` methods to `invalid-mock-access`
(`PGH005`)
([#&#8203;18547](https://redirect.github.com/astral-sh/ruff/pull/18547))
- \[`pylint`] Ignore `__init__.py` files in (`PLC0414`)
([#&#8203;18400](https://redirect.github.com/astral-sh/ruff/pull/18400))
- \[`ruff`] Trigger `RUF037` for empty string and byte strings
([#&#8203;18862](https://redirect.github.com/astral-sh/ruff/pull/18862))
- \[formatter] Fix missing blank lines before decorated classes in
`.pyi` files
([#&#8203;18888](https://redirect.github.com/astral-sh/ruff/pull/18888))

##### Bug fixes

- Avoid generating diagnostics with per-file ignores
([#&#8203;18801](https://redirect.github.com/astral-sh/ruff/pull/18801))
- Handle parenthesized arguments in `remove_argument`
([#&#8203;18805](https://redirect.github.com/astral-sh/ruff/pull/18805))
- \[`flake8-logging`] Avoid false positive for `exc_info=True` outside
`logger.exception` (`LOG014`)
([#&#8203;18737](https://redirect.github.com/astral-sh/ruff/pull/18737))
- \[`flake8-pytest-style`] Enforce `pytest` import for decorators
([#&#8203;18779](https://redirect.github.com/astral-sh/ruff/pull/18779))
- \[`flake8-pytest-style`] Mark autofix for `PT001` and `PT023` as
unsafe if there's comments in the decorator
([#&#8203;18792](https://redirect.github.com/astral-sh/ruff/pull/18792))
- \[`flake8-pytest-style`] `PT001`/`PT023` fix makes syntax error on
parenthesized decorator
([#&#8203;18782](https://redirect.github.com/astral-sh/ruff/pull/18782))
- \[`flake8-raise`] Make fix unsafe if it deletes comments (`RSE102`)
([#&#8203;18788](https://redirect.github.com/astral-sh/ruff/pull/18788))
- \[`flake8-simplify`] Fix `SIM911` autofix creating a syntax error
([#&#8203;18793](https://redirect.github.com/astral-sh/ruff/pull/18793))
- \[`flake8-simplify`] Fix false negatives for shadowed bindings
(`SIM910`, `SIM911`)
([#&#8203;18794](https://redirect.github.com/astral-sh/ruff/pull/18794))
- \[`flake8-simplify`] Preserve original behavior for `except ()` and
bare `except` (`SIM105`)
([#&#8203;18213](https://redirect.github.com/astral-sh/ruff/pull/18213))
- \[`flake8-pyi`] Fix `PYI041`'s fix causing `TypeError` with `None |
None | ...`
([#&#8203;18637](https://redirect.github.com/astral-sh/ruff/pull/18637))
- \[`perflint`] Fix `PERF101` autofix creating a syntax error and mark
autofix as unsafe if there are comments in the `list` call expr
([#&#8203;18803](https://redirect.github.com/astral-sh/ruff/pull/18803))
- \[`perflint`] Fix false negative in `PERF401`
([#&#8203;18866](https://redirect.github.com/astral-sh/ruff/pull/18866))
- \[`pylint`] Avoid flattening nested `min`/`max` when outer call has
single argument (`PLW3301`)
([#&#8203;16885](https://redirect.github.com/astral-sh/ruff/pull/16885))
- \[`pylint`] Fix `PLC2801` autofix creating a syntax error
([#&#8203;18857](https://redirect.github.com/astral-sh/ruff/pull/18857))
- \[`pylint`] Mark `PLE0241` autofix as unsafe if there's comments in
the base classes
([#&#8203;18832](https://redirect.github.com/astral-sh/ruff/pull/18832))
- \[`pylint`] Suppress `PLE2510`/`PLE2512`/`PLE2513`/`PLE2514`/`PLE2515`
autofix if the text contains an odd number of backslashes
([#&#8203;18856](https://redirect.github.com/astral-sh/ruff/pull/18856))
- \[`refurb`] Detect more exotic float literals in `FURB164`
([#&#8203;18925](https://redirect.github.com/astral-sh/ruff/pull/18925))
- \[`refurb`] Fix `FURB163` autofix creating a syntax error for `yield`
expressions
([#&#8203;18756](https://redirect.github.com/astral-sh/ruff/pull/18756))
- \[`refurb`] Mark `FURB129` autofix as unsafe if there's comments in
the `readlines` call
([#&#8203;18858](https://redirect.github.com/astral-sh/ruff/pull/18858))
- \[`ruff`] Fix false positives and negatives in `RUF010`
([#&#8203;18690](https://redirect.github.com/astral-sh/ruff/pull/18690))
- Fix casing of `analyze.direction` variant names
([#&#8203;18892](https://redirect.github.com/astral-sh/ruff/pull/18892))

##### Rule changes

- Fix f-string interpolation escaping in generated fixes
([#&#8203;18882](https://redirect.github.com/astral-sh/ruff/pull/18882))
- \[`flake8-return`] Mark `RET501` fix unsafe if comments are inside
([#&#8203;18780](https://redirect.github.com/astral-sh/ruff/pull/18780))
- \[`flake8-async`] Fix detection for large integer sleep durations in
`ASYNC116` rule
([#&#8203;18767](https://redirect.github.com/astral-sh/ruff/pull/18767))
- \[`flake8-async`] Mark autofix for `ASYNC115` as unsafe if the call
expression contains comments
([#&#8203;18753](https://redirect.github.com/astral-sh/ruff/pull/18753))
- \[`flake8-bugbear`] Mark autofix for `B004` as unsafe if the `hasattr`
call expr contains comments
([#&#8203;18755](https://redirect.github.com/astral-sh/ruff/pull/18755))
- \[`flake8-comprehension`] Mark autofix for `C420` as unsafe if there's
comments inside the dict comprehension
([#&#8203;18768](https://redirect.github.com/astral-sh/ruff/pull/18768))
- \[`flake8-comprehensions`] Handle template strings for comprehension
fixes
([#&#8203;18710](https://redirect.github.com/astral-sh/ruff/pull/18710))
- \[`flake8-future-annotations`] Add autofix (`FA100`)
([#&#8203;18903](https://redirect.github.com/astral-sh/ruff/pull/18903))
- \[`pyflakes`] Mark `F504`/`F522`/`F523` autofix as unsafe if there's a
call with side effect
([#&#8203;18839](https://redirect.github.com/astral-sh/ruff/pull/18839))
- \[`pylint`] Allow fix with comments and document performance
implications (`PLW3301`)
([#&#8203;18936](https://redirect.github.com/astral-sh/ruff/pull/18936))
- \[`pylint`] Detect more exotic `NaN` literals in `PLW0177`
([#&#8203;18630](https://redirect.github.com/astral-sh/ruff/pull/18630))
- \[`pylint`] Fix `PLC1802` autofix creating a syntax error and mark
autofix as unsafe if there's comments in the `len` call
([#&#8203;18836](https://redirect.github.com/astral-sh/ruff/pull/18836))
- \[`pyupgrade`] Extend version detection to include
`sys.version_info.major` (`UP036`)
([#&#8203;18633](https://redirect.github.com/astral-sh/ruff/pull/18633))
- \[`ruff`] Add lint rule `RUF064` for calling `chmod` with non-octal
integers
([#&#8203;18541](https://redirect.github.com/astral-sh/ruff/pull/18541))
- \[`ruff`] Added `cls.__dict__.get('__annotations__')` check (`RUF063`)
([#&#8203;18233](https://redirect.github.com/astral-sh/ruff/pull/18233))
- \[`ruff`] Frozen `dataclass` default should be valid (`RUF009`)
([#&#8203;18735](https://redirect.github.com/astral-sh/ruff/pull/18735))

##### Server

- Consider virtual path for various server actions
([#&#8203;18910](https://redirect.github.com/astral-sh/ruff/pull/18910))

##### Documentation

- Add fix safety sections
([#&#8203;18940](https://redirect.github.com/astral-sh/ruff/pull/18940),[#&#8203;18841](https://redirect.github.com/astral-sh/ruff/pull/18841),[#&#8203;18802](https://redirect.github.com/astral-sh/ruff/pull/18802),[#&#8203;18837](https://redirect.github.com/astral-sh/ruff/pull/18837),[#&#8203;18800](https://redirect.github.com/astral-sh/ruff/pull/18800),[#&#8203;18415](https://redirect.github.com/astral-sh/ruff/pull/18415),[#&#8203;18853](https://redirect.github.com/astral-sh/ruff/pull/18853),[#&#8203;18842](https://redirect.github.com/astral-sh/ruff/pull/18842))
- Use updated pre-commit id
([#&#8203;18718](https://redirect.github.com/astral-sh/ruff/pull/18718))
- \[`perflint`] Small docs improvement to `PERF401`
([#&#8203;18786](https://redirect.github.com/astral-sh/ruff/pull/18786))
- \[`pyupgrade`]: Use `super()`, not `__super__` in error messages
(`UP008`)
([#&#8203;18743](https://redirect.github.com/astral-sh/ruff/pull/18743))
- \[`flake8-pie`] Small docs fix to `PIE794`
([#&#8203;18829](https://redirect.github.com/astral-sh/ruff/pull/18829))
- \[`flake8-pyi`] Correct `collections-named-tuple` example to use
PascalCase assignment
([#&#8203;16884](https://redirect.github.com/astral-sh/ruff/pull/16884))
- \[`flake8-pie`] Add note on type checking benefits to
`unnecessary-dict-kwargs` (`PIE804`)
([#&#8203;18666](https://redirect.github.com/astral-sh/ruff/pull/18666))
- \[`pycodestyle`] Clarify PEP 8 relationship to
`whitespace-around-operator` rules
([#&#8203;18870](https://redirect.github.com/astral-sh/ruff/pull/18870))

##### Other changes

- Disallow newlines in format specifiers of single quoted f- or
t-strings
([#&#8203;18708](https://redirect.github.com/astral-sh/ruff/pull/18708))
- \[`flake8-logging`] Add fix safety section to `LOG002`
([#&#8203;18840](https://redirect.github.com/astral-sh/ruff/pull/18840))
- \[`pyupgrade`] Add fix safety section to `UP010`
([#&#8203;18838](https://redirect.github.com/astral-sh/ruff/pull/18838))

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 13:12:53 +05:30
renovate[bot]
e33980fd5c Update PyO3/maturin-action action to v1.49.3 (#19033)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [PyO3/maturin-action](https://redirect.github.com/PyO3/maturin-action)
| action | patch | `v1.49.2` -> `v1.49.3` |

---

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

---

### Release Notes

<details>
<summary>PyO3/maturin-action (PyO3/maturin-action)</summary>

###
[`v1.49.3`](https://redirect.github.com/PyO3/maturin-action/releases/tag/v1.49.3)

[Compare
Source](https://redirect.github.com/PyO3/maturin-action/compare/v1.49.2...v1.49.3)

##### What's Changed

- Fix `musllinux` builds with `rust-toolchain.toml` containing channel
that is not `stable` by
[@&#8203;FloezeTv](https://redirect.github.com/FloezeTv) in
[https://github.com/PyO3/maturin-action/pull/359](https://redirect.github.com/PyO3/maturin-action/pull/359)

##### New Contributors

- [@&#8203;FloezeTv](https://redirect.github.com/FloezeTv) made their
first contribution in
[https://github.com/PyO3/maturin-action/pull/359](https://redirect.github.com/PyO3/maturin-action/pull/359)

**Full Changelog**:
https://github.com/PyO3/maturin-action/compare/v1.49.2...v1.49.3

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 13:08:04 +05:30
renovate[bot]
053739f698 Update astral-sh/setup-uv action to v6.3.1 (#19031)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [astral-sh/setup-uv](https://redirect.github.com/astral-sh/setup-uv) |
action | patch | `v6.3.0` -> `v6.3.1` |

---

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

---

### Release Notes

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

###
[`v6.3.1`](https://redirect.github.com/astral-sh/setup-uv/releases/tag/v6.3.1):
🌈 Do not warn when version not in manifest-file

[Compare
Source](https://redirect.github.com/astral-sh/setup-uv/compare/v6.3.0...v6.3.1)

##### Changes

This is a hotfix to change the warning messages that a version could not
be found in the local manifest-file to info level.

A `setup-uv` release contains a version-manifest.json file with infos in
all available `uv` releases. When a new `uv` version is released this is
not contained in this file until the file gets updated and a new
`setup-uv` release is made.
We will overhaul this process in the future but for now the spamming of
warnings is removed.

##### 🐛 Bug fixes

- Do not warn when version not in manifest-file
[@&#8203;eifinger](https://redirect.github.com/eifinger)
([#&#8203;462](https://redirect.github.com/astral-sh/setup-uv/issues/462))

##### 🧰 Maintenance

- chore: update known versions for 0.7.14
@&#8203;[github-actions\[bot\]](https://redirect.github.com/apps/github-actions)
([#&#8203;459](https://redirect.github.com/astral-sh/setup-uv/issues/459))
- Revert "Set expected cache dir drive to C: on windows
([#&#8203;451](https://redirect.github.com/astral-sh/setup-uv/issues/451))"
[@&#8203;eifinger](https://redirect.github.com/eifinger)
([#&#8203;460](https://redirect.github.com/astral-sh/setup-uv/issues/460))

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 13:06:39 +05:30
renovate[bot]
192569c848 Update Rust crate clearscreen to v4.0.2 (#19034)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [clearscreen](https://redirect.github.com/watchexec/clearscreen) |
workspace.dependencies | patch | `4.0.1` -> `4.0.2` |

---

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

---

### Release Notes

<details>
<summary>watchexec/clearscreen (clearscreen)</summary>

###
[`v4.0.2`](https://redirect.github.com/watchexec/clearscreen/blob/HEAD/CHANGELOG.md#v402-2025-06-25)

[Compare
Source](https://redirect.github.com/watchexec/clearscreen/compare/v4.0.1...v4.0.2)

- **Deps:** Upgrade which from 7.0.2 to 8.0.0
([#&#8203;34](https://redirect.github.com/watchexec/clearscreen/issues/34))
-
([09bc029](09bc0299f4))

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 13:06:10 +05:30
renovate[bot]
ae7eaa9913 Update Rust crate ordermap to v0.5.8 (#19036)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [ordermap](https://redirect.github.com/indexmap-rs/ordermap) |
workspace.dependencies | patch | `0.5.7` -> `0.5.8` |

---

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

---

### Release Notes

<details>
<summary>indexmap-rs/ordermap (ordermap)</summary>

###
[`v0.5.8`](https://redirect.github.com/indexmap-rs/ordermap/blob/HEAD/RELEASES.md#058-2025-06-26)

[Compare
Source](https://redirect.github.com/indexmap-rs/ordermap/compare/0.5.7...0.5.8)

- Added `extract_if` methods to `OrderMap` and `OrderSet`, similar to
the
methods for `HashMap` and `HashSet` with ranges like `Vec::extract_if`.
- Added more `#[track_caller]` annotations to functions that may panic.

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 13:05:52 +05:30
renovate[bot]
d88bebca32 Update Rust crate indexmap to v2.10.0 (#19040)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [indexmap](https://redirect.github.com/indexmap-rs/indexmap) |
workspace.dependencies | minor | `2.9.0` -> `2.10.0` |

---

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

---

### Release Notes

<details>
<summary>indexmap-rs/indexmap (indexmap)</summary>

###
[`v2.10.0`](https://redirect.github.com/indexmap-rs/indexmap/blob/HEAD/RELEASES.md#2100-2025-06-26)

[Compare
Source](https://redirect.github.com/indexmap-rs/indexmap/compare/2.9.0...2.10.0)

- Added `extract_if` methods to `IndexMap` and `IndexSet`, similar to
the
methods for `HashMap` and `HashSet` with ranges like `Vec::extract_if`.
- Added more `#[track_caller]` annotations to functions that may panic.

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

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 13:04:43 +05:30
InSync
e7aadfc28b [ty] Add special-cased inference for __import__(name) and importlib.import_module(name) (#19008) 2025-06-29 11:49:23 +01:00
Shunsuke Shibayama
de1f8177be [ty] Improve protocol member type checking and relation handling (#18847)
Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
2025-06-29 10:46:33 +00:00
Ibraheem Ahmed
9218bf72ad [ty] Print salsa memory usage totals in mypy primer CI runs (#18973)
## Summary

Print the [new salsa memory usage
dumps](https://github.com/astral-sh/ruff/pull/18928) in mypy primer CI
runs to help us catch memory regressions. The numbers are rounded to the
nearest power of 1.1 (about a 5% threshold between buckets) to avoid overly sensitive diffs.
2025-06-28 15:09:50 -04:00
Micha Reiser
29927f2b59 Update Rust toolchain to 1.88 and MSRV to 1.86 (#19011) 2025-06-28 20:24:00 +02:00
GiGaGon
c5995c40d3 [flake8-async] Make ASYNC105 example error out-of-the-box (#19002)
## Summary

Part of #18972

This PR makes [trio-sync-call
(ASYNC105)](https://docs.astral.sh/ruff/rules/trio-sync-call/#trio-sync-call-async105)'s
example error out-of-the-box

[Old example](https://play.ruff.rs/5b267e01-1c0a-4902-949e-45fc46f8b0d0)
```py
async def double_sleep(x):
    trio.sleep(2 * x)
```

[New example](https://play.ruff.rs/eba6ea40-ff88-4ea8-8cb4-cea472c15c53)
```py
import trio


async def double_sleep(x):
    trio.sleep(2 * x)
```

## Test Plan

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

N/A, no functionality/tests affected
2025-06-28 10:18:06 -05:00
GiGaGon
68f98cfcd8 [Airflow] Make AIR312 example error out-of-the-box (#18989)
## Summary

Part of #18972

This PR makes [airflow3-suggested-to-move-to-provider
(AIR312)](https://docs.astral.sh/ruff/rules/airflow3-suggested-to-move-to-provider/#airflow3-suggested-to-move-to-provider-air312)'s
example error out-of-the-box

[Old example](https://play.ruff.rs/1be0d654-1ed5-4a0b-8791-cc5db73333d5)
```py
from airflow.operators.python import PythonOperator
```

[New example](https://play.ruff.rs/b6260206-fa19-4ab2-8d45-ddd43c46a759)
```py
from airflow.operators.python import PythonOperator


def print_context(ds=None, **kwargs):
    print(kwargs)
    print(ds)


print_the_context = PythonOperator(
    task_id="print_the_context", python_callable=print_context
)
```

## Test Plan

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

N/A, no functionality/tests affected
2025-06-28 10:17:11 -05:00
GiGaGon
315adba906 [flake8-async] Make ASYNC251 example error out-of-the-box (#18990)
## Summary

Part of #18972

This PR makes [blocking-sleep-in-async-function
(ASYNC251)](https://docs.astral.sh/ruff/rules/blocking-sleep-in-async-function/#blocking-sleep-in-async-function-async251)'s
example error out-of-the-box

[Old example](https://play.ruff.rs/796684a2-c437-4390-b754-491e576ffe5e)
```py
async def fetch():
    time.sleep(1)
```

[New example](https://play.ruff.rs/90741192-fd0d-49fb-a04e-3127312da659)
```py
import time


async def fetch():
    time.sleep(1)
```

Imports were also added to the `Use instead:` section to make it valid
code out-of-the-box.

## Test Plan

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

N/A, no functionality/tests affected
2025-06-28 10:15:34 -05:00
GiGaGon
523174e8be [flake8-async] Make ASYNC100 example error out-of-the-box (#18993)
## Summary

Part of #18972

This PR makes [cancel-scope-no-checkpoint
(ASYNC100)](https://docs.astral.sh/ruff/rules/cancel-scope-no-checkpoint/#cancel-scope-no-checkpoint-async100)'s
example error out-of-the-box

[Old example](https://play.ruff.rs/6a399ae5-9b89-4438-b808-6604f1e40a70)
```py
async def func():
    async with asyncio.timeout(2):
        do_something()
```

[New example](https://play.ruff.rs/c44db531-d2f8-4a61-9e04-e5fc0ea989e3)
```py
import asyncio


async def func():
    async with asyncio.timeout(2):
        do_something()
```

## Test Plan

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

N/A, no functionality/tests affected
2025-06-28 10:13:54 -05:00
GiGaGon
ed2e90371b [flake8-async] Make ASYNC210 example error out-of-the-box (#18977)
## Summary

Part of #18972

This PR makes [blocking-http-call-in-async-function
(ASYNC210)](https://docs.astral.sh/ruff/rules/blocking-http-call-in-async-function/#blocking-http-call-in-async-function-async210)'s
example error out-of-the-box

[Old example](https://play.ruff.rs/20cba4f4-fe2f-428a-a721-311d1a081e64)
```py
async def fetch():
    urllib.request.urlopen("https://example.com/foo/bar").read()
```

[New example](https://play.ruff.rs/5ca2a10d-5294-49ee-baee-0447f7188d9b)
```py
import urllib


async def fetch():
    urllib.request.urlopen("https://example.com/foo/bar").read()
```

## Test Plan

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

N/A, no functionality/tests affected
2025-06-28 10:11:38 -05:00
David Peter
90cb0d3a7b [ty] Reduce 'complex_constrained_attributes_2' runtime (#19001)
Re: https://github.com/astral-sh/ruff/pull/18979#issuecomment-3012541095

Each check increases the runtime by a factor of 3, so this should be an
order of magnitude faster.
2025-06-27 23:15:45 +02:00
Alex Waygood
1297d6a9eb [ty] Followups to tuple constructor improvements in #18987 (#19000) 2025-06-27 22:09:14 +01:00
Douglas Creager
caf3c916e8 [ty] Refactor argument matching / type checking in call binding (#18997)
This PR extracts a lot of the complex logic in the `match_parameters`
and `check_types` methods of our call binding machinery into separate
helper types. This is setup for #18996, which will update this logic to
handle variadic arguments. To do so, it is helpful to have the
per-argument logic extracted into a method that we can call repeatedly
for each _element_ of a variadic argument.

This should be a pure refactoring, with no behavioral changes.
2025-06-27 17:01:52 -04:00
Douglas Creager
c60e590b4c [ty] Support variable-length tuples in unpacking assignments (#18948)
This PR updates our unpacking assignment logic to use the new tuple
machinery. As a result, we can now unpack variable-length tuples
correctly.

As part of this, the `TupleSpec` classes have been renamed to `Tuple`,
and can now contain any element (Rust) type, not just `Type<'db>`. The
unpacker uses a tuple of `UnionBuilder`s to maintain the types that will
be assigned to each target, as we iterate through potentially many union
elements on the rhs. We also add a new consuming iterator for tuples,
and update the `all_elements` methods to wrap the result in an enum
(similar to `itertools::Position`) letting you know which part of the
tuple each element appears in. I also added a new
`UnionBuilder::try_build`, which lets you specify a different fallback
type if the union contains no elements.
2025-06-27 15:29:04 -04:00
Alex Waygood
a50a993b9c [ty] Make tuple instantiations sound (#18987)
## Summary

Ensure that we correctly infer calls such as `tuple((1, 2))`,
`tuple(range(42))`, etc. Ensure that we emit errors on invalid calls
such as `tuple[int, str]()`.

## Test Plan

Mdtests
2025-06-27 19:37:16 +01:00
Robsdedude
6802c4702f [flake8-pyi] Expand Optional[A] to A | None (PYI016) (#18572)
## Summary
Under preview 🧪 I've expanded rule `PYI016` to also flag type
union duplicates containing `None` and `Optional`.

## Test Plan
Examples/tests have been added. I've made sure that the existing
examples did not change unless preview is enabled.

## Relevant Issues
* https://github.com/astral-sh/ruff/issues/18508 (discussing
introducing/extending a rule to flag `Optional[None]`)
* https://github.com/astral-sh/ruff/issues/18546 (where I discussed this
addition with @AlexWaygood)

---------

Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2025-06-27 15:43:11 +00:00
Brent Westbrook
96f3c8d1ab Convert OldDiagnostic::noqa_code to an Option<String> (#18946)
## Summary

I think this should be the last step before combining `OldDiagnostic`
and `ruff_db::Diagnostic`. We can't store a `NoqaCode` on
`ruff_db::Diagnostic`, so I converted the `noqa_code` field to an
`Option<String>` and then propagated this change to all of the callers.

I tried to use `&str` everywhere it was possible, so I think the
remaining `to_string` calls are necessary. I spent some time trying to
convert _everything_ to `&str` but ran into lifetime issues, especially
in the `FixTable`. Maybe we can take another look at that if it causes a
performance regression, but hopefully these paths aren't too hot. We
also avoid some `to_string` calls, so it might even out a bit too.

## Test Plan

Existing tests

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
2025-06-27 11:36:55 -04:00
Andrew Gallant
efcb63fe3a [ty] Fix playground (#18986)
I renamed a field on a `Completion` struct in #18982, and it looks like
this caused the playground to fail to build:

https://github.com/astral-sh/ruff/actions/runs/15928550050/job/44931734349

Maybe building that playground can be added to CI for pull requests?
2025-06-27 10:43:36 -04:00
Andrew Gallant
5f6b0ded21 [ty] Add builtins to completions derived from scope (#18982)
Most of the work here was doing some light refactoring to facilitate
sensible testing. That is, we don't want to list every builtin included
in most tests, so we add some structure to the completion type returned.
Tests can now filter based on whether a completion is a builtin or not.

Otherwise, builtins are found using the existing infrastructure for
`object.attr` completions (where we hard-code the module name
`builtins`).

I did consider changing the sort order based on whether a completion
suggestion was a builtin or not. In particular, it seemed like it might
be a good idea to sort builtins after other scope based completions,
but before the dunder and sunder attributes. Namely, it seems likely
that there is an inverse correlation between the size of a scope and
the likelihood of an item in that scope being used at any given point.
So it *might* be a good idea to prioritize the likelier candidates in
the completions returned.

Additionally, the number of items introduced by adding builtins is quite
large. So I wondered whether mixing them in with everything else would
become too noisy.

However, it's not totally clear to me that this is the right thing to
do. Right now, I feel like there is a very obvious lexicographic
ordering that makes "finding" the right suggestion to activate
potentially easier than if the ranking mechanism is less clear.
(Technically, the dunder and sunder attributes are not sorted
lexicographically, but I'd put forward that most folks don't have an
intuitive understanding of where `_` ranks lexicographically with
respect to "regular" letters. Moreover, since dunder and sunder
attributes are all grouped together, I think the ordering here ends up
being very obvious after even a quick glance.)
2025-06-27 10:20:01 -04:00
Matthew Mckee
a3c79d8170 [ty] Don't add incorrect subdiagnostic for unresolved reference (#18487)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Co-authored-by: Carl Meyer <carl@astral.sh>
2025-06-27 12:40:33 +00:00
Alex Waygood
57bd7d055d [ty] Simplify KnownClass::check_call() and KnownFunction::check_call() (#18981) 2025-06-27 12:23:29 +01:00
David Peter
3c18d85c7d [ty] Add micro-benchmark for #711 (#18979)
## Summary

Add a benchmark for the problematic case in
https://github.com/astral-sh/ty/issues/711, which will potentially be
solved in https://github.com/astral-sh/ruff/pull/18955
2025-06-27 11:34:51 +02:00
GiGaGon
e5e3d998c5 [flake8-annotations] Make ANN401 example error out-of-the-box (#18974) 2025-06-27 07:06:11 +00:00
GiGaGon
85b2a08b5c [flake8-async] Make ASYNC110 example error out-of-the-box (#18975) 2025-06-27 09:01:02 +02:00
Jordy Williams
1874d52eda [pandas]: Fix issue on non pandas dataframe in-place usage (PD002) (#18963) 2025-06-27 06:56:13 +00:00
Yair Peretz
18efe2ab46 [pylint] Fix PLC0415 example (#18970)
Fixed documentation error
2025-06-26 18:33:33 -04:00
Ibraheem Ahmed
6f7b1c9bb3 [ty] Add environment variable to dump Salsa memory usage stats (#18928)
## Summary

Setting `TY_MEMORY_REPORT=full` will generate and print a memory usage
report to the CLI after a `ty check` run:

```
=======SALSA STRUCTS=======
`Definition`                                       metadata=7.24MB   fields=17.38MB  count=181062
`Expression`                                       metadata=4.45MB   fields=5.94MB   count=92804
`member_lookup_with_policy_::interned_arguments`   metadata=1.97MB   fields=2.25MB   count=35176
...
=======SALSA QUERIES=======
`File -> ty_python_semantic::semantic_index::SemanticIndex`
    metadata=11.46MB  fields=88.86MB  count=1638
`Definition -> ty_python_semantic::types::infer::TypeInference`
    metadata=24.52MB  fields=86.68MB  count=146018
`File -> ruff_db::parsed::ParsedModule`
    metadata=0.12MB   fields=69.06MB  count=1642
...
=======SALSA SUMMARY=======
TOTAL MEMORY USAGE: 577.61MB
    struct metadata = 29.00MB
    struct fields = 35.68MB
    memo metadata = 103.87MB
    memo fields = 409.06MB
```

Eventually, we should integrate these numbers into CI in some form. The
one limitation currently is that heap allocations in salsa structs (e.g.
interned values) are not tracked, but memoized values should have full
coverage. We may also want a peak memory usage counter (that accounts
for non-salsa memory), but that is relatively simple to profile manually
(e.g. `time -v ty check`) and would require a compile-time option to
avoid runtime overhead.
2025-06-26 21:27:51 +00:00
Victor Hugo Gomes
a1579d82d0 [pylint] Fix PLW0108 autofix introducing a syntax error when the lambda's body contains an assignment expression (#18678)
<!--
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

This PR also supresses the fix if the assignment expression target
shadows one of the lambda's parameters.

Fixes #18675

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

## Test Plan

Add regression tests.
<!-- How was it tested? -->
2025-06-26 16:56:17 -04:00
Dylan
32c54189cb Bump 0.12.1 (#18969) 2025-06-26 15:20:31 -05:00
GiGaGon
b85c219283 [FastAPI] Add fix safety section to FAST002 (#18940)
## Summary

Part of #15584

This PR adds a fix safety section to [fast-api-non-annotated-dependency
(FAST002)](https://docs.astral.sh/ruff/rules/fast-api-non-annotated-dependency/#fast-api-non-annotated-dependency-fast002).
It also re-words the availability section since I found it confusing.

The lint/fix was added in #11579 as always unsafe.
No reasoning is given in the original PR/code as to why this was chosen.
Example of why the fix is unsafe:
https://play.ruff.rs/3bd0566e-1ef6-4cec-ae34-3b07cd308155
```py
from fastapi import Depends, FastAPI, Query

app = FastAPI()

# Fix will remove the parameter default value
@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
    return commons

# Fix will delete comment and change default parameter value
@app.get("/items/")
async def read_items_1(q: str = Query(  # This comment will be deleted
    default="rick")):
    return q
```
After fixing both instances of `FAST002`:
```py
from fastapi import Depends, FastAPI, Query
from typing import Annotated

app = FastAPI()

# Fix will remove the parameter default value
@app.get("/items/")
async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
    return commons

# Fix will delete comment and change default parameter value
@app.get("/items/")
async def read_items_1(q: Annotated[str, Query()] = "rick"):
    return q
```
2025-06-26 12:38:02 -05:00
Andrew Gallant
b1d1cf1d38 [ty] Add regression test for leading tab mis-alignment in diagnostic rendering (#18965)
It turns out that astral-sh/ty#18692 also fixed astral-sh/ty#203. This
PR adds a regression test for it. (Locally, I "unfixed" the bug and
confirmed that this is actually a regression test.)

Fixes astral-sh/ty#203
2025-06-26 16:27:26 +00:00
Micha Reiser
1dcdf7f41d [ty] Resolve python environment in Options::to_program_settings (#18960)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-06-26 17:57:16 +02:00
Victor Hugo Gomes
d00697621e [ruff] Fix false positives and negatives in RUF010 (#18690) 2025-06-26 17:53:52 +02:00
Andrew Gallant
76619b96e5 [ty] Fix rendering of long lines that are indented with tabs
It turns out that `annotate-snippets` doesn't do a great job of
consistently handling tabs. The intent of the implementation is clearly
to expand tabs into 4 ASCII whitespace characters. But there are a few
places where the column computation wasn't taking this expansion into
account. In particular, the `unicode-width` crate returns `None` for a
`\t` input, and `annotate-snippets` would in turn treat this as either
zero columns or one column. Both are wrong.

In patching this, it caused one of the existing `annotate-snippets`
tests to fail. I spent a fair bit of time on it trying to fix it before
coming to the conclusion that the test itself was wrong. In particular,
the annotation ranges are 4 bytes off. However, when the range was
wrong, the buggy code was rendering the example as intended since `\t`
characters were treated as taking up zero columns of space. Now that
they are correctly computed as taking up 4 columns of space, the offsets
of the test needed to be adjusted.

Fixes #670
2025-06-26 11:12:16 -04:00
Andrew Gallant
6e25cfba2b [ty] Add regression test for diagnostic rendering panic
This converts the MRE in #670 into a fixture test for
`annotate-snippets`.
2025-06-26 11:12:16 -04:00
Micha Reiser
76387295a5 [ty] Move venv and conda env discovery to SearchPath::from_settings (#18938)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-06-26 16:39:27 +02:00
David Peter
d04e63a6d9 [ty] Add regression-benchmark for attribute-assignment hang (#18957)
## Summary

Adds a new micro-benchmark as a regression test for
https://github.com/astral-sh/ty/issues/627.

## Test Plan

Ran the benchmark on the parent commit of
89d915a1e3,
and verified that it took > 1s, while it takes ~10 ms after the fix.
2025-06-26 15:21:08 +02:00
David Peter
86fd9b634e [ty] Format conflicting types as an enumeration (#18956)
## Summary

Format conflicting declared types as
```
`str`, `int` and `bytes`
```

Thanks to @AlexWaygood for the initial draft.

@dcreager, looking forward to your one-character follow-up PR.
2025-06-26 14:29:33 +02:00
David Peter
c0beb3412f [ty] Prevent union builder construction for just one declaration (#18954)
## Summary

Avoid the construction of the `DeclaredTypeBuilder` if there is just one
declared type.
2025-06-26 13:00:09 +02:00
David Peter
b01003f81d [ty] Infer nonlocal types as unions of all reachable bindings (#18750)
## Summary

This PR includes a behavioral change to how we infer types for public
uses of symbols within a module. Where we would previously use the type
that a use at the end of the scope would see, we now consider all
reachable bindings and union the results:

```py
x = None

def f():
    reveal_type(x)  # previously `Unknown | Literal[1]`, now `Unknown | None | Literal[1]`

f()

x = 1

f()
```

This helps especially in cases where the the end of the scope is not
reachable:

```py
def outer(x: int):
    def inner():
        reveal_type(x)  # previously `Unknown`, now `int`

    raise ValueError
```

This PR also proposes to skip the boundness analysis of public uses.
This is consistent with the "all reachable bindings" strategy, because
the implicit `x = <unbound>` binding is also always reachable, and we
would have to emit "possibly-unresolved" diagnostics for every public
use otherwise. Changing this behavior allows common use-cases like the
following to type check without any errors:

```py
def outer(flag: bool):
    if flag:
        x = 1

        def inner():
            print(x)  # previously: possibly-unresolved-reference, now: no error
```

closes https://github.com/astral-sh/ty/issues/210
closes https://github.com/astral-sh/ty/issues/607
closes https://github.com/astral-sh/ty/issues/699

## Follow up

It is now possible to resolve the following TODO, but I would like to do
that as a follow-up, because it requires some changes to how we treat
implicit attribute assignments, which could result in ecosystem changes
that I'd like to see separately.


315fb0f3da/crates/ty_python_semantic/src/semantic_index/builder.rs (L1095-L1117)

## Ecosystem analysis

[**Full report**](https://shark.fish/diff-public-types.html)

* This change obviously removes a lot of `possibly-unresolved-reference`
diagnostics (7818) because we do not analyze boundness for public uses
of symbols inside modules anymore.
* As the primary goal here, this change also removes a lot of
false-positive `unresolved-reference` diagnostics (231) in scenarios
like this:
    ```py
    def _(flag: bool):
        if flag:
            x = 1
    
            def inner():
                x
    
            raise
    ```
* This change also introduces some new false positives for cases like:
    ```py
    def _():
        x = None
    
        x = "test"
    
        def inner():
x.upper() # Attribute `upper` on type `Unknown | None | Literal["test"]`
is possibly unbound
    ```
We have test cases for these situations and it's plausible that we can
improve this in a follow-up.


## Test Plan

New Markdown tests
2025-06-26 12:24:40 +02:00
Victor Hugo Gomes
2362263d5e [pyflakes] Mark F504/F522/F523 autofix as unsafe if there's a call with side effect (#18839)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-06-26 08:48:29 +00:00
GiGaGon
170ccd80b4 [playground] Add ruff logo docs link to Header.tsx (#18947) 2025-06-26 08:54:45 +02:00
Alex Waygood
4a5715b97a [ty] Reduce the overwhelming complexity of TypeInferenceBuilder::infer_call_expression (#18943)
## Summary

This function is huge, and hugely indented. This PR breaks most of it
out into two helper functions: `KnownFunction::check_call()` and
`KnownClass::check_call`.

My immediate motivation is that we need to add yet more special cases to
this function in order to properly handle `tuple` instantiations and
instantiations of tuple subclasses. But I really don't relish the
thought of doing that with the function's current structure 😆

## Test Plan

Existing tests all pass. No new ones are added; this is a pure refactor
that should have no functional change.
2025-06-25 21:10:55 +01:00
Alex Waygood
c77e72ea1a [ty] Add subdiagnostic about empty bodies in more cases (#18942) 2025-06-25 20:25:00 +01:00
Micha Reiser
5d546c600a [ty] Move search path resolution to Options::to_program_settings (#18937) 2025-06-25 18:00:38 +02:00
Nikolas Hearp
8b22992988 [flake8-errmsg] Extend EM101 to support byte strings (#18867)
## Summary

Fixes #18765

## Test Plan

Added test
2025-06-25 10:53:56 -04:00
GiGaGon
f6def1c86d Move big rule implementations (#18931)
<!--
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? -->

Here's the part that was split out of #18906. I wanted to move these
into the rule files since the rest of the rules in
`deferred_scope`/`statement` have that same structure of implementations
being in the rule definition file. It also resolves the dilemma of where
to put the comment, at least for these rules.

## Test Plan

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

N/A, no test/functionality affected
2025-06-25 10:46:25 -04:00
Brent Westbrook
5aab49880a [pylint] Allow fix with comments and document performance implications (PLW3301) (#18936)
Summary
--

Closes #18849 by adding a `## Known issues` section describing the
potential performance issues when fixing nested iterables. I also
deleted the comment check since the fix is already unsafe and added a
note to the `## Fix safety` docs.

Test Plan
--

Existing tests, updated to allow a fix when comments are present since
the fix is already unsafe.
2025-06-25 09:29:23 -04:00
Brent Westbrook
7783cea14f [flake8-future-annotations] Add autofix (FA100) (#18903)
Summary
--

This PR resolves the easiest part of
https://github.com/astral-sh/ruff/issues/18502 by adding an autofix that
just adds
`from __future__ import annotations` at the top of the file, in the same
way
as FA102, which already has an identical unsafe fix.

Test Plan
--

Existing snapshots, updated to add the fixes.
2025-06-25 08:37:18 -04:00
Micha Reiser
c1fed55d51 Delete the ruff_python_resolver crate (#18933) 2025-06-25 12:53:13 +02:00
David Peter
689797a984 [ty] Type narrowing in comprehensions (#18934)
## Summary

Add type narrowing inside comprehensions:

```py
def _(xs: list[int | None]):
    [reveal_type(x) for x in xs if x is not None]  # revealed: int
```

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

## Test Plan

* New Markdown tests
* Made sure the example from https://github.com/astral-sh/ty/issues/680
now checks without errors
* Made sure that all removed ecosystem diagnostics were actually false
positives
2025-06-25 11:30:28 +02:00
Victor Hugo Gomes
66dbea90f1 [perflint] Fix false negative in PERF401 (#18866) 2025-06-25 10:44:32 +02:00
GiGaGon
d2684a00c6 Fix f-string interpolation escaping (#18882) 2025-06-25 10:04:15 +02:00
Robsdedude
2a0c5669f2 [refurb] Detect more exotic float literals in FURB164 (#18925) 2025-06-25 09:08:25 +02:00
GiGaGon
cb152b4725 [Internal] Use more report_diagnostic_if_enabled (#18924)
<!--
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? -->

From @ntBre
https://github.com/astral-sh/ruff/pull/18906#discussion_r2162843366 :
> This could be a good target for a follow-up PR, but we could fold
these `if checker.is_rule_enabled { checker.report_diagnostic` checks
into calls to `checker.report_diagnostic_if_enabled`. I didn't notice
these when adding that method.
> 
> Also, the docs on `Checker::report_diagnostic_if_enabled` and
`LintContext::report_diagnostic_if_enabled` are outdated now that the
`Rule` conversion is basically free 😅
> 
> No pressure to take on this refactor, just an idea if you're
interested!

This PR folds those calls. I also updated the doc comments by copying
from `report_diagnostic`.

Note: It seems odd to me that the doc comment for `Checker` says
`Diagnostic` while `LintContext` says `OldDiagnostic`, not sure if that
needs a bigger docs change to fix the inconsistency.

<details>
<summary>Python script to do the changes</summary>

This script assumes it is placed in the top level `ruff` directory (ie
next to `.git`/`crates`/`README.md`)

```py
import re
from copy import copy
from pathlib import Path

ruff_crates = Path(__file__).parent / "crates"

for path in ruff_crates.rglob("**/*.rs"):
    with path.open(encoding="utf-8", newline="") as f:
        original_content = f.read()
    if "is_rule_enabled" not in original_content or "report_diagnostic" not in original_content:
        continue
    original_content_position = 0
    changed_content = ""
    for match in re.finditer(r"(?m)(?:^[ \n]*|(?<=(?P<else>else )))if[ \n]+checker[ \n]*\.is_rule_enabled\([ \n]*Rule::\w+[ \n]*\)[ \n]*{[ \n]*checker\.report_diagnostic\(", original_content):
        # Content between last match and start of this one is unchanged
        changed_content += original_content[original_content_position:match.start()]
        # If this was an else if, a { needs to be added at the start
        if match.group("else"):
            changed_content += "{"
        # This will result in bad formatting, but the precommit cargo format will handle it
        changed_content += "checker.report_diagnostic_if_enabled("
        # Depth tracking would fail if a string/comment included a { or }, but unlikely given the context
        depth = 1
        position = match.end()
        while depth > 0:
            if original_content[position] == "{":
                depth += 1
            if original_content[position] == "}":
                depth -= 1
            position += 1
        # pos - 1 is the closing }
        changed_content += original_content[match.end():position - 1]
        # If this was an else if, a } needs to be added at the end
        if match.group("else"):
            changed_content += "}"
        # Skip the closing }
        original_content_position = position
        if original_content[original_content_position] == "\n":
            # If the } is followed by a \n, also skip it for better formatting
            original_content_position += 1
    # Add remaining content between last match and file end
    changed_content += original_content[original_content_position:]
    with path.open("w", encoding="utf-8", newline="") as f:
        f.write(changed_content)
```

</details>

## Test Plan

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

N/A, no tests/functionality affected.
2025-06-24 21:43:22 -04:00
GiGaGon
90f47e9b7b Add missing rule code comments (#18906)
<!--
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? -->

While making some of my other changes, I noticed some of the lints were
missing comments with their lint code/had the wrong numbered lint code.
These comments are super useful since they allow for very easily and
quickly finding the source code of a lint, so I decided to try and
normalize them.

Most of them were fairly straightforward, just adding a doc
comment/comment in the appropriate place.

I decided to make all of the `Pylint` rules have the `PL` prefix.
Previously it was split between no prefix and having prefix, but I
decided to normalize to with prefix since that's what's in the docs, and
the with prefix will show up on no prefix searches, while the reverse is
not true.

I also ran into a lot of rules with implementations in "non-standard"
places (where "standard" means inside a file matching the glob
`crates/ruff_linter/rules/*/rules/**/*.rs` and/or the same rule file
where the rule `struct`/`ViolationMetadata` is defined).

I decided to move all the implementations out of
`crates/ruff_linter/src/checkers/ast/analyze/deferred_scopes.rs` and
into their own files, since that is what the rest of the rules in
`deferred_scopes.rs` did, and those were just the outliers.

There were several rules which I did not end up moving, which you can
see as the extra paths I had to add to my python code besides the
"standard" glob. These rules are generally the error-type rules that
just wrap an error from the parser, and have very small
implementations/are very tightly linked to the module they are in, and
generally every rule of that type was implemented in module instead of
in the "standard" place.

Resolving that requires answering a question I don't think I'm equipped
to handle: Is the point of these comments to give quick access to the
rule definition/docs, or the rule implementation? For all the rules with
implementations in the "standard" location this isn't a problem, as they
are the same, but it is an issue for all of these error type rules. In
the end I chose to leave the implementations where they were, but I'm
not sure if that was the right choice.

<details>
<summary>Python script I wrote to find missing comments</summary>

This script assumes it is placed in the top level `ruff` directory (ie
next to `.git`/`crates`/`README.md`)

```py
import re
from copy import copy
from pathlib import Path

linter_to_code_prefix = {
    "Airflow": "AIR",
    "Eradicate": "ERA",
    "FastApi": "FAST",
    "Flake82020": "YTT",
    "Flake8Annotations": "ANN",
    "Flake8Async": "ASYNC",
    "Flake8Bandit": "S",
    "Flake8BlindExcept": "BLE",
    "Flake8BooleanTrap": "FBT",
    "Flake8Bugbear": "B",
    "Flake8Builtins": "A",
    "Flake8Commas": "COM",
    "Flake8Comprehensions": "C4",
    "Flake8Copyright": "CPY",
    "Flake8Datetimez": "DTZ",
    "Flake8Debugger": "T10",
    "Flake8Django": "DJ",
    "Flake8ErrMsg": "EM",
    "Flake8Executable": "EXE",
    "Flake8Fixme": "FIX",
    "Flake8FutureAnnotations": "FA",
    "Flake8GetText": "INT",
    "Flake8ImplicitStrConcat": "ISC",
    "Flake8ImportConventions": "ICN",
    "Flake8Logging": "LOG",
    "Flake8LoggingFormat": "G",
    "Flake8NoPep420": "INP",
    "Flake8Pie": "PIE",
    "Flake8Print": "T20",
    "Flake8Pyi": "PYI",
    "Flake8PytestStyle": "PT",
    "Flake8Quotes": "Q",
    "Flake8Raise": "RSE",
    "Flake8Return": "RET",
    "Flake8Self": "SLF",
    "Flake8Simplify": "SIM",
    "Flake8Slots": "SLOT",
    "Flake8TidyImports": "TID",
    "Flake8Todos": "TD",
    "Flake8TypeChecking": "TC",
    "Flake8UnusedArguments": "ARG",
    "Flake8UsePathlib": "PTH",
    "Flynt": "FLY",
    "Isort": "I",
    "McCabe": "C90",
    "Numpy": "NPY",
    "PandasVet": "PD",
    "PEP8Naming": "N",
    "Perflint": "PERF",
    "Pycodestyle": "",
    "Pydoclint": "DOC",
    "Pydocstyle": "D",
    "Pyflakes": "F",
    "PygrepHooks": "PGH",
    "Pylint": "PL",
    "Pyupgrade": "UP",
    "Refurb": "FURB",
    "Ruff": "RUF",
    "Tryceratops": "TRY",
}

ruff = Path(__file__).parent / "crates"

ruff_linter = ruff / "ruff_linter" / "src"

code_to_rule_name = {}

with open(ruff_linter / "codes.rs") as codes_file:
    for linter, code, rule_name in re.findall(
        # The (?<! skips ruff test rules
        # Only Preview|Stable rules are checked
        r"(?<!#\[cfg\(any\(feature = \"test-rules\", test\)\)\]\n)        \((\w+), \"(\w+)\"\) => \(RuleGroup::(?:Preview|Stable), [\w:]+::(\w+)\)",
        codes_file.read(),
    ):
        code_to_rule_name[linter_to_code_prefix[linter] + code] = (rule_name, [])

ruff_linter_rules = ruff_linter / "rules"
for rule_file_path in [
    *ruff_linter_rules.rglob("*/rules/**/*.rs"),
    ruff / "ruff_python_parser" / "src" / "semantic_errors.rs",
    ruff_linter / "pyproject_toml.rs",
    ruff_linter / "checkers" / "noqa.rs",
    ruff_linter / "checkers" / "ast" / "mod.rs",
    ruff_linter / "checkers" / "ast" / "analyze" / "unresolved_references.rs",
    ruff_linter / "checkers" / "ast" / "analyze" / "expression.rs",
    ruff_linter / "checkers" / "ast" / "analyze" / "statement.rs",
]:
    with open(rule_file_path, encoding="utf-8") as f:
        rule_file_content = f.read()
    for code, (rule, _) in copy(code_to_rule_name).items():
        if rule in rule_file_content:
            if f"// {code}" in rule_file_content or f", {code}" in rule_file_content:
                del code_to_rule_name[code]
            else:
                code_to_rule_name[code][1].append(rule_file_path)

for code, rule in code_to_rule_name.items():
    print(code, rule[0])
    for path in rule[1]:
        print(path)
```

</details>

## Test Plan

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

N/A, no tests/functionality affected.
2025-06-24 21:18:57 -04:00
Carl Meyer
62975b3ab2 [ty] eliminate is_fully_static (#18799)
## Summary

Having a recursive type method to check whether a type is fully static
is inefficient, unnecessary, and makes us overly strict about subtyping
relations.

It's inefficient because we end up re-walking the same types many times
to check for fully-static-ness.

It's unnecessary because we can check relations involving the dynamic
type appropriately, depending whether the relation is subtyping or
assignability.

We use the subtyping relation to simplify unions and intersections. We
can usefully consider that `S <: T` for gradual types also, as long as
it remains true that `S | T` is equivalent to `T` and `S & T` is
equivalent to `S`.

One conservative definition (implemented here) that satisfies this
requirement is that we consider `S <: T` if, for every possible pair of
materializations `S'` and `T'`, `S' <: T'`. Or put differently the top
materialization of `S` (`S+` -- the union of all possible
materializations of `S`) is a subtype of the bottom materialization of
`T` (`T-` -- the intersection of all possible materializations of `T`).
In the most basic cases we can usefully say that `Any <: object` and
that `Never <: Any`, and we can handle more complex cases inductively
from there.

This definition of subtyping for gradual subtypes is not reflexive
(`Any` is not a subtype of `Any`).

As a corollary, we also remove `is_gradual_equivalent_to` --
`is_equivalent_to` now has the meaning that `is_gradual_equivalent_to`
used to have. If necessary, we could restore an
`is_fully_static_equivalent_to` or similar (which would not do an
`is_fully_static` pre-check of the types, but would instead pass a
relation-kind enum down through a recursive equivalence check, similar
to `has_relation_to`), but so far this doesn't appear to be necessary.

Credit to @JelleZijlstra for the observation that `is_fully_static` is
unnecessary and overly restrictive on subtyping.

There is another possible definition of gradual subtyping: instead of
requiring that `S+ <: T-`, we could instead require that `S+ <: T+` and
`S- <: T-`. In other words, instead of requiring all materializations of
`S` to be a subtype of every materialization of `T`, we just require
that every materialization of `S` be a subtype of _some_ materialization
of `T`, and that every materialization of `T` be a supertype of some
materialization of `S`. This definition also preserves the core
invariant that `S <: T` implies that `S | T = T` and `S & T = S`, and it
restores reflexivity: under this definition, `Any` is a subtype of
`Any`, and for any equivalent types `S` and `T`, `S <: T` and `T <: S`.
But unfortunately, this definition breaks transitivity of subtyping,
because nominal subclasses in Python use assignability ("consistent
subtyping") to define acceptable overrides. This means that we may have
a class `A` with `def method(self) -> Any` and a subtype `B(A)` with
`def method(self) -> int`, since `int` is assignable to `Any`. This
means that if we have a protocol `P` with `def method(self) -> Any`, we
would have `B <: A` (from nominal subtyping) and `A <: P` (`Any` is a
subtype of `Any`), but not `B <: P` (`int` is not a subtype of `Any`).
Breaking transitivity of subtyping is not tenable, so we don't use this
definition of subtyping.

## Test Plan

Existing tests (modified in some cases to account for updated
semantics.)

Stable property tests pass at a million iterations:
`QUICKCHECK_TESTS=1000000 cargo test -p ty_python_semantic -- --ignored
types::property_tests::stable`

### Changes to property test type generation

Since we no longer have a method of categorizing built types as
fully-static or not-fully-static, I had to add a previously-discussed
feature to the property tests so that some tests can build types that
are known by construction to be fully static, because there are still
properties that only apply to fully-static types (for example,
reflexiveness of subtyping.)

## Changes to handling of `*args, **kwargs` signatures

This PR "discovered" that, once we allow non-fully-static types to
participate in subtyping under the above definitions, `(*args: Any,
**kwargs: Any) -> Any` is now a subtype of `() -> object`. This is true,
if we take a literal interpretation of the former signature: all
materializations of the parameters `*args: Any, **kwargs: Any` can
accept zero arguments, making the former signature a subtype of the
latter. But the spec actually says that `*args: Any, **kwargs: Any`
should be interpreted as equivalent to `...`, and that makes a
difference here: `(...) -> Any` is not a subtype of `() -> object`,
because (unlike a literal reading of `(*args: Any, **kwargs: Any)`),
`...` can materialize to _any_ signature, including a signature with
required positional arguments.

This matters for this PR because it makes the "any two types are both
assignable to their union" property test fail if we don't implement the
equivalence to `...`. Because `FunctionType.__call__` has the signature
`(*args: Any, **kwargs: Any) -> Any`, and if we take that at face value
it's a subtype of `() -> object`, making `FunctionType` a subtype of `()
-> object)` -- but then a function with a required argument is also a
subtype of `FunctionType`, but not a subtype of `() -> object`. So I
went ahead and implemented the equivalence to `...` in this PR.

## Ecosystem analysis

* Most of the ecosystem report are cases of improved union/intersection
simplification. For example, we can now simplify a union like `bool |
(bool & Unknown) | Unknown` to simply `bool | Unknown`, because we can
now observe that every possible materialization of `bool & Unknown` is
still a subtype of `bool` (whereas before we would set aside `bool &
Unknown` as a not-fully-static type.) This is clearly an improvement.
* The `possibly-unresolved-reference` errors in sockeye, pymongo,
ignite, scrapy and others are true positives for conditional imports
that were formerly silenced by bogus conflicting-declarations (which we
currently don't issue a diagnostic for), because we considered two
different declarations of `Unknown` to be conflicting (we used
`is_equivalent_to` not `is_gradual_equivalent_to`). In this PR that
distinction disappears and all equivalence is gradual, so a declaration
of `Unknown` no longer conflicts with a declaration of `Unknown`, which
then results in us surfacing the possibly-unbound error.
* We will now issue "redundant cast" for casting from a typevar with a
gradual bound to the same typevar (the hydra-zen diagnostic). This seems
like an improvement.
* The new diagnostics in bandersnatch are interesting. For some reason
primer in CI seems to be checking bandersnatch on Python 3.10 (not yet
sure why; this doesn't happen when I run it locally). But bandersnatch
uses `enum.StrEnum`, which doesn't exist on 3.10. That makes the `class
SimpleDigest(StrEnum)` a class that inherits from `Unknown` (and
bypasses our current TODO handling for accessing attributes on enum
classes, since we don't recognize it as an enum class at all). This PR
improves our understanding of assignability to classes that inherit from
`Any` / `Unknown`, and we now recognize that a string literal is not
assignable to a class inheriting `Any` or `Unknown`.
2025-06-24 18:02:05 -07:00
Dan Parizher
eee5a5a3d6 [docs] Typo fix for playground (#18929)
<!--
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? -->

I saw the smallest typo while familiarizing myself with the playground,
it bothered me so much I just had to make a PR for it 😂
2025-06-24 21:01:48 -04:00
Douglas Creager
66f50fb04b [ty] Add property test generators for variable-length tuples (#18901)
Add property test generators for the new variable-length tuples. This
covers homogeneous tuples as well.

The property tests did their job! This identified several fixes we
needed to make to various type property methods.

cf https://github.com/astral-sh/ruff/pull/18600#issuecomment-2993764471

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-06-24 18:13:47 -04:00
Robsdedude
919af9628d [pygrep_hooks] Add AsyncMock methods to invalid-mock-access (PGH005) (#18547)
## Summary
This PR expands PGH005 to also check for AsyncMock methods in the same
vein. E.g., currently `assert mock.not_called` is linted. This PR adds
the corresponding async assertions `assert mock.not_awaited()`.

---------

Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
2025-06-24 17:27:21 -04:00
Alex Waygood
9d8cba4e8b [ty] Improve disjointness inference for NominalInstanceTypes and SubclassOfTypes (#18864)
Co-authored-by: Carl Meyer <carl@astral.sh>
2025-06-24 20:27:37 +00:00
Josiah Kane
d89f75f9cc Fix link typo in ty's CONTRIBUTING.md (#18923) 2025-06-24 20:23:31 +00:00
Alex Waygood
e44c489273 [ty] Fix false positives when subscripting an object inferred as having an Intersection type (#18920) 2025-06-24 18:39:02 +00:00
chiri
3220242dec [flake8-use-pathlib] Add autofix for PTH202 (#18763)
<!--
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
/closes #2331
<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan
update snapshots
<!-- How was it tested? -->

---------

Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
2025-06-24 17:58:31 +00:00
Andrew Gallant
6abafcb565 [ty] Add relative import completion tests
This tests things like `from ...foo import <CURSOR>`.

I had previously tested this on an ad hoc basis inside
of my editor, so the token state machine already recognizes
this pattern.

Ref https://github.com/astral-sh/ruff/pull/18830#discussion_r2159670033
2025-06-24 11:41:16 -04:00
Andrew Gallant
cef1a522dc [ty] Clarify what "cursor" means
This commit does a small refactor to combine the file and
cursor offset into a single type. I think this makes it
clearer that even if there are multiple files in the cursor
test, this one in particular corresponds to the file that
contains the `<CURSOR>` marker.
2025-06-24 11:41:16 -04:00
Andrew Gallant
40731f0589 [ty] Add a cursor test builder
This doesn't change any functionality of the cursor tests, but does
re-arrange the code a bit. Firstly, it's now in a builder. And secondly,
there's an API to add multiple files to the test (but exactly one must
have a `<CURSOR>` marker).
2025-06-24 11:41:16 -04:00
Andrew Gallant
1461137407 [ty] Enforce sort order of completions (#18917)
We achieve this by setting the "sort text" field of every completion.
Since we are trying to be smart about the order, we want the client to
respect our order.

Prior to this change, VS Code was re-sorting completions in
lexicographic order. This in turn resulted in dunder attributes
appearing before "normal" attributes.
2025-06-24 11:31:08 -04:00
K
47653ca88a [formatter] Fix missing blank lines before decorated classes in .pyi files (#18888)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-06-24 16:25:44 +02:00
Brent Westbrook
02ae8e1210 Apply fix availability and applicability when adding to DiagnosticGuard and remove NoqaCode::rule (#18834)
## Summary

This PR removes the last two places we were using `NoqaCode::rule` in
`linter.rs` (see
https://github.com/astral-sh/ruff/pull/18391#discussion_r2154637329 and
https://github.com/astral-sh/ruff/pull/18391#discussion_r2154649726) by
checking whether fixes are actually desired before adding them to a
`DiagnosticGuard`. I implemented this by storing a `Violation`'s `Rule`
on the `DiagnosticGuard` so that we could check if it was enabled in the
embedded `LinterSettings` when trying to set a fix.

All of the corresponding `set_fix` methods on `OldDiagnostic` were now
unused (except in tests where I just set `.fix` directly), so I moved
these to the guard instead of keeping both sets.

The very last place where we were using `NoqaCode::rule` was in the
cache. I just reverted this to parsing the `Rule` from the name. I had
forgotten to update the comment there anyway. Hopefully this doesn't
cause too much of a perf hit.

In terms of binary size, we're back down almost to where `main` was two
days ago
(https://github.com/astral-sh/ruff/pull/18391#discussion_r2155034320):

```
41,559,344 bytes for main 2 days ago
41,669,840 bytes for #18391
41,653,760 bytes for main now (after #18391 merged)
41,602,224 bytes for this branch
```

Only 43 kb up, but that shouldn't all be me this time :)

## Test Plan

Existing tests and benchmarks on this PR
2025-06-24 10:08:36 -04:00
David Peter
0edbd6c390 py-fuzzer: allow relative executable paths (#18915)
## Summary

I tried running `py-fuzzer` using executables in the current working
directory, but that failed with:
```
▶ uvx --from ./python/py-fuzzer --reinstall fuzz --test-executable ./ty_feature --bin=ty --baseline-executable ./ty_main --only-new-bugs 0-500
Usage: fuzz [-h] [--only-new-bugs] [--quiet] [--test-executable TEST_EXECUTABLE] [--baseline-executable BASELINE_EXECUTABLE] --bin {ruff,ty} seeds [seeds ...]
fuzz: error: Bad argument passed to `--baseline-executable`: no such file or executable PosixPath('ty_main')
 "Bad argument passed to `--baseline-executable`: no such file or executable PosixPath('ty_main')"
```

Using `.absolute()` on the `Path` fixes this.


## Test Plan

Successful `py-fuzzer` run with the invocation above.
2025-06-24 15:16:21 +02:00
Micha Reiser
833be2e66a [ty] Change environment.root to accept multiple paths (#18913) 2025-06-24 14:52:36 +02:00
Micha Reiser
0194452928 [ty] Rename src.root setting to environment.root (#18760)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-06-24 14:40:44 +02:00
Dhruv Manilawala
2c4c015f74 Use file path for detecting package root (#18914)
Ref: https://github.com/astral-sh/ruff/pull/18910#discussion_r2163847956
2025-06-24 12:32:41 +00:00
Dhruv Manilawala
66fc7c8fc0 Consider virtual path for various server actions (#18910)
## Summary

Ref:
https://github.com/astral-sh/ruff/issues/14820#issuecomment-2996690681

This PR fixes a bug where virtual paths or any paths that doesn't exists
on the file system weren't being considered for checking inclusion /
exclusion. This was because the logic used `file_path` which returns
`None` for those path. This PR fixes that by using the
`virtual_file_path` method that returns a `Path` corresponding to the
actual file on disk or any kind of virtual path.

This should ideally just fix the above linked issue by way of excluding
the documents representing the interactive window because they aren't in
the inclusion set. It failed only on Windows previously because the file
path construction would fail and then Ruff would default to including
all the files.

## Test Plan

On my machine, the `.interactive` paths are always excluded so I'm using
the inclusion set instead:

```json
{
  "ruff.nativeServer": "on",
  "ruff.path": ["/Users/dhruv/work/astral/ruff/target/debug/ruff"],
  "ruff.configuration": {
    "extend-include": ["*.interactive"]
  }
}
```

The diagnostics are shown for both the file paths and the interactive
window:

<img width="1727" alt="Screenshot 2025-06-24 at 14 56 40"
src="https://github.com/user-attachments/assets/d36af96a-777e-4367-8acf-4d9c9014d025"
/>

And, the logs:

```
2025-06-24 14:56:26.478275000 DEBUG notification{method="notebookDocument/didChange"}: Included path via `extend-include`: /Interactive-1.interactive
```

And, when using `ruff.exclude` via:

```json
{
	"ruff.exclude": ["*.interactive"]
}
```

With logs:

```
2025-06-24 14:58:41.117743000 DEBUG notification{method="notebookDocument/didChange"}: Ignored path via `exclude`: /Interactive-1.interactive
```
2025-06-24 12:24:28 +00:00
Alex Waygood
237a5821ba [ty] Introduce UnionType::try_from_elements and UnionType::try_map (#18911) 2025-06-24 12:09:02 +00:00
Alex Waygood
27eee5a1a8 [ty] Support narrowing on isinstance()/issubclass() if the second argument is a dynamic, intersection, union or typevar type (#18900) 2025-06-24 10:55:26 +00:00
med1844
fd2cc37f90 [ty] Add decorator check for implicit attribute assignments (#18587)
## Summary

Previously, the checks for implicit attribute assignments didn't
properly account for method decorators. This PR fixes that by:

- Adding a decorator check in `implicit_instance_attribute`. This allows
it to filter out methods with mismatching decorators when analyzing
attribute assignments.
- Adding attribute search for implicit class attributes: if an attribute
can't be found directly in the class body, the
`ClassLiteral::own_class_member` function will now search in
classmethods.
- Adding `staticmethod`: it has been added into `KnownClass` and
together with the new decorator check, it will no longer expose
attributes when the assignment target name is the same as the first
method name.

If accepted, it should fix https://github.com/astral-sh/ty/issues/205
and https://github.com/astral-sh/ty/issues/207.

## Test Plan

This is tested with existing mdtest suites and is able to get most of
the TODO marks for implicit assignments in classmethods and
staticmethods removed.

However, there's one specific test case I failed to figure out how to
correctly resolve:


b279508bdc/crates/ty_python_semantic/resources/mdtest/attributes.md (L754-L755)

I tried to add `instance_member().is_unbound()` check in this [else
branch](b279508bdc/crates/ty_python_semantic/src/types/infer.rs (L3299-L3301))
but it causes tests with class attributes defined in class body to fail.
While it's possible to implicitly add `ClassVar` to qualifiers to make
this assignment fail and keep everything else passing, it doesn't feel
like the right solution.
2025-06-24 11:42:10 +02:00
Victor Hugo Gomes
ca7933804e [ruff] Trigger RUF037 for empty string and byte strings (#18862) 2025-06-24 08:26:28 +02:00
Dhruv Manilawala
e474f36473 [ty] Avoid duplicate diagnostic in unpacking (#18897)
## Summary

This PR fixes astral-sh/ty#185 by avoiding to infer the value expression
for an unpacking.

This is done simply by only inferring the value expression in a
non-unpacking branch for assignment statement, for statement, with
statement and comprehensions.

This is a simpler alternative to
https://github.com/astral-sh/ruff/pull/18890 which I only realized in
hindsight! Ideally, the solution would to consider the "unpack" as it's
own region and do all of the inference of every expressions involved in
an unpacking inside the unpack query and then merge the results in the
outer query. This would require access to the `Unpack` ingredient which
is stored on the `Definition`. And, this would require create the said
`Definition`s for all attributes and subscript expressions. It does
simplify the target inference logic by streamlining it into a single
`infer_target` method instead of the `infer_target`/`infer_target_impl`
split.

Additionally, #18890 also solves a couple of TODOs around raising errors
around attribute / subscript assignment.

## Test Plan

Update the existing test, go through a couple of ecosystem diagnostic.
2025-06-24 07:49:44 +05:30
Igor Drokin
da16e00751 [pyupgrade] Extend version detection to include sys.version_info.major (UP036) (#18633)
## Summary

Resolves #18165 

Added pattern `["sys", "version_info", "major"]` to the existing matches
for `sys.version_info` to ensure consistent handling of both the base
object and its major version attribute.

## Test Plan
`cargo nextest run` and `cargo insta test`

---------

Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2025-06-23 20:01:55 +00:00
Илья Любавский
885dc9091f [ruff] Frozen Dataclass default should be valid (RUF009) (#18735)
<!--
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
/closes #17424
<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan

<!-- How was it tested? -->
2025-06-23 19:10:12 +00:00
Andrew Gallant
d01e0faee3 [ty] Include imported sub-modules as attributes on modules for completions (#18898)
This also adds a new `ModuleName::relative_to` public API to help with
this.

Kudos to @AlexWaygood for the meat of this patch!

Ref https://github.com/astral-sh/ruff/pull/18830#discussion_r2161770991
2025-06-23 12:48:16 -04:00
Suneet Tipirneni
ef8281b695 [ty] add support for mapped union and intersection subscript loads (#18846)
## Summary

Note this modifies the diagnostics a bit. Previously performing
subscript access on something like `NotSubscriptable1 |
NotSubscriptable2` would report the full type as not being
subscriptable:

```
[non-subscriptable] "Cannot subscript object of type `NotSubscriptable1 | NotSubscriptable2` with no `__getitem__` method"
```

Now each erroneous constituent has a separate error:

```
[non-subscriptable] "Cannot subscript object of type `NotSubscriptable2` with no `__getitem__` method"
[non-subscriptable] "Cannot subscript object of type `NotSubscriptable1` with no `__getitem__` method"
```

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

## Test Plan

 mdtest

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2025-06-23 16:38:01 +00:00
Andrew Gallant
a77db3da3f [ty] Add completions for from module import <CURSOR> (#18830)
There were two main challenges in this PR.

The first was mostly just figuring out how to get the symbols
corresponding to `module`. It turns out that we do this in a couple
of places in ty already, but through different means. In one approach,
we use [`exported_names`]. In another approach, we get a `Type`
corresponding to the module. We take the latter approach here, which is
consistent with how we do completions elsewhere. (I looked into
factoring this logic out into its own function, but it ended up being
pretty constrained. e.g., There's only one other place where we want to
go from `ast::StmtImportFrom` to a module `Type`, and that code also
wants the module name.)

The second challenge was recognizing the `from module import <CURSOR>`
pattern in the code. I initially started with some fixed token patterns
to get a proof of concept working. But I ended up switching to mini
state machine over tokens. I looked at the parser for `StmtImportFrom`
to determine what kinds of tokens we can expect.

[`exported_names`]:
23a3b6ef23/crates/ty_python_semantic/src/semantic_index/re_exports.rs (L47)
2025-06-23 10:43:25 -04:00
Victor Hugo Gomes
9e9c4fe17b [flake8-simplify] Fix SIM911 autofix creating a syntax error (#18793)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary
The fix would create a syntax error if there wasn't a space between the
`in` keyword and the following expression.
For example:
```python
for country, stars in(zip)(flag_stars.keys(), flag_stars.values()):...
```

I also noticed that the tests for `SIM911` were note being run, so I
fixed that.

Fixes #18776

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

## Test Plan

Add regression test
<!-- How was it tested? -->
2025-06-23 16:24:47 +02:00
Victor Hugo Gomes
f4c6ff3f68 [pylint] Fix PLC2801 autofix creating a syntax error (#18857)
<!--
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
This PR fixes `PLC2801` autofix creating a syntax error due to lack of
padding if it is directly after a keyword.

Fixes https://github.com/astral-sh/ruff/issues/18813
<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan
Add regression test
<!-- How was it tested? -->
2025-06-23 10:15:53 -04:00
Vasco Schiavo
ca8ed35275 [flake8-simplify] Preserve original behavior for except () and bare except (SIM105) (#18213)
The PR addresses issue #18209.
2025-06-23 10:01:57 -04:00
GiGaGon
315fb0f3da [flake8-logging] Add fix safety section to LOG002 (#18840)
## Summary

Part of #15584

This adds a `Fix safety` section to [invalid-get-logger-argument
(LOG002)](https://docs.astral.sh/ruff/rules/invalid-get-logger-argument/#invalid-get-logger-argument-log002).

The fix/lint was introduced in #7399
No reasoning is given on the unsafety in the PR/code
Unsafe fix demonstration:
[playground](https://play.ruff.rs/e8008cbf-2ef5-4d38-8255-324f90e624cb)
```py
import logging
logger = logging.getLogger(__file__)
```

---------

Co-authored-by: Dylan <dylwil3@gmail.com>
2025-06-23 13:23:01 +00:00
GiGaGon
bbc26b2f11 [pyupgrade] Add fix safety section to UP004 (#18853)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

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

Part of #15584

This adds a `Fix safety` section to [useless-object-inheritance
(UP004)](https://docs.astral.sh/ruff/rules/useless-object-inheritance/#useless-object-inheritance-up004)

I could not track down the original PR as this rule is so old it has
gone through several large ruff refactors.
No reasoning is given on the unsafety in the PR/code.
The unsafety is determined here:

f24e650dfd/crates/ruff_linter/src/rules/pyupgrade/rules/useless_class_metaclass_type.rs (L76-L80)

Unsafe fix demonstration:
[playground](https://play.ruff.rs/12b24eb4-d7a5-4ae0-93bb-492d64967ae3)
```py
class A(  # will be deleted
    object
):
    ...
```

## Test Plan

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

N/A, no tests/functionality affected
2025-06-23 08:22:36 -05:00
GiGaGon
ec07a0f885 [flake8-use-pathlib] Add fix safety section to PTH201 (#18837)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

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

Part of #15584

This adds a `Fix safety` section to [path-constructor-current-directory
(PTH201)](https://docs.astral.sh/ruff/rules/path-constructor-current-directory/#path-constructor-current-directory-pth201)

I could not track down the original PR as this rule is so old it has
gone through several large ruff refactors.
The unsafety is determined here:

d9266284df/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/path_constructor_current_directory.rs (L55-L59)
Unsafe code example:
[playground](https://play.ruff.rs/76da532a-c7ad-4ef9-bba3-4626296e5317)
```py
from pathlib import Path
Path(#
    "."#
)
```

## Test Plan

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

N/A, no tests/functionality affected
2025-06-23 08:22:00 -05:00
GiGaGon
861dff1dd8 [refurb] Add fix safety section to FURB122 (#18842)
## Summary

Part of #15584

This adds a `Fix safety` section to [for-loop-writes
(FURB122)](https://docs.astral.sh/ruff/rules/for-loop-writes/#for-loop-writes-furb122).

The fix/lint was introduced in #10630
No reasoning is given on the unsafety in the PR/code.
The unsafety is determined here:

ea812d0813/crates/ruff_linter/src/rules/refurb/rules/for_loop_writes.rs (L200-L204)
Unsafe fix demonstration:
[playground](https://play.ruff.rs/06592f33-10b9-4a77-b31e-0d3a98f402f4)
```py
with open("issue.txt", "w") as f:
    for i in range(10):
        # will be deleted
        f.write(str(i))
```

---------

Co-authored-by: Dylan <dylwil3@gmail.com>
2025-06-23 13:21:37 +00:00
GiGaGon
8be205df99 [pyupgrade] Add fix safety section to UP010 (#18838)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

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

Part of #15584

This adds a `Fix safety` section to [unnecessary-future-import
(UP010)](https://docs.astral.sh/ruff/rules/unnecessary-future-import/#unnecessary-future-import-up010)

The unsafety is determined here:

d9266284df/crates/ruff_linter/src/rules/pyupgrade/rules/unnecessary_future_import.rs (L128-L132)

Unsafe code example:
[playground](https://play.ruff.rs/c07d8c41-9ab8-4b86-805b-8cf482d450d9)
```py
from __future__ import (print_function,# ...
__annotations__)  # ...
```

Edit: It looks like there was already a PR for this, #17490, but I
missed it since they said `UP029` instead of `UP010` :/

## Test Plan

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

N/A, no tests/functionality affected
2025-06-23 08:20:55 -05:00
GiGaGon
34fd44eb55 [flake8-logging] Add fix safety section to LOG001 (#18841)
Part of #15584

This adds a `Fix safety` section to [direct-logger-instantiation
(LOG001)](https://docs.astral.sh/ruff/rules/direct-logger-instantiation/#direct-logger-instantiation-log001).

The fix/lint was introduced in #7397
No reasoning is given on the unsafety in the PR/code
Unsafe fix demonstration:
[playground](https://play.ruff.rs/72e7277e-a9db-4cd9-9afb-2c56ef2db361)
```py
import logging
logger = logging.Logger(__name__)
```

## Test Plan

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

N/A, no tests/functionality affected

---------

Co-authored-by: Dylan <dylwil3@gmail.com>
2025-06-23 13:19:23 +00:00
Micha Reiser
b64307f65e fix casing of analyze.direction variant names (#18892)
## Summary

Fixes `analyze.direction` to use kebab-case for the variant names. 

Fixes https://github.com/astral-sh/ruff/issues/18887

## Test Plan

Created a `ruff.toml` and tested that both `dependents` and `Dependents`
were accepted
2025-06-23 14:30:30 +02:00
Victor Hugo Gomes
291413b126 [perflint] Fix PERF101 autofix creating a syntax error and mark autofix as unsafe if there are comments in the list call expr (#18803)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-06-23 11:51:46 +00:00
Dan Parizher
7ec7853cec [flake8-pytest-style] PT001/PT023 fix makes syntax error on parenthesized decorator (#18782)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-06-23 13:46:15 +02:00
David Peter
907c291877 [ty] Update mypy_primer, add two new projects (#18891)
## Summary

Pull in latest changes to mypy_primer:
01a7ca325f..e5f5544796
2025-06-23 13:08:11 +02:00
David Peter
21303d1a02 [ty] Minor change to builtins.md test (#18889)
## Summary

As far as I can tell, the two existing tests did the exact same thing.
Remove the redundant test, and add tests for all combinations of
declared/not-declared and local/"public" use of the name.

Proposing this as a separate PR before the behavior might change via
https://github.com/astral-sh/ruff/pull/18750
2025-06-23 12:32:50 +02:00
Victor Hugo Gomes
528ae8083b Remove redundant settings field from Checker (#18845)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-06-23 11:06:44 +02:00
Victor Hugo Gomes
0ce022e64e [refurb] Fix FURB163 autofix creating a syntax error for yield expressions (#18756) 2025-06-23 10:13:03 +02:00
Victor Hugo Gomes
659ecba477 [pylint] Supress PLE2510/2512/2513/2514/2515 autofix if the text contains an odd number of backslashes (#18856)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-06-23 10:11:51 +02:00
renovate[bot]
96660d93ca Update docker/setup-buildx-action action to v3.11.1 (#18881)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-23 08:06:43 +02:00
renovate[bot]
c111517f1b Update rui314/setup-mold digest to 85c79d0 (#18874)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-23 08:06:32 +02:00
renovate[bot]
9f29551fb2 Update PyO3/maturin-action action to v1.49.2 (#18875)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-23 08:06:18 +02:00
renovate[bot]
cc659c6988 Update Rust crate libc to v0.2.174 (#18876)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-23 08:06:03 +02:00
renovate[bot]
c8e03a0449 Update Rust crate mimalloc to v0.1.47 (#18877)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-23 08:05:40 +02:00
renovate[bot]
a5494839b1 Update astral-sh/setup-uv action to v6.3.0 (#18879)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-23 08:03:06 +02:00
renovate[bot]
835b37818c Update cargo-bins/cargo-binstall action to v1.14.1 (#18880)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-23 08:02:25 +02:00
renovate[bot]
f88fbc3952 Update Rust crate syn to v2.0.104 (#18878)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-23 08:02:04 +02:00
Charlie Marsh
9570d39f9b Remove extra dot in rule documentation (#18871) 2025-06-23 00:33:21 +00:00
Victor Hugo Gomes
06a78d0bd0 [pylint] Fix PLC1802 autofix creating a syntax error and mark autofix as unsafe if there's comments in the len call (#18836)
<!--
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
I've also found another bug while fixing this, where the diagnostic
would not trigger if the `len` call argument variable was shadowed. This
fixed a few false negatives in the test cases.
Example:
```python
fruits = []
fruits = []
if len(fruits):  # comment
    ...
```

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

## Test Plan
Add regression test
<!-- How was it tested? -->

---------

Co-authored-by: Charlie Marsh <crmarsh416@gmail.com>
2025-06-23 00:32:57 +00:00
Charlie Marsh
cfec89e8c3 Clarify PEP 8 relationship to whitespace-around-operator rules (#18870)
## Summary

See: https://github.com/astral-sh/ruff/issues/18868.
2025-06-22 20:30:34 -04:00
Victor Hugo Gomes
9089493263 [refurb] Mark FURB129 autofix as unsafe if there's comments in the readlines call (#18858) 2025-06-22 08:51:37 +01:00
Carl Meyer
089f5152f6 [ty] Fix mixed tuple subtyping (#18852)
## Summary

The code in the `Variable` branch of
`VariableLengthTupleSpec::has_relation_to` made the incorrect assumption
that if you zip two possibly-different-length iterators together and
iterate over the resulting zip iterator, the original two iterators will
only have their common elements consumed. But in fact, the zip iterator
detects that it is done when it receives a `None` from one iterator and
`Some()` element from the other iterator, which means that it consumes
one additional element from the longer iterator. This meant that we
failed to detect mismatched types on this extra consumed element,
because we never compared it to the variable type of the other tuple.

Use `zip_longest` from itertools as an alternative, which allows us to
combine all the handling into just two `zip_longest`, one for prefixes
and one for suffixes.

Marking this PR internal since it fixes a bug in a commit that wasn't
released yet.

## Test Plan

Added mdtests that failed before this fix and pass after it.
2025-06-21 13:09:23 -07:00
Alex Waygood
f24e650dfd [ty] Support --python=<symlink to executable> (#18827)
## Summary

Fixes https://github.com/astral-sh/ty/issues/640. If a user passes
`--python=<some-virtual-environment>/bin/python`, we must avoid
canonicalizing the path until we've traversed upwards to find the
`sys.prefix` directory (`<some-virtual-environment>`). On Unix systems,
`<sys.prefix>/bin/python` is often a symlink to a system interpreter; if
we resolve the symlink too easily then we'll add the system
interpreter's `site-packages` directory as a search path rather than the
virtual environment's directory.

## Test Plan

I added an integration test to
`crates/ty/tests/cli/python_environment.rs` which fails on `main`. I
also manually tested locally that running `cargo run -p ty check foo.py
--python=.venv/bin/python -vv` now prints this log to the terminal

```
2025-06-20 18:35:24.57702 DEBUG Resolved site-packages directories for this virtual environment are: SitePackagesPaths({"/Users/alexw/dev/ruff/.venv/lib/python3.13/site-packages"})
```

Whereas it previously resolved `site-packages` to my system
intallation's `site-packages` directory
2025-06-21 20:28:47 +01:00
Victor Hugo Gomes
f32ae94bc3 [pylint] Mark PLE0241 autofix as unsafe if there's comments in the base classes (#18832)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-06-21 17:20:39 +00:00
chiri
da6cbeee60 [flake8-raise] Make fix unsafe if it deletes comments (RSE102) (#18788) 2025-06-21 19:09:40 +02:00
Micha Reiser
cccbd0286e [ty] Add Tanjun benchmark (#18850) 2025-06-21 18:29:02 +02:00
Micha Reiser
a6ad8fb342 [ty] Add multithreaded benchmark (#18822) 2025-06-21 17:44:30 +02:00
Douglas Creager
ea812d0813 [ty] Homogeneous and mixed tuples (#18600)
We already had support for homogeneous tuples (`tuple[int, ...]`). This
PR extends this to also support mixed tuples (`tuple[str, str,
*tuple[int, ...], str str]`).

A mixed tuple consists of a fixed-length (possibly empty) prefix and
suffix, and a variable-length portion in the middle. Every element of
the variable-length portion must be of the same type. A homogeneous
tuple is then just a mixed tuple with an empty prefix and suffix.

The new data representation uses different Rust types for a fixed-length
(aka heterogeneous) tuple. Another option would have been to use the
`VariableLengthTuple` representation for all tuples, and to wrap the
"variable + suffix" portion in an `Option`. I don't think that would
simplify the method implementations much, though, since we would still
have a 2×2 case analysis for most of them.

One wrinkle is that the definition of the `tuple` class in the typeshed
has a single typevar, and canonically represents a homogeneous tuple.
When getting the class of a tuple instance, that means that we have to
summarize our detailed mixed tuple type information into its
"homogeneous supertype". (We were already doing this for heterogeneous
types.)

A similar thing happens when concatenating two mixed tuples: the
variable-length portion and suffix of the LHS, and the prefix and
variable-length portion of the RHS, all get unioned into the
variable-length portion of the result. The LHS prefix and RHS suffix
carry through unchanged.

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-06-20 18:23:54 -04:00
Victor Hugo Gomes
d9266284df Handle parenthesized arguments in remove_argument (#18805)
Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
2025-06-20 21:24:41 +00:00
Dylan
2d224e6096 Unify helpers modules (#18835)
A little bit of cleanup for consistency's sake: we move all the helpers
modules to a consistent location, and update the import paths when
needed. In the case of `refurb` there were two helpers modules, so we
just merged them.

Happy to revert the last commit if people are okay with `super::super` I
just thought it looked a little silly.
2025-06-20 16:03:01 -05:00
GiGaGon
ef785d2e74 Normalize some docs sections (#18831) 2025-06-20 21:56:11 +01:00
Robsdedude
e36611c4d8 [flake8_pyi] Fix PYI041's fix causing TypeError with None | None | ... (#18637)
<!--
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
Fix `PYI041`'s fix turning `None | int | None | float` into `None | None
| float`, which raises a `TypeError` when executed.

The fix consists of making sure that the merged super-type is inserted
where the first type that is merged was before.

## Test Plan
Tests have been expanded with examples from the issue.

## Related Issue
Fixes https://github.com/astral-sh/ruff/issues/18298
2025-06-20 15:04:51 -04:00
Hmvp
49763a7f7c [flake8-logging] Avoid false positive for exc_info=True outside logger.exception (LOG014) (#18737)
<!--
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

Fixes https://github.com/astral-sh/ruff/issues/18726 by also checking if
its a literal and not only that it is truthy. See also the first comment
in the issue.

It would have been nice to check for inheritance of BaseException but I
figured that is not possible yet...

## Test Plan

I added a few tests for valid input to exc_info
2025-06-20 14:43:08 -04:00
GiGaGon
2d25aaeaa2 [flake8-pie] Small docs fix to PIE794 (#18829)
<!--
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? -->

I noticed this since my code for finding missing safety fix sections
flagged it, there is a missing `/` causing part of the new changes to be
a normal comment instead of a doc comment

## Test Plan

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

N/A, no functionality/tests affected
2025-06-20 14:38:13 -04:00
Gideon
2910988b06 [pylint] Ignore __init__.py files in (PLC0414) (#18400)
## Summary

Ignore `__init__.py` files in `useless-import-alias` (PLC0414).
See discussion in #18365 and #6294: we want to allow redundant aliases
in `__init__.py` files, as they're almost always intentional explicit
re-exports.
Closes #18365
 Closes #6294

---------

Co-authored-by: Dylan <dylwil3@gmail.com>
2025-06-20 18:20:27 +00:00
Brent Westbrook
8cff77c82e Avoid generating diagnostics with per-file ignores (#18801)
## Summary

This PR avoids one of the three calls to `NoqaCode::rule` from
https://github.com/astral-sh/ruff/pull/18391 by applying per-file
ignores in the `LintContext`. To help with this, it also replaces all
direct uses of `LinterSettings.rules.enabled` with a
`LintContext::enabled` (or `Checker::enabled`, which defers to its
context) method. There are still some direct accesses to
`settings.rules`, but as far as I can tell these are not in a part of
the code where we can really access a `LintContext`. I believe all of
the code reachable from `check_path`, where the replaced per-file ignore
code was, should be converted to the new methods.

## Test Plan

Existing tests, with a single snapshot updated for RUF100, which I think
actually shows a more accurate diagnostic message now.
2025-06-20 13:33:09 -04:00
Victor Hugo Gomes
ffb09c84f2 [flake8-simplify] Fix false negatives for shadowed bindings (SIM910, SIM911) (#18794)
<!--
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
I also noticed that the tests for SIM911 were note being run, so I fixed
that.

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

## Test Plan
Add regression test
<!-- How was it tested? -->
2025-06-20 13:25:36 -04:00
Alex Waygood
dc160c4a49 [ty] Fix panics when pulling types for ClassVar or Final parameterized with >1 argument (#18824) 2025-06-20 18:06:40 +01:00
Yunchi Pang
073a71ca9a [pylint] add fix safety section (PLR1714) (#18415)
parent #15584
fix was introduced in #7910

---------

Co-authored-by: dylwil3 <dylwil3@gmail.com>
2025-06-20 15:40:44 +00:00
GiGaGon
23a3b6ef23 [Perflint] Small docs improvement to PERF401 (#18786)
<!--
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? -->

While reading the docs I noticed this paragraph on `PERF401`. It was
added in the same PR that the bug with `:=` was fixed, #15050, but don't
know why it was added. The fix should already take care of adding the
parenthesis, so having this paragraph in the docs is just confusing
since it sounds like the user has to do something.

## Test Plan

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

N/A, no tests/functionality affected
2025-06-20 09:55:49 -05:00
Gene Parmesan Thomas
5b3a501fae [pylint] Avoid flattening nested min/max when outer call has single argument (PLW3301) (#16885)
## Summary

Fixes false positives (and incorrect autofixes) in `nested-min-max`
(`PLW3301`) when the outer `min`/`max` call only has a single argument.
Previously the rule would flatten:

```python
min(min([2, 3], [4, 1]))
```

into `min([2, 3], [4, 1])`, changing the semantics. The rule now skips
any nested call when the outer call has only one positional argument.
The pylint fixture and snapshot were updated accordingly.

## Test Plan

Ran Ruff against the updated `nested_min_max.py` fixture:

```shell
cargo run -p ruff -- check crates/ruff_linter/resources/test/fixtures/pylint/nested_min_max.py --no-cache --select=PLW3301 --preview
```

to verify that `min(min([2, 3], [4, 1]))` and `max(max([2, 4], [3, 1]))`
are no longer flagged. Updated the fixture and snapshot; all other
existing warnings remain unchanged. The code compiles and the unit tests
pass.

---

This PR was generated by an AI system in collaboration with maintainers:
@carljm, @ntBre

Fixes #16163

---------

Signed-off-by: Gene Parmesan Thomas <201852096+gopoto@users.noreply.github.com>
Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2025-06-20 09:35:09 -04:00
Deric Crago
e66f182045 [ruff] Added cls.__dict__.get('__annotations__') check (RUF063) (#18233)
Added `cls.__dict__.get('__annotations__')` check for Python 3.10+ and
Python < 3.10 with `typing-extensions` enabled.

Closes #17853 

<!--
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

Added `cls.__dict__.get('__annotations__')` check for Python 3.10+ and
Python < 3.10 with `typing-extensions` enabled.

## Test Plan

`cargo test`

---------

Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-06-20 09:32:40 -04:00
Micha Reiser
f544026b81 [ty] Use HashTable in PlaceTable (#18819) 2025-06-20 15:31:54 +02:00
Gene Parmesan Thomas
234d248730 docs: Correct collections-named-tuple example to use PascalCase assignment (#16884) 2025-06-20 13:30:46 +00:00
David Peter
0ef324b2dd [ty] ecosystem-analyzer workflow (#18719)
## Summary

Adds a new ecosystem-analyzer workflow with a similar purpose to the
mypy-primer workflow. It creates a richer ecosystem diff report using
[ecosystem-analyzer](https://github.com/astral-sh/ecosystem-analyzer/)
([example
report](https://shark.fish/diff-attr-subscript-narrowing.html)). This is
still experimental and also quite a bit slower than mypy_primer, so I
chose to make this opt-in for now via a `ecosystem-analyzer` label. This
would give us a way to play with this while still evaluating if we
should further invest in this or not.

Advantages over the mypy_primer diff output:
- Interactive filtering of diagnostics
- Statistics overview which breaks down added/removed/changed
diagnostics across lint rules
- Has the concept of "changed" diagnostics, which makes it easier to
review changes where diagnostic messages have changed (along with other
changes).
- Compute diff based on old and new project-lists (`good.txt`). This
allows us to diff changes to the project list itself. This has caused
confusion in the past where we tried to add new projects to `good.txt`,
but then ran the `main`-branch version of ty on that new list (where the
bug was not yet fixed)

Disadvantages:
- The report currently needs to be downloaded from the workflow run, as
I don't know if we have a way of deploying HTML files like this
temporarily to some hosted infrastructure.
2025-06-20 15:23:22 +02:00
med1844
7982edac90 [ty] Add support for @staticmethods (#18809)
## Summary

Add support for `@staticmethod`s. Overall, the changes are very similar
to #16305.

#18587 will be dependent on this PR for a potential fix of
https://github.com/astral-sh/ty/issues/207.

mypy_primer will look bad since the new code allows ty to check more
code.

## Test Plan

Added new markdown tests. Please comment if there's any missing tests
that I should add in, thank you.
2025-06-20 10:38:17 +02:00
Andrej
e180975226 unnecessary_dict_kwargs doc - a note on type checking benefits (#18666) 2025-06-20 08:27:51 +02:00
Victor Hugo Gomes
97819f8a37 [flake8-pytest-style] Mark autofix for PT001 and PT023 as unsafe if there's comments in the decorator (#18792) 2025-06-20 08:23:59 +02:00
Dhruv Manilawala
22177e6915 [ty] Surface matched overload diagnostic directly (#18452)
## Summary

This PR resolves the way diagnostics are reported for an invalid call to
an overloaded function.

If any of the steps in the overload call evaluation algorithm yields a
matching overload but it's type checking that failed, the
`no-matching-overload` diagnostic is incorrect because there is a
matching overload, it's the arguments passed that are invalid as per the
signature. So, this PR improves that by surfacing the diagnostics on the
matching overload directly.

It also provides additional context, specifically the matching overload
where this error occurred and other non-matching overloads. Consider the
following example:

```py
from typing import overload


@overload
def f() -> None: ...
@overload
def f(x: int) -> int: ...
@overload
def f(x: int, y: int) -> int: ...
def f(x: int | None = None, y: int | None = None) -> int | None:
    return None


f("a")
```

We get:

<img width="857" alt="Screenshot 2025-06-18 at 11 07 10"
src="https://github.com/user-attachments/assets/8dbcaf13-2a74-4661-aa94-1225c9402ea6"
/>


## Test Plan

Update test cases, resolve existing todos and validate the updated
snapshots.
2025-06-20 08:36:49 +05:30
InSync
20d73dd41c [ty] Report when a dataclass contains more than one KW_ONLY field (#18731)
## Summary

Part of [#111](https://github.com/astral-sh/ty/issues/111).

After this change, dataclasses with two or more `KW_ONLY` field will be
reported as invalid. The duplicate fields will simply be ignored when
computing `__init__`'s signature.

## Test Plan

Markdown tests.
2025-06-19 19:42:31 -07:00
GiGaGon
50bf3fa45a [flake8-pie] Add fix safety section to PIE794 (#18802)
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

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

Part of #15584

This PR adds a fix safety section to `PIE794`

I could not track down when this rule was initially implemented/made
unsafe due how old it could be + multiple large refactors to `ruff`.

There is no comment/reasoning in the code given for the unsafety.

Here is a code example demonstrating why it should be unsafe, since
removing any of the assignments would change program behavior
[playground](https://play.ruff.rs/01004644-4259-4449-a581-5007cd59846a)
```py
class A:
    x = 1
    x = 2
    print(x)

class B:
    x = print(3)
    x = print(4)

class C:
    x = [1,2,3]
    y = x
    x = y[1]
```

## Test Plan

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

N/A, no tests affected.

---------

Co-authored-by: Dylan <dylwil3@gmail.com>
2025-06-19 21:51:23 +00:00
GiGaGon
440635cbe6 [pycodestyle] Add fix safety section to W291 and W293 (#18800)
Part of #15584

This PR adds fix safety sections to `W291` and `W293`

The unsafe caveat was added in #10049


10a1d9f01e/crates/ruff_linter/src/rules/pycodestyle/rules/trailing_whitespace.rs (L92)

Code example demonstrating unsafety:
```
PS ~\Desktop\New_folder\ruff>Get-Content issue.py
```
```py
# W291
"""
1
"""

# W293
"""

"""
```
```
PS ~\Desktop\New_folder\ruff>Get-Escaped-Content issue.py
```
```
# W291\n"""\n1 \n"""\n\n# W293\n"""\n \n"""\r\n
```
```
PS ~\Desktop\New_folder\ruff>uvx ruff check issue.py --isolated --select W
```
```snap
issue.py:3:2: W291 Trailing whitespace
  |
1 | # W291
2 | """
3 | 1
  |  ^ W291
4 | """
  |
  = help: Remove trailing whitespace

issue.py:8:1: W293 Blank line contains whitespace
  |
6 | # W293
7 | """
8 |
  | ^ W293
9 | """
  |
  = help: Remove whitespace from blank line

Found 2 errors.
No fixes available (2 hidden fixes can be enabled with the `--unsafe-fixes` option).
```

## Test Plan

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

N/A, no tests affected.
2025-06-19 16:48:02 -05:00
Dylan
ce0a32aadb [flake8-comprehensions] Handle template strings for comprehension fixes (#18710)
Essentially this PR ensures that when we do fixes like this:

```diff
- t"{set(f(x) for x in foo)}"
+ t"{ {f(x) for x in foo} }"
```
we are correctly adding whitespace around the braces. 

This logic is already in place for f-strings and just needed to be
generalized to interpolated strings.
2025-06-19 16:23:46 -05:00
Brent Westbrook
10a1d9f01e Unify OldDiagnostic and Message (#18391)
Summary
--

This PR unifies the remaining differences between `OldDiagnostic` and
`Message` (`OldDiagnostic` was only missing an optional `noqa_offset`
field) and
replaces `Message` with `OldDiagnostic`.

The biggest functional difference is that the combined `OldDiagnostic`
kind no
longer implements `AsRule` for an infallible conversion to `Rule`. This
was
pretty easy to work around with `is_some_and` and `is_none_or` in the
few places
it was needed. In `LintContext::report_diagnostic_if_enabled` we can
just use
the new `Violation::rule` method, which takes care of most cases.

Most of the interesting changes are in [this
range](8156992540)
before I started renaming.

Test Plan
--

Existing tests

Future Work
--

I think it's time to start shifting some of these fields to the new
`Diagnostic`
kind. I believe we want `Fix` for sure, but I'm less sure about the
others. We
may want to keep a thin wrapper type here anyway to implement a `rule`
method,
so we could leave some of these fields on that too.
2025-06-19 09:37:58 -04:00
Robsdedude
4e83db4d40 [pylint] Detect more exotic NaN literals in PLW0177 (#18630)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-06-19 11:05:06 +00:00
Victor Hugo Gomes
136443b71b [flake8-async] Mark autofix for ASYNC115 as unsafe if the call expression contains comments (#18753)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-06-19 13:01:33 +02:00
Victor Hugo Gomes
f7a741a99e [flake8-bugbear] Mark autofix for B004 as unsafe if the hasattr call expr contains comments (#18755) 2025-06-19 10:46:53 +00:00
Charlie Marsh
4c8d612120 Enforce pytest import for decorators (#18779) 2025-06-19 09:49:34 +00:00
Victor Hugo Gomes
65b288b45b [flake8-comprehension] Mark autofix for C420 as unsafe if there's comments inside the dict comprehension (#18768) 2025-06-19 09:43:05 +00:00
chiri
06da2c808f [flake8-async] fix detection for large integer sleep durations in ASYNC116 rule (#18767)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-06-19 09:37:20 +00:00
renovate[bot]
55a2ff91c7 Update dependency ruff to v0.12.0 (#18790)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-19 09:32:47 +00:00
renovate[bot]
d6705f4700 Update taiki-e/install-action action to v2.53.2 (#18789)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-19 09:31:46 +00:00
Frazer McLean
f67ff33177 Add lint rule for calling chmod with non-octal integers (#18541)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-06-19 11:30:29 +02:00
Nikolas Hearp
dcf0a8d4d7 Mark RET501 fix unsafe if comments are inside (#18780)
Co-authored-by: Charlie Marsh <crmarsh416@gmail.com>
2025-06-19 11:12:12 +02:00
1003 changed files with 41008 additions and 17678 deletions

View File

@@ -49,7 +49,7 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build sdist"
uses: PyO3/maturin-action@aef21716ff3dcae8a1c301d23ec3e4446972a6e3 # v1.49.1
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
with:
command: sdist
args: --out dist
@@ -79,7 +79,7 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels - x86_64"
uses: PyO3/maturin-action@aef21716ff3dcae8a1c301d23ec3e4446972a6e3 # v1.49.1
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
with:
target: x86_64
args: --release --locked --out dist
@@ -121,7 +121,7 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels - aarch64"
uses: PyO3/maturin-action@aef21716ff3dcae8a1c301d23ec3e4446972a6e3 # v1.49.1
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
with:
target: aarch64
args: --release --locked --out dist
@@ -177,7 +177,7 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: PyO3/maturin-action@aef21716ff3dcae8a1c301d23ec3e4446972a6e3 # v1.49.1
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
with:
target: ${{ matrix.platform.target }}
args: --release --locked --out dist
@@ -230,7 +230,7 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: PyO3/maturin-action@aef21716ff3dcae8a1c301d23ec3e4446972a6e3 # v1.49.1
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
with:
target: ${{ matrix.target }}
manylinux: auto
@@ -304,7 +304,7 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: PyO3/maturin-action@aef21716ff3dcae8a1c301d23ec3e4446972a6e3 # v1.49.1
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
with:
target: ${{ matrix.platform.target }}
manylinux: auto
@@ -370,7 +370,7 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: PyO3/maturin-action@aef21716ff3dcae8a1c301d23ec3e4446972a6e3 # v1.49.1
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
with:
target: ${{ matrix.target }}
manylinux: musllinux_1_2
@@ -435,7 +435,7 @@ jobs:
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: PyO3/maturin-action@aef21716ff3dcae8a1c301d23ec3e4446972a6e3 # v1.49.1
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
with:
target: ${{ matrix.platform.target }}
manylinux: musllinux_1_2

View File

@@ -38,7 +38,7 @@ jobs:
submodules: recursive
persist-credentials: false
- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
with:
@@ -119,7 +119,7 @@ jobs:
pattern: digests-*
merge-multiple: true
- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- name: Extract metadata (tags, labels) for Docker
id: meta
@@ -167,7 +167,7 @@ jobs:
- debian:bookworm-slim,bookworm-slim,debian-slim
- buildpack-deps:bookworm,bookworm,debian
steps:
- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
with:
@@ -262,7 +262,7 @@ jobs:
pattern: digests-*
merge-multiple: true
- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- name: Extract metadata (tags, labels) for Docker
id: meta

View File

@@ -214,7 +214,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- name: "Install Rust toolchain"
run: |
rustup component add clippy
@@ -234,17 +234,17 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"
uses: rui314/setup-mold@b3958095189f34b95d402a680b6e96b7f194f7b9 # v1
uses: rui314/setup-mold@85c79d00377f0d32cdbae595a46de6f7c2fa6599 # v1
- name: "Install cargo nextest"
uses: taiki-e/install-action@735e5933943122c5ac182670a935f54a949265c1 # v2.52.4
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
with:
tool: cargo-nextest
- name: "Install cargo insta"
uses: taiki-e/install-action@735e5933943122c5ac182670a935f54a949265c1 # v2.52.4
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
with:
tool: cargo-insta
- name: ty mdtests (GitHub annotations)
@@ -292,17 +292,17 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"
uses: rui314/setup-mold@b3958095189f34b95d402a680b6e96b7f194f7b9 # v1
uses: rui314/setup-mold@85c79d00377f0d32cdbae595a46de6f7c2fa6599 # v1
- name: "Install cargo nextest"
uses: taiki-e/install-action@735e5933943122c5ac182670a935f54a949265c1 # v2.52.4
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
with:
tool: cargo-nextest
- name: "Install cargo insta"
uses: taiki-e/install-action@735e5933943122c5ac182670a935f54a949265c1 # v2.52.4
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
with:
tool: cargo-insta
- name: "Run tests"
@@ -321,11 +321,11 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- name: "Install Rust toolchain"
run: rustup show
- name: "Install cargo nextest"
uses: taiki-e/install-action@735e5933943122c5ac182670a935f54a949265c1 # v2.52.4
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
with:
tool: cargo-nextest
- name: "Run tests"
@@ -348,7 +348,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- name: "Install Rust toolchain"
run: rustup target add wasm32-unknown-unknown
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
@@ -377,11 +377,11 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"
uses: rui314/setup-mold@b3958095189f34b95d402a680b6e96b7f194f7b9 # v1
uses: rui314/setup-mold@85c79d00377f0d32cdbae595a46de6f7c2fa6599 # v1
- name: "Build"
run: cargo build --release --locked
@@ -400,19 +400,19 @@ jobs:
with:
file: "Cargo.toml"
field: "workspace.package.rust-version"
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- name: "Install Rust toolchain"
env:
MSRV: ${{ steps.msrv.outputs.value }}
run: rustup default "${MSRV}"
- name: "Install mold"
uses: rui314/setup-mold@b3958095189f34b95d402a680b6e96b7f194f7b9 # v1
uses: rui314/setup-mold@85c79d00377f0d32cdbae595a46de6f7c2fa6599 # v1
- name: "Install cargo nextest"
uses: taiki-e/install-action@735e5933943122c5ac182670a935f54a949265c1 # v2.52.4
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
with:
tool: cargo-nextest
- name: "Install cargo insta"
uses: taiki-e/install-action@735e5933943122c5ac182670a935f54a949265c1 # v2.52.4
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
with:
tool: cargo-insta
- name: "Run tests"
@@ -432,13 +432,13 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
with:
workspaces: "fuzz -> target"
- name: "Install Rust toolchain"
run: rustup show
- name: "Install cargo-binstall"
uses: cargo-bins/cargo-binstall@ea65a39d2dcca142c53bddd3a097a674e903f475 # v1.12.7
uses: cargo-bins/cargo-binstall@8aac5aa2bf0dfaa2863eccad9f43c68fe40e5ec8 # v1.14.1
with:
tool: cargo-fuzz@0.11.2
- name: "Install cargo-fuzz"
@@ -460,7 +460,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
name: Download Ruff binary to test
id: download-cached-binary
@@ -494,7 +494,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- name: "Install Rust toolchain"
run: rustup component add rustfmt
# Run all code generation scripts, and verify that the current output is
@@ -661,7 +661,7 @@ jobs:
branch: ${{ github.event.pull_request.base.ref }}
workflow: "ci.yaml"
check_artifacts: true
- uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- name: Fuzz
env:
FORCE_COLOR: 1
@@ -691,7 +691,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: cargo-bins/cargo-binstall@ea65a39d2dcca142c53bddd3a097a674e903f475 # v1.12.7
- uses: cargo-bins/cargo-binstall@8aac5aa2bf0dfaa2863eccad9f43c68fe40e5ec8 # v1.14.1
- run: cargo binstall --no-confirm cargo-shear
- run: cargo shear
@@ -708,11 +708,11 @@ jobs:
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- name: "Prep README.md"
run: python scripts/transform_readme.py --target pypi
- name: "Build wheels"
uses: PyO3/maturin-action@aef21716ff3dcae8a1c301d23ec3e4446972a6e3 # v1.49.1
uses: PyO3/maturin-action@e10f6c464b90acceb5f640d31beda6d586ba7b4a # v1.49.3
with:
args: --out dist
- name: "Test wheel"
@@ -731,8 +731,8 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 22
@@ -765,7 +765,7 @@ jobs:
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.13"
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- name: "Add SSH key"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1
@@ -774,7 +774,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- name: Install uv
uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- name: "Install Insiders dependencies"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
run: uv pip install -r docs/requirements-insiders.txt --system
@@ -804,7 +804,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- name: "Install Rust toolchain"
run: rustup show
- name: "Run checks"
@@ -874,7 +874,7 @@ jobs:
persist-credentials: false
- name: "Install Rust toolchain"
run: rustup target add wasm32-unknown-unknown
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 22
@@ -905,14 +905,14 @@ jobs:
with:
persist-credentials: false
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
- uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- name: "Install Rust toolchain"
run: rustup show
- name: "Install codspeed"
uses: taiki-e/install-action@735e5933943122c5ac182670a935f54a949265c1 # v2.52.4
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
with:
tool: cargo-codspeed
@@ -938,14 +938,14 @@ jobs:
with:
persist-credentials: false
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
- uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- name: "Install Rust toolchain"
run: rustup show
- name: "Install codspeed"
uses: taiki-e/install-action@735e5933943122c5ac182670a935f54a949265c1 # v2.52.4
uses: taiki-e/install-action@f3a27926ea13d7be3ee2f4cbb925883cf9442b56 # v2.56.7
with:
tool: cargo-codspeed

View File

@@ -34,12 +34,12 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- name: "Install Rust toolchain"
run: rustup show
- name: "Install mold"
uses: rui314/setup-mold@b3958095189f34b95d402a680b6e96b7f194f7b9 # v1
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
uses: rui314/setup-mold@85c79d00377f0d32cdbae595a46de6f7c2fa6599 # v1
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- name: Build ruff
# A debug build means the script runs slower once it gets started,
# but this is outweighed by the fact that a release build takes *much* longer to compile in CI

View File

@@ -37,9 +37,9 @@ jobs:
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
with:
workspaces: "ruff"
@@ -48,45 +48,13 @@ jobs:
- name: Run mypy_primer
shell: bash
env:
PRIMER_SELECTOR: crates/ty_python_semantic/resources/primer/good.txt
DIFF_FILE: mypy_primer.diff
run: |
cd ruff
echo "Enabling mypy primer specific configuration overloads (see .github/mypy-primer-ty.toml)"
mkdir -p ~/.config/ty
cp .github/mypy-primer-ty.toml ~/.config/ty/ty.toml
PRIMER_SELECTOR="$(paste -s -d'|' crates/ty_python_semantic/resources/primer/good.txt)"
echo "new commit"
git rev-list --format=%s --max-count=1 "$GITHUB_SHA"
MERGE_BASE="$(git merge-base "$GITHUB_SHA" "origin/$GITHUB_BASE_REF")"
git checkout -b base_commit "$MERGE_BASE"
echo "base commit"
git rev-list --format=%s --max-count=1 base_commit
cd ..
echo "Project selector: $PRIMER_SELECTOR"
# Allow the exit code to be 0 or 1, only fail for actual mypy_primer crashes/bugs
uvx \
--from="git+https://github.com/hauntsaninja/mypy_primer@01a7ca325f674433c58e02416a867178d1571128" \
mypy_primer \
--repo ruff \
--type-checker ty \
--old base_commit \
--new "$GITHUB_SHA" \
--project-selector "/($PRIMER_SELECTOR)\$" \
--output concise \
--debug > mypy_primer.diff || [ $? -eq 1 ]
# Output diff with ANSI color codes
cat mypy_primer.diff
# Remove ANSI color codes before uploading
sed -ie 's/\x1b\[[0-9;]*m//g' mypy_primer.diff
echo ${{ github.event.number }} > pr-number
scripts/mypy_primer.sh
echo ${{ github.event.number }} > ../pr-number
- name: Upload diff
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
@@ -99,3 +67,41 @@ jobs:
with:
name: pr-number
path: pr-number
memory_usage:
name: Run memory statistics
runs-on: depot-ubuntu-22.04-32
timeout-minutes: 20
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
path: ruff
fetch-depth: 0
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
with:
workspaces: "ruff"
- name: Install Rust toolchain
run: rustup show
- name: Run mypy_primer
shell: bash
env:
TY_MAX_PARALLELISM: 1 # for deterministic memory numbers
TY_MEMORY_REPORT: mypy_primer
PRIMER_SELECTOR: crates/ty_python_semantic/resources/primer/memory.txt
DIFF_FILE: mypy_primer_memory.diff
run: |
cd ruff
scripts/mypy_primer.sh
- name: Upload diff
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: mypy_primer_memory_diff
path: mypy_primer_memory.diff

View File

@@ -45,15 +45,28 @@ jobs:
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'
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 ]]
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 cannot be a symlink"
echo "Error: mypy_primer.diff and mypy_primer_memory.diff cannot be a symlink"
exit 1
fi
@@ -74,6 +87,18 @@ jobs:
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"

View File

@@ -68,7 +68,7 @@ jobs:
- name: "Install Rust toolchain"
run: rustup show
- uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
- name: "Install Insiders dependencies"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}

View File

@@ -22,7 +22,7 @@ jobs:
id-token: write
steps:
- name: "Install uv"
uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
pattern: wheels-*

View File

@@ -0,0 +1,94 @@
name: ty ecosystem-analyzer
permissions: {}
on:
pull_request:
types: [labeled]
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true
env:
CARGO_INCREMENTAL: 0
CARGO_NET_RETRY: 10
CARGO_TERM_COLOR: always
RUSTUP_MAX_RETRIES: 10
RUST_BACKTRACE: 1
REF_NAME: ${{ github.ref_name }}
jobs:
ty-ecosystem-analyzer:
name: Compute diagnostic diff
runs-on: depot-ubuntu-22.04-32
timeout-minutes: 20
if: contains(github.event.label.name, 'ecosystem-analyzer')
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
path: ruff
fetch-depth: 0
persist-credentials: false
- name: Install the latest version of uv
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
with:
workspaces: "ruff"
- name: Install Rust toolchain
run: rustup show
- name: Compute diagnostic diff
shell: bash
run: |
cd ruff
echo "Enabling configuration overloads (see .github/mypy-primer-ty.toml)"
mkdir -p ~/.config/ty
cp .github/mypy-primer-ty.toml ~/.config/ty/ty.toml
echo "new commit"
git checkout -b new_commit "$GITHUB_SHA"
git rev-list --format=%s --max-count=1 new_commit
cp crates/ty_python_semantic/resources/primer/good.txt projects_new.txt
echo "old commit (merge base)"
MERGE_BASE="$(git merge-base "$GITHUB_SHA" "origin/$GITHUB_BASE_REF")"
git checkout -b old_commit "$MERGE_BASE"
git rev-list --format=%s --max-count=1 old_commit
cp crates/ty_python_semantic/resources/primer/good.txt projects_old.txt
cd ..
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@9c34dc514ee9aef6735db1dfebb80f63acbc3440"
ecosystem-analyzer \
--repository ruff \
analyze \
--projects ruff/projects_old.txt \
--commit old_commit \
--output diagnostics_old.json
ecosystem-analyzer \
--repository ruff \
analyze \
--projects ruff/projects_new.txt \
--commit new_commit \
--output diagnostics_new.json
ecosystem-analyzer \
generate-diff \
diagnostics_old.json \
diagnostics_new.json \
--old-name "main (merge base)" \
--new-name "$REF_NAME" \
--output-html diff.html
- name: Upload HTML diff report
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: diff.html
path: diff.html

View File

@@ -67,7 +67,7 @@ repos:
- black==25.1.0
- repo: https://github.com/crate-ci/typos
rev: v1.33.1
rev: v1.34.0
hooks:
- id: typos
@@ -81,7 +81,7 @@ repos:
pass_filenames: false # This makes it a lot faster
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.13
rev: v0.12.2
hooks:
- id: ruff-format
- id: ruff
@@ -91,7 +91,7 @@ repos:
# Prettier
- repo: https://github.com/rbubley/mirrors-prettier
rev: v3.5.3
rev: v3.6.2
hooks:
- id: prettier
types: [yaml]
@@ -99,12 +99,12 @@ 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.9.0
rev: v1.11.0
hooks:
- id: zizmor
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.33.0
rev: 0.33.2
hooks:
- id: check-github-workflows

View File

@@ -1,5 +1,137 @@
# Changelog
## 0.12.2
### Preview features
- \[`flake8-pyi`\] Expand `Optional[A]` to `A | None` (`PYI016`) ([#18572](https://github.com/astral-sh/ruff/pull/18572))
- \[`pyupgrade`\] Mark `UP008` fix safe if no comments are in range ([#18683](https://github.com/astral-sh/ruff/pull/18683))
### Bug fixes
- \[`flake8-comprehensions`\] Fix `C420` to prepend whitespace when needed ([#18616](https://github.com/astral-sh/ruff/pull/18616))
- \[`perflint`\] Fix `PERF403` panic on attribute or subscription loop variable ([#19042](https://github.com/astral-sh/ruff/pull/19042))
- \[`pydocstyle`\] Fix `D413` infinite loop for parenthesized docstring ([#18930](https://github.com/astral-sh/ruff/pull/18930))
- \[`pylint`\] Fix `PLW0108` autofix introducing a syntax error when the lambda's body contains an assignment expression ([#18678](https://github.com/astral-sh/ruff/pull/18678))
- \[`refurb`\] Fix false positive on empty tuples (`FURB168`) ([#19058](https://github.com/astral-sh/ruff/pull/19058))
- \[`ruff`\] Allow more `field` calls from `attrs` (`RUF009`) ([#19021](https://github.com/astral-sh/ruff/pull/19021))
- \[`ruff`\] Fix syntax error introduced for an empty string followed by a u-prefixed string (`UP025`) ([#18899](https://github.com/astral-sh/ruff/pull/18899))
### Rule changes
- \[`flake8-executable`\] Allow `uvx` in shebang line (`EXE003`) ([#18967](https://github.com/astral-sh/ruff/pull/18967))
- \[`pandas`\] Avoid flagging `PD002` if `pandas` is not imported ([#18963](https://github.com/astral-sh/ruff/pull/18963))
- \[`pyupgrade`\] Avoid PEP-604 unions with `typing.NamedTuple` (`UP007`, `UP045`) ([#18682](https://github.com/astral-sh/ruff/pull/18682))
### Documentation
- Document link between `import-outside-top-level (PLC0415)` and `lint.flake8-tidy-imports.banned-module-level-imports` ([#18733](https://github.com/astral-sh/ruff/pull/18733))
- Fix description of the `format.skip-magic-trailing-comma` example ([#19095](https://github.com/astral-sh/ruff/pull/19095))
- \[`airflow`\] Make `AIR302` example error out-of-the-box ([#18988](https://github.com/astral-sh/ruff/pull/18988))
- \[`airflow`\] Make `AIR312` example error out-of-the-box ([#18989](https://github.com/astral-sh/ruff/pull/18989))
- \[`flake8-annotations`\] Make `ANN401` example error out-of-the-box ([#18974](https://github.com/astral-sh/ruff/pull/18974))
- \[`flake8-async`\] Make `ASYNC100` example error out-of-the-box ([#18993](https://github.com/astral-sh/ruff/pull/18993))
- \[`flake8-async`\] Make `ASYNC105` example error out-of-the-box ([#19002](https://github.com/astral-sh/ruff/pull/19002))
- \[`flake8-async`\] Make `ASYNC110` example error out-of-the-box ([#18975](https://github.com/astral-sh/ruff/pull/18975))
- \[`flake8-async`\] Make `ASYNC210` example error out-of-the-box ([#18977](https://github.com/astral-sh/ruff/pull/18977))
- \[`flake8-async`\] Make `ASYNC220`, `ASYNC221`, and `ASYNC222` examples error out-of-the-box ([#18978](https://github.com/astral-sh/ruff/pull/18978))
- \[`flake8-async`\] Make `ASYNC251` example error out-of-the-box ([#18990](https://github.com/astral-sh/ruff/pull/18990))
- \[`flake8-bandit`\] Make `S201` example error out-of-the-box ([#19017](https://github.com/astral-sh/ruff/pull/19017))
- \[`flake8-bandit`\] Make `S604` and `S609` examples error out-of-the-box ([#19049](https://github.com/astral-sh/ruff/pull/19049))
- \[`flake8-bugbear`\] Make `B028` example error out-of-the-box ([#19054](https://github.com/astral-sh/ruff/pull/19054))
- \[`flake8-bugbear`\] Make `B911` example error out-of-the-box ([#19051](https://github.com/astral-sh/ruff/pull/19051))
- \[`flake8-datetimez`\] Make `DTZ011` example error out-of-the-box ([#19055](https://github.com/astral-sh/ruff/pull/19055))
- \[`flake8-datetimez`\] Make `DTZ901` example error out-of-the-box ([#19056](https://github.com/astral-sh/ruff/pull/19056))
- \[`flake8-pyi`\] Make `PYI032` example error out-of-the-box ([#19061](https://github.com/astral-sh/ruff/pull/19061))
- \[`flake8-pyi`\] Make example error out-of-the-box (`PYI014`, `PYI015`) ([#19097](https://github.com/astral-sh/ruff/pull/19097))
- \[`flake8-pyi`\] Make example error out-of-the-box (`PYI042`) ([#19101](https://github.com/astral-sh/ruff/pull/19101))
- \[`flake8-pyi`\] Make example error out-of-the-box (`PYI059`) ([#19080](https://github.com/astral-sh/ruff/pull/19080))
- \[`flake8-pyi`\] Make example error out-of-the-box (`PYI062`) ([#19079](https://github.com/astral-sh/ruff/pull/19079))
- \[`flake8-pytest-style`\] Make example error out-of-the-box (`PT023`) ([#19104](https://github.com/astral-sh/ruff/pull/19104))
- \[`flake8-pytest-style`\] Make example error out-of-the-box (`PT030`) ([#19105](https://github.com/astral-sh/ruff/pull/19105))
- \[`flake8-quotes`\] Make example error out-of-the-box (`Q003`) ([#19106](https://github.com/astral-sh/ruff/pull/19106))
- \[`flake8-simplify`\] Make example error out-of-the-box (`SIM110`) ([#19113](https://github.com/astral-sh/ruff/pull/19113))
- \[`flake8-simplify`\] Make example error out-of-the-box (`SIM113`) ([#19109](https://github.com/astral-sh/ruff/pull/19109))
- \[`flake8-simplify`\] Make example error out-of-the-box (`SIM401`) ([#19110](https://github.com/astral-sh/ruff/pull/19110))
- \[`pyflakes`\] Fix backslash in docs (`F621`) ([#19098](https://github.com/astral-sh/ruff/pull/19098))
- \[`pylint`\] Fix `PLC0415` example ([#18970](https://github.com/astral-sh/ruff/pull/18970))
## 0.12.1
### Preview features
- \[`flake8-errmsg`\] Extend `EM101` to support byte strings ([#18867](https://github.com/astral-sh/ruff/pull/18867))
- \[`flake8-use-pathlib`\] Add autofix for `PTH202` ([#18763](https://github.com/astral-sh/ruff/pull/18763))
- \[`pygrep-hooks`\] Add `AsyncMock` methods to `invalid-mock-access` (`PGH005`) ([#18547](https://github.com/astral-sh/ruff/pull/18547))
- \[`pylint`\] Ignore `__init__.py` files in (`PLC0414`) ([#18400](https://github.com/astral-sh/ruff/pull/18400))
- \[`ruff`\] Trigger `RUF037` for empty string and byte strings ([#18862](https://github.com/astral-sh/ruff/pull/18862))
- [formatter] Fix missing blank lines before decorated classes in `.pyi` files ([#18888](https://github.com/astral-sh/ruff/pull/18888))
### Bug fixes
- Avoid generating diagnostics with per-file ignores ([#18801](https://github.com/astral-sh/ruff/pull/18801))
- Handle parenthesized arguments in `remove_argument` ([#18805](https://github.com/astral-sh/ruff/pull/18805))
- \[`flake8-logging`\] Avoid false positive for `exc_info=True` outside `logger.exception` (`LOG014`) ([#18737](https://github.com/astral-sh/ruff/pull/18737))
- \[`flake8-pytest-style`\] Enforce `pytest` import for decorators ([#18779](https://github.com/astral-sh/ruff/pull/18779))
- \[`flake8-pytest-style`\] Mark autofix for `PT001` and `PT023` as unsafe if there's comments in the decorator ([#18792](https://github.com/astral-sh/ruff/pull/18792))
- \[`flake8-pytest-style`\] `PT001`/`PT023` fix makes syntax error on parenthesized decorator ([#18782](https://github.com/astral-sh/ruff/pull/18782))
- \[`flake8-raise`\] Make fix unsafe if it deletes comments (`RSE102`) ([#18788](https://github.com/astral-sh/ruff/pull/18788))
- \[`flake8-simplify`\] Fix `SIM911` autofix creating a syntax error ([#18793](https://github.com/astral-sh/ruff/pull/18793))
- \[`flake8-simplify`\] Fix false negatives for shadowed bindings (`SIM910`, `SIM911`) ([#18794](https://github.com/astral-sh/ruff/pull/18794))
- \[`flake8-simplify`\] Preserve original behavior for `except ()` and bare `except` (`SIM105`) ([#18213](https://github.com/astral-sh/ruff/pull/18213))
- \[`flake8-pyi`\] Fix `PYI041`'s fix causing `TypeError` with `None | None | ...` ([#18637](https://github.com/astral-sh/ruff/pull/18637))
- \[`perflint`\] Fix `PERF101` autofix creating a syntax error and mark autofix as unsafe if there are comments in the `list` call expr ([#18803](https://github.com/astral-sh/ruff/pull/18803))
- \[`perflint`\] Fix false negative in `PERF401` ([#18866](https://github.com/astral-sh/ruff/pull/18866))
- \[`pylint`\] Avoid flattening nested `min`/`max` when outer call has single argument (`PLW3301`) ([#16885](https://github.com/astral-sh/ruff/pull/16885))
- \[`pylint`\] Fix `PLC2801` autofix creating a syntax error ([#18857](https://github.com/astral-sh/ruff/pull/18857))
- \[`pylint`\] Mark `PLE0241` autofix as unsafe if there's comments in the base classes ([#18832](https://github.com/astral-sh/ruff/pull/18832))
- \[`pylint`\] Suppress `PLE2510`/`PLE2512`/`PLE2513`/`PLE2514`/`PLE2515` autofix if the text contains an odd number of backslashes ([#18856](https://github.com/astral-sh/ruff/pull/18856))
- \[`refurb`\] Detect more exotic float literals in `FURB164` ([#18925](https://github.com/astral-sh/ruff/pull/18925))
- \[`refurb`\] Fix `FURB163` autofix creating a syntax error for `yield` expressions ([#18756](https://github.com/astral-sh/ruff/pull/18756))
- \[`refurb`\] Mark `FURB129` autofix as unsafe if there's comments in the `readlines` call ([#18858](https://github.com/astral-sh/ruff/pull/18858))
- \[`ruff`\] Fix false positives and negatives in `RUF010` ([#18690](https://github.com/astral-sh/ruff/pull/18690))
- Fix casing of `analyze.direction` variant names ([#18892](https://github.com/astral-sh/ruff/pull/18892))
### Rule changes
- Fix f-string interpolation escaping in generated fixes ([#18882](https://github.com/astral-sh/ruff/pull/18882))
- \[`flake8-return`\] Mark `RET501` fix unsafe if comments are inside ([#18780](https://github.com/astral-sh/ruff/pull/18780))
- \[`flake8-async`\] Fix detection for large integer sleep durations in `ASYNC116` rule ([#18767](https://github.com/astral-sh/ruff/pull/18767))
- \[`flake8-async`\] Mark autofix for `ASYNC115` as unsafe if the call expression contains comments ([#18753](https://github.com/astral-sh/ruff/pull/18753))
- \[`flake8-bugbear`\] Mark autofix for `B004` as unsafe if the `hasattr` call expr contains comments ([#18755](https://github.com/astral-sh/ruff/pull/18755))
- \[`flake8-comprehension`\] Mark autofix for `C420` as unsafe if there's comments inside the dict comprehension ([#18768](https://github.com/astral-sh/ruff/pull/18768))
- \[`flake8-comprehensions`\] Handle template strings for comprehension fixes ([#18710](https://github.com/astral-sh/ruff/pull/18710))
- \[`flake8-future-annotations`\] Add autofix (`FA100`) ([#18903](https://github.com/astral-sh/ruff/pull/18903))
- \[`pyflakes`\] Mark `F504`/`F522`/`F523` autofix as unsafe if there's a call with side effect ([#18839](https://github.com/astral-sh/ruff/pull/18839))
- \[`pylint`\] Allow fix with comments and document performance implications (`PLW3301`) ([#18936](https://github.com/astral-sh/ruff/pull/18936))
- \[`pylint`\] Detect more exotic `NaN` literals in `PLW0177` ([#18630](https://github.com/astral-sh/ruff/pull/18630))
- \[`pylint`\] Fix `PLC1802` autofix creating a syntax error and mark autofix as unsafe if there's comments in the `len` call ([#18836](https://github.com/astral-sh/ruff/pull/18836))
- \[`pyupgrade`\] Extend version detection to include `sys.version_info.major` (`UP036`) ([#18633](https://github.com/astral-sh/ruff/pull/18633))
- \[`ruff`\] Add lint rule `RUF064` for calling `chmod` with non-octal integers ([#18541](https://github.com/astral-sh/ruff/pull/18541))
- \[`ruff`\] Added `cls.__dict__.get('__annotations__')` check (`RUF063`) ([#18233](https://github.com/astral-sh/ruff/pull/18233))
- \[`ruff`\] Frozen `dataclass` default should be valid (`RUF009`) ([#18735](https://github.com/astral-sh/ruff/pull/18735))
### Server
- Consider virtual path for various server actions ([#18910](https://github.com/astral-sh/ruff/pull/18910))
### Documentation
- Add fix safety sections ([#18940](https://github.com/astral-sh/ruff/pull/18940),[#18841](https://github.com/astral-sh/ruff/pull/18841),[#18802](https://github.com/astral-sh/ruff/pull/18802),[#18837](https://github.com/astral-sh/ruff/pull/18837),[#18800](https://github.com/astral-sh/ruff/pull/18800),[#18415](https://github.com/astral-sh/ruff/pull/18415),[#18853](https://github.com/astral-sh/ruff/pull/18853),[#18842](https://github.com/astral-sh/ruff/pull/18842))
- Use updated pre-commit id ([#18718](https://github.com/astral-sh/ruff/pull/18718))
- \[`perflint`\] Small docs improvement to `PERF401` ([#18786](https://github.com/astral-sh/ruff/pull/18786))
- \[`pyupgrade`\]: Use `super()`, not `__super__` in error messages (`UP008`) ([#18743](https://github.com/astral-sh/ruff/pull/18743))
- \[`flake8-pie`\] Small docs fix to `PIE794` ([#18829](https://github.com/astral-sh/ruff/pull/18829))
- \[`flake8-pyi`\] Correct `collections-named-tuple` example to use PascalCase assignment ([#16884](https://github.com/astral-sh/ruff/pull/16884))
- \[`flake8-pie`\] Add note on type checking benefits to `unnecessary-dict-kwargs` (`PIE804`) ([#18666](https://github.com/astral-sh/ruff/pull/18666))
- \[`pycodestyle`\] Clarify PEP 8 relationship to `whitespace-around-operator` rules ([#18870](https://github.com/astral-sh/ruff/pull/18870))
### Other changes
- Disallow newlines in format specifiers of single quoted f- or t-strings ([#18708](https://github.com/astral-sh/ruff/pull/18708))
- \[`flake8-logging`\] Add fix safety section to `LOG002` ([#18840](https://github.com/astral-sh/ruff/pull/18840))
- \[`pyupgrade`\] Add fix safety section to `UP010` ([#18838](https://github.com/astral-sh/ruff/pull/18838))
## 0.12.0
Check out the [blog post](https://astral.sh/blog/ruff-v0.12.0) for a migration
@@ -16,7 +148,7 @@ guide and overview of the changes!
- **New default Python version handling for syntax errors**
Ruff will default to the _latest_ supported Python version (3.13) when
Ruff will default to the *latest* supported Python version (3.13) when
checking for the version-related syntax errors mentioned above to prevent
false positives in projects without a Python version configured. The default
in all other cases, like applying lint rules, is unchanged and remains at the
@@ -71,7 +203,7 @@ The following rules have been stabilized and are no longer in preview:
- [`class-with-mixed-type-vars`](https://docs.astral.sh/ruff/rules/class-with-mixed-type-vars) (`RUF053`)
- [`unnecessary-round`](https://docs.astral.sh/ruff/rules/unnecessary-round) (`RUF057`)
- [`starmap-zip`](https://docs.astral.sh/ruff/rules/starmap-zip) (`RUF058`)
- [`non-pep604-annotation-optional`](https://docs.astral.sh/ruff/rules/non-pep604-annotation-optional) (`UP045`)
- [`non-pep604-annotation-optional`] (`UP045`)
- [`non-pep695-generic-class`](https://docs.astral.sh/ruff/rules/non-pep695-generic-class) (`UP046`)
- [`non-pep695-generic-function`](https://docs.astral.sh/ruff/rules/non-pep695-generic-function) (`UP047`)
- [`private-type-parameter`](https://docs.astral.sh/ruff/rules/private-type-parameter) (`UP049`)

486
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.85"
rust-version = "1.86"
homepage = "https://docs.astral.sh/ruff"
documentation = "https://docs.astral.sh/ruff"
repository = "https://github.com/astral-sh/ruff"
@@ -62,8 +62,8 @@ camino = { version = "1.1.7" }
clap = { version = "4.5.3", features = ["derive"] }
clap_complete_command = { version = "0.6.0" }
clearscreen = { version = "4.0.0" }
divan = { package = "codspeed-divan-compat", version = "2.10.1" }
codspeed-criterion-compat = { version = "2.6.0", default-features = false }
divan = { package = "codspeed-divan-compat", version = "3.0.2" }
codspeed-criterion-compat = { version = "3.0.2", default-features = false }
colored = { version = "3.0.0" }
console_error_panic_hook = { version = "0.1.7" }
console_log = { version = "1.0.0" }
@@ -75,11 +75,16 @@ dashmap = { version = "6.0.1" }
dir-test = { version = "0.4.0" }
dunce = { version = "1.0.5" }
drop_bomb = { version = "0.1.5" }
env_logger = { version = "0.11.0" }
etcetera = { version = "0.10.0" }
fern = { version = "0.7.0" }
filetime = { version = "0.2.23" }
getrandom = { version = "0.3.1" }
get-size2 = { version = "0.5.0", features = [
"derive",
"smallvec",
"hashbrown",
"compact-str"
] }
glob = { version = "0.3.1" }
globset = { version = "0.4.14" }
globwalk = { version = "0.9.1" }
@@ -93,7 +98,7 @@ ignore = { version = "0.4.22" }
imara-diff = { version = "0.1.5" }
imperative = { version = "1.0.4" }
indexmap = { version = "2.6.0" }
indicatif = { version = "0.17.8" }
indicatif = { version = "0.18.0" }
indoc = { version = "2.0.4" }
insta = { version = "1.35.1" }
insta-cmd = { version = "0.6.0" }
@@ -132,7 +137,7 @@ 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 = "09627e450566f894956710a3fd923dc80462ae6d" }
salsa = { git = "https://github.com/salsa-rs/salsa", rev = "fc00eba89e5dcaa5edba51c41aa5f309b5cb126b" }
schemars = { version = "0.8.16" }
seahash = { version = "4.1.0" }
serde = { version = "1.0.197", features = ["derive"] }
@@ -162,7 +167,7 @@ tikv-jemallocator = { version = "0.6.0" }
toml = { version = "0.8.11" }
tracing = { version = "0.1.40" }
tracing-flame = { version = "0.2.0" }
tracing-indicatif = { version = "0.3.6" }
tracing-indicatif = { version = "0.3.11" }
tracing-log = { version = "0.2.0" }
tracing-subscriber = { version = "0.3.18", default-features = false, features = [
"env-filter",
@@ -222,6 +227,7 @@ unnecessary_debug_formatting = "allow" # too many instances, the display also d
# Without the hashes we run into a `rustfmt` bug in some snapshot tests, see #13250
needless_raw_string_hashes = "allow"
# Disallowed restriction lints
ignore_without_reason = "allow" # Too many exsisting instances, and there's no auto fix.
print_stdout = "warn"
print_stderr = "warn"
dbg_macro = "warn"

View File

@@ -148,8 +148,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.12.0/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.12.0/install.ps1 | iex"
curl -LsSf https://astral.sh/ruff/0.12.2/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.12.2/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 +182,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.12.0
rev: v0.12.2
hooks:
# Run the linter.
- id: ruff-check
@@ -423,6 +423,7 @@ Ruff is used by a number of major open-source projects and companies, including:
- [Albumentations](https://github.com/albumentations-team/albumentations)
- Amazon ([AWS SAM](https://github.com/aws/serverless-application-model))
- [Anki](https://apps.ankiweb.net/)
- Anthropic ([Python SDK](https://github.com/anthropics/anthropic-sdk-python))
- [Apache Airflow](https://github.com/apache/airflow)
- AstraZeneca ([Magnus](https://github.com/AstraZeneca/magnus-core))
@@ -505,6 +506,7 @@ Ruff is used by a number of major open-source projects and companies, including:
- [Streamlit](https://github.com/streamlit/streamlit)
- [The Algorithms](https://github.com/TheAlgorithms/Python)
- [Vega-Altair](https://github.com/altair-viz/altair)
- [Weblate](https://weblate.org/)
- WordPress ([Openverse](https://github.com/WordPress/openverse))
- [ZenML](https://github.com/zenml-io/zenml)
- [Zulip](https://github.com/zulip/zulip)

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.12.0"
version = "0.12.2"
publish = true
authors = { workspace = true }
edition = { workspace = true }
@@ -68,6 +68,7 @@ ruff_linter = { workspace = true, features = ["clap", "test-rules"] }
assert_fs = { workspace = true }
# Avoid writing colored snapshots when running tests from the terminal
colored = { workspace = true, features = ["no-color"] }
dunce = { workspace = true }
indoc = { workspace = true }
insta = { workspace = true, features = ["filters", "json"] }
insta-cmd = { workspace = true }

View File

@@ -18,14 +18,15 @@ use rustc_hash::FxHashMap;
use tempfile::NamedTempFile;
use ruff_cache::{CacheKey, CacheKeyHasher};
use ruff_db::diagnostic::Diagnostic;
use ruff_diagnostics::Fix;
use ruff_linter::message::Message;
use ruff_linter::message::create_lint_diagnostic;
use ruff_linter::package::PackageRoot;
use ruff_linter::{VERSION, warn_user};
use ruff_macros::CacheKey;
use ruff_notebook::NotebookIndex;
use ruff_source_file::SourceFileBuilder;
use ruff_text_size::{Ranged, TextRange, TextSize};
use ruff_text_size::{TextRange, TextSize};
use ruff_workspace::Settings;
use ruff_workspace::resolver::Resolver;
@@ -341,16 +342,16 @@ impl FileCache {
/// Convert the file cache into `Diagnostics`, using `path` as file name.
pub(crate) fn to_diagnostics(&self, path: &Path) -> Option<Diagnostics> {
self.data.lint.as_ref().map(|lint| {
let messages = if lint.messages.is_empty() {
let diagnostics = if lint.messages.is_empty() {
Vec::new()
} else {
let file = SourceFileBuilder::new(path.to_string_lossy(), &*lint.source).finish();
lint.messages
.iter()
.map(|msg| {
Message::diagnostic(
msg.body.clone(),
msg.suggestion.clone(),
create_lint_diagnostic(
&msg.body,
msg.suggestion.as_ref(),
msg.range,
msg.fix.clone(),
msg.parent,
@@ -366,7 +367,7 @@ impl FileCache {
} else {
FxHashMap::default()
};
Diagnostics::new(messages, notebook_indexes)
Diagnostics::new(diagnostics, notebook_indexes)
})
}
}
@@ -427,17 +428,17 @@ pub(crate) struct LintCacheData {
}
impl LintCacheData {
pub(crate) fn from_messages(
messages: &[Message],
pub(crate) fn from_diagnostics(
diagnostics: &[Diagnostic],
notebook_index: Option<NotebookIndex>,
) -> Self {
let source = if let Some(msg) = messages.first() {
msg.source_file().source_text().to_owned()
let source = if let Some(msg) = diagnostics.first() {
msg.expect_ruff_source_file().source_text().to_owned()
} else {
String::new() // No messages, no need to keep the source!
};
let messages = messages
let messages = diagnostics
.iter()
// Parse the kebab-case rule name into a `Rule`. This will fail for syntax errors, so
// this also serves to filter them out, but we shouldn't be caching files with syntax
@@ -446,16 +447,16 @@ impl LintCacheData {
.map(|(rule, msg)| {
// Make sure that all message use the same source file.
assert_eq!(
msg.source_file(),
messages.first().unwrap().source_file(),
msg.expect_ruff_source_file(),
diagnostics.first().unwrap().expect_ruff_source_file(),
"message uses a different source file"
);
CacheMessage {
rule,
body: msg.body().to_string(),
suggestion: msg.suggestion().map(ToString::to_string),
range: msg.range(),
parent: msg.parent,
range: msg.expect_range(),
parent: msg.parent(),
fix: msg.fix().cloned(),
noqa_offset: msg.noqa_offset(),
}
@@ -608,12 +609,12 @@ mod tests {
use anyhow::Result;
use filetime::{FileTime, set_file_mtime};
use itertools::Itertools;
use ruff_linter::settings::LinterSettings;
use test_case::test_case;
use ruff_cache::CACHE_DIR_NAME;
use ruff_linter::message::Message;
use ruff_db::diagnostic::Diagnostic;
use ruff_linter::package::PackageRoot;
use ruff_linter::settings::LinterSettings;
use ruff_linter::settings::flags;
use ruff_linter::settings::types::UnsafeFixes;
use ruff_python_ast::{PySourceType, PythonVersion};
@@ -680,7 +681,7 @@ mod tests {
UnsafeFixes::Enabled,
)
.unwrap();
if diagnostics.messages.iter().any(Message::is_syntax_error) {
if diagnostics.inner.iter().any(Diagnostic::is_syntax_error) {
parse_errors.push(path.clone());
}
paths.push(path);

View File

@@ -9,11 +9,10 @@ use ignore::Error;
use log::{debug, error, warn};
#[cfg(not(target_family = "wasm"))]
use rayon::prelude::*;
use ruff_linter::message::diagnostic_from_violation;
use rustc_hash::FxHashMap;
use ruff_db::panic::catch_unwind;
use ruff_linter::OldDiagnostic;
use ruff_linter::message::Message;
use ruff_linter::package::PackageRoot;
use ruff_linter::registry::Rule;
use ruff_linter::settings::types::UnsafeFixes;
@@ -130,9 +129,10 @@ pub(crate) fn check(
SourceFileBuilder::new(path.to_string_lossy().as_ref(), "").finish();
Diagnostics::new(
vec![Message::from_diagnostic(
OldDiagnostic::new(IOError { message }, TextRange::default(), &dummy),
None,
vec![diagnostic_from_violation(
IOError { message },
TextRange::default(),
&dummy,
)],
FxHashMap::default(),
)
@@ -166,7 +166,7 @@ pub(crate) fn check(
|a, b| (a.0 + b.0, a.1 + b.1),
);
all_diagnostics.messages.sort();
all_diagnostics.inner.sort();
// Store the caches.
caches.persist()?;
@@ -283,7 +283,7 @@ mod test {
.with_show_fix_status(true)
.emit(
&mut output,
&diagnostics.messages,
&diagnostics.inner,
&EmitterContext::new(&FxHashMap::default()),
)
.unwrap();

View File

@@ -52,6 +52,6 @@ pub(crate) fn check_stdin(
noqa,
fix_mode,
)?;
diagnostics.messages.sort_unstable();
diagnostics.inner.sort_unstable();
Ok(diagnostics)
}

View File

@@ -10,12 +10,10 @@ use std::path::Path;
use anyhow::{Context, Result};
use colored::Colorize;
use log::{debug, warn};
use rustc_hash::FxHashMap;
use ruff_linter::OldDiagnostic;
use ruff_db::diagnostic::Diagnostic;
use ruff_linter::codes::Rule;
use ruff_linter::linter::{FixTable, FixerResult, LinterResult, ParseSource, lint_fix, lint_only};
use ruff_linter::message::Message;
use ruff_linter::message::{create_syntax_error_diagnostic, diagnostic_from_violation};
use ruff_linter::package::PackageRoot;
use ruff_linter::pyproject_toml::lint_pyproject_toml;
use ruff_linter::settings::types::UnsafeFixes;
@@ -27,23 +25,24 @@ use ruff_python_ast::{PySourceType, SourceType, TomlSourceType};
use ruff_source_file::SourceFileBuilder;
use ruff_text_size::TextRange;
use ruff_workspace::Settings;
use rustc_hash::FxHashMap;
use crate::cache::{Cache, FileCacheKey, LintCacheData};
#[derive(Debug, Default, PartialEq)]
pub(crate) struct Diagnostics {
pub(crate) messages: Vec<Message>,
pub(crate) inner: Vec<Diagnostic>,
pub(crate) fixed: FixMap,
pub(crate) notebook_indexes: FxHashMap<String, NotebookIndex>,
}
impl Diagnostics {
pub(crate) fn new(
messages: Vec<Message>,
diagnostics: Vec<Diagnostic>,
notebook_indexes: FxHashMap<String, NotebookIndex>,
) -> Self {
Self {
messages,
inner: diagnostics,
fixed: FixMap::default(),
notebook_indexes,
}
@@ -63,15 +62,12 @@ impl Diagnostics {
let name = path.map_or_else(|| "-".into(), Path::to_string_lossy);
let source_file = SourceFileBuilder::new(name, "").finish();
Self::new(
vec![Message::from_diagnostic(
OldDiagnostic::new(
IOError {
message: err.to_string(),
},
TextRange::default(),
&source_file,
),
None,
vec![diagnostic_from_violation(
IOError {
message: err.to_string(),
},
TextRange::default(),
&source_file,
)],
FxHashMap::default(),
)
@@ -102,7 +98,11 @@ impl Diagnostics {
let name = path.map_or_else(|| "-".into(), Path::to_string_lossy);
let dummy = SourceFileBuilder::new(name, "").finish();
Self::new(
vec![Message::syntax_error(err, TextRange::default(), dummy)],
vec![create_syntax_error_diagnostic(
dummy,
err,
TextRange::default(),
)],
FxHashMap::default(),
)
}
@@ -121,7 +121,7 @@ impl Add for Diagnostics {
impl AddAssign for Diagnostics {
fn add_assign(&mut self, other: Self) {
self.messages.extend(other.messages);
self.inner.extend(other.inner);
self.fixed += other.fixed;
self.notebook_indexes.extend(other.notebook_indexes);
}
@@ -202,7 +202,7 @@ pub(crate) fn lint_path(
if match fix_mode {
flags::FixMode::Generate => true,
flags::FixMode::Apply | flags::FixMode::Diff => {
diagnostics.messages.is_empty() && diagnostics.fixed.is_empty()
diagnostics.inner.is_empty() && diagnostics.fixed.is_empty()
}
} {
return Ok(diagnostics);
@@ -222,7 +222,7 @@ pub(crate) fn lint_path(
Some(source_type) => source_type,
None => match SourceType::from(path) {
SourceType::Toml(TomlSourceType::Pyproject) => {
let messages = if settings
let diagnostics = if settings
.rules
.iter_enabled()
.any(|rule_code| rule_code.lint_source().is_pyproject_toml())
@@ -240,7 +240,7 @@ pub(crate) fn lint_path(
vec![]
};
return Ok(Diagnostics {
messages,
inner: diagnostics,
..Diagnostics::default()
});
}
@@ -324,7 +324,7 @@ pub(crate) fn lint_path(
};
let has_error = result.has_syntax_errors();
let messages = result.messages;
let diagnostics = result.diagnostics;
if let Some((cache, relative_path, key)) = caching {
// We don't cache parsing errors.
@@ -335,14 +335,14 @@ pub(crate) fn lint_path(
if match fix_mode {
flags::FixMode::Generate => true,
flags::FixMode::Apply | flags::FixMode::Diff => {
messages.is_empty() && fixed.is_empty()
diagnostics.is_empty() && fixed.is_empty()
}
} {
cache.update_lint(
relative_path.to_owned(),
&key,
LintCacheData::from_messages(
&messages,
LintCacheData::from_diagnostics(
&diagnostics,
transformed.as_ipy_notebook().map(Notebook::index).cloned(),
),
);
@@ -357,7 +357,7 @@ pub(crate) fn lint_path(
};
Ok(Diagnostics {
messages,
inner: diagnostics,
fixed: FixMap::from_iter([(fs::relativize_path(path), fixed)]),
notebook_indexes,
})
@@ -396,7 +396,7 @@ pub(crate) fn lint_stdin(
}
return Ok(Diagnostics {
messages: lint_pyproject_toml(&source_file, &settings.linter),
inner: lint_pyproject_toml(&source_file, &settings.linter),
fixed: FixMap::from_iter([(fs::relativize_path(path), FixTable::default())]),
notebook_indexes: FxHashMap::default(),
});
@@ -417,7 +417,7 @@ pub(crate) fn lint_stdin(
};
// Lint the inputs.
let (LinterResult { messages, .. }, transformed, fixed) =
let (LinterResult { diagnostics, .. }, transformed, fixed) =
if matches!(fix_mode, flags::FixMode::Apply | flags::FixMode::Diff) {
if let Ok(FixerResult {
result,
@@ -501,7 +501,7 @@ pub(crate) fn lint_stdin(
};
Ok(Diagnostics {
messages,
inner: diagnostics,
fixed: FixMap::from_iter([(
fs::relativize_path(path.unwrap_or_else(|| Path::new("-"))),
fixed,

View File

@@ -363,7 +363,7 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<Exi
Printer::clear_screen()?;
printer.write_to_user("Starting linter in watch mode...\n");
let messages = commands::check::check(
let diagnostics = commands::check::check(
&files,
&pyproject_config,
&config_arguments,
@@ -372,7 +372,7 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<Exi
fix_mode,
unsafe_fixes,
)?;
printer.write_continuously(&mut writer, &messages, preview)?;
printer.write_continuously(&mut writer, &diagnostics, preview)?;
// In watch mode, we may need to re-resolve the configuration.
// TODO(charlie): Re-compute other derivative values, like the `printer`.
@@ -392,7 +392,7 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<Exi
Printer::clear_screen()?;
printer.write_to_user("File change detected...\n");
let messages = commands::check::check(
let diagnostics = commands::check::check(
&files,
&pyproject_config,
&config_arguments,
@@ -401,7 +401,7 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<Exi
fix_mode,
unsafe_fixes,
)?;
printer.write_continuously(&mut writer, &messages, preview)?;
printer.write_continuously(&mut writer, &diagnostics, preview)?;
}
Err(err) => return Err(err.into()),
}
@@ -463,11 +463,11 @@ pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<Exi
// there are any violations, unless we're explicitly asked to exit zero on
// fix.
if cli.exit_non_zero_on_fix {
if !diagnostics.fixed.is_empty() || !diagnostics.messages.is_empty() {
if !diagnostics.fixed.is_empty() || !diagnostics.inner.is_empty() {
return Ok(ExitStatus::Failure);
}
} else {
if !diagnostics.messages.is_empty() {
if !diagnostics.inner.is_empty() {
return Ok(ExitStatus::Failure);
}
}

View File

@@ -6,16 +6,16 @@ use anyhow::Result;
use bitflags::bitflags;
use colored::Colorize;
use itertools::{Itertools, iterate};
use ruff_linter::codes::NoqaCode;
use ruff_linter::linter::FixTable;
use serde::Serialize;
use ruff_db::diagnostic::{Diagnostic, SecondaryCode};
use ruff_linter::fs::relativize_path;
use ruff_linter::logging::LogLevel;
use ruff_linter::message::{
AzureEmitter, Emitter, EmitterContext, GithubEmitter, GitlabEmitter, GroupedEmitter,
JsonEmitter, JsonLinesEmitter, JunitEmitter, Message, PylintEmitter, RdjsonEmitter,
SarifEmitter, TextEmitter,
JsonEmitter, JsonLinesEmitter, JunitEmitter, PylintEmitter, RdjsonEmitter, SarifEmitter,
TextEmitter,
};
use ruff_linter::notify_user;
use ruff_linter::settings::flags::{self};
@@ -36,8 +36,8 @@ bitflags! {
}
#[derive(Serialize)]
struct ExpandedStatistics {
code: Option<NoqaCode>,
struct ExpandedStatistics<'a> {
code: Option<&'a SecondaryCode>,
name: &'static str,
count: usize,
fixable: bool,
@@ -85,7 +85,7 @@ impl Printer {
.sum::<usize>();
if self.flags.intersects(Flags::SHOW_VIOLATIONS) {
let remaining = diagnostics.messages.len();
let remaining = diagnostics.inner.len();
let total = fixed + remaining;
if fixed > 0 {
let s = if total == 1 { "" } else { "s" };
@@ -229,16 +229,16 @@ impl Printer {
match self.format {
OutputFormat::Json => {
JsonEmitter.emit(writer, &diagnostics.messages, &context)?;
JsonEmitter.emit(writer, &diagnostics.inner, &context)?;
}
OutputFormat::Rdjson => {
RdjsonEmitter.emit(writer, &diagnostics.messages, &context)?;
RdjsonEmitter.emit(writer, &diagnostics.inner, &context)?;
}
OutputFormat::JsonLines => {
JsonLinesEmitter.emit(writer, &diagnostics.messages, &context)?;
JsonLinesEmitter.emit(writer, &diagnostics.inner, &context)?;
}
OutputFormat::Junit => {
JunitEmitter.emit(writer, &diagnostics.messages, &context)?;
JunitEmitter.emit(writer, &diagnostics.inner, &context)?;
}
OutputFormat::Concise | OutputFormat::Full => {
TextEmitter::default()
@@ -246,7 +246,7 @@ impl Printer {
.with_show_fix_diff(self.flags.intersects(Flags::SHOW_FIX_DIFF))
.with_show_source(self.format == OutputFormat::Full)
.with_unsafe_fixes(self.unsafe_fixes)
.emit(writer, &diagnostics.messages, &context)?;
.emit(writer, &diagnostics.inner, &context)?;
if self.flags.intersects(Flags::SHOW_FIX_SUMMARY) {
if !diagnostics.fixed.is_empty() {
@@ -262,7 +262,7 @@ impl Printer {
GroupedEmitter::default()
.with_show_fix_status(show_fix_status(self.fix_mode, fixables.as_ref()))
.with_unsafe_fixes(self.unsafe_fixes)
.emit(writer, &diagnostics.messages, &context)?;
.emit(writer, &diagnostics.inner, &context)?;
if self.flags.intersects(Flags::SHOW_FIX_SUMMARY) {
if !diagnostics.fixed.is_empty() {
@@ -274,19 +274,19 @@ impl Printer {
self.write_summary_text(writer, diagnostics)?;
}
OutputFormat::Github => {
GithubEmitter.emit(writer, &diagnostics.messages, &context)?;
GithubEmitter.emit(writer, &diagnostics.inner, &context)?;
}
OutputFormat::Gitlab => {
GitlabEmitter::default().emit(writer, &diagnostics.messages, &context)?;
GitlabEmitter::default().emit(writer, &diagnostics.inner, &context)?;
}
OutputFormat::Pylint => {
PylintEmitter.emit(writer, &diagnostics.messages, &context)?;
PylintEmitter.emit(writer, &diagnostics.inner, &context)?;
}
OutputFormat::Azure => {
AzureEmitter.emit(writer, &diagnostics.messages, &context)?;
AzureEmitter.emit(writer, &diagnostics.inner, &context)?;
}
OutputFormat::Sarif => {
SarifEmitter.emit(writer, &diagnostics.messages, &context)?;
SarifEmitter.emit(writer, &diagnostics.inner, &context)?;
}
}
@@ -301,13 +301,13 @@ impl Printer {
writer: &mut dyn Write,
) -> Result<()> {
let statistics: Vec<ExpandedStatistics> = diagnostics
.messages
.inner
.iter()
.map(|message| (message.noqa_code(), message))
.map(|message| (message.secondary_code(), message))
.sorted_by_key(|(code, message)| (*code, message.fixable()))
.fold(
vec![],
|mut acc: Vec<((Option<NoqaCode>, &Message), usize)>, (code, message)| {
|mut acc: Vec<((Option<&SecondaryCode>, &Diagnostic), usize)>, (code, message)| {
if let Some(((prev_code, _prev_message), count)) = acc.last_mut() {
if *prev_code == code {
*count += 1;
@@ -349,12 +349,7 @@ impl Printer {
);
let code_width = statistics
.iter()
.map(|statistic| {
statistic
.code
.map_or_else(String::new, |rule| rule.to_string())
.len()
})
.map(|statistic| statistic.code.map_or(0, |s| s.len()))
.max()
.unwrap();
let any_fixable = statistics.iter().any(|statistic| statistic.fixable);
@@ -370,7 +365,8 @@ impl Printer {
statistic.count.to_string().bold(),
statistic
.code
.map_or_else(String::new, |rule| rule.to_string())
.map(SecondaryCode::as_str)
.unwrap_or_default()
.red()
.bold(),
if any_fixable {
@@ -416,20 +412,20 @@ impl Printer {
}
if self.log_level >= LogLevel::Default {
let s = if diagnostics.messages.len() == 1 {
let s = if diagnostics.inner.len() == 1 {
""
} else {
"s"
};
notify_user!(
"Found {} error{s}. Watching for file changes.",
diagnostics.messages.len()
diagnostics.inner.len()
);
}
let fixables = FixableStatistics::try_from(diagnostics, self.unsafe_fixes);
if !diagnostics.messages.is_empty() {
if !diagnostics.inner.is_empty() {
if self.log_level >= LogLevel::Default {
writeln!(writer)?;
}
@@ -439,7 +435,7 @@ impl Printer {
.with_show_fix_status(show_fix_status(self.fix_mode, fixables.as_ref()))
.with_show_source(preview)
.with_unsafe_fixes(self.unsafe_fixes)
.emit(writer, &diagnostics.messages, &context)?;
.emit(writer, &diagnostics.inner, &context)?;
}
writer.flush()?;
@@ -522,7 +518,7 @@ impl FixableStatistics {
let mut applicable = 0;
let mut inapplicable_unsafe = 0;
for message in &diagnostics.messages {
for message in &diagnostics.inner {
if let Some(fix) = message.fix() {
if fix.applies(unsafe_fixes.required_applicability()) {
applicable += 1;

View File

@@ -17,6 +17,7 @@ fn command() -> Command {
command.arg("analyze");
command.arg("graph");
command.arg("--preview");
command.env_clear();
command
}
@@ -565,8 +566,8 @@ fn venv() -> Result<()> {
----- stderr -----
ruff failed
Cause: Invalid search path settings
Cause: Failed to discover the site-packages directory: Invalid `--python` argument `none`: does not point to a Python executable or a directory on disk
Cause: Invalid `--python` argument `none`: does not point to a Python executable or a directory on disk
Cause: No such file or directory (os error 2)
");
});

View File

@@ -1067,7 +1067,7 @@ fn show_statistics_syntax_errors() {
success: false
exit_code: 1
----- stdout -----
1 syntax-error
1 invalid-syntax
Found 1 error.
----- stderr -----
@@ -1080,7 +1080,7 @@ fn show_statistics_syntax_errors() {
success: false
exit_code: 1
----- stdout -----
1 syntax-error
1 invalid-syntax
Found 1 error.
----- stderr -----
@@ -1093,7 +1093,7 @@ fn show_statistics_syntax_errors() {
success: false
exit_code: 1
----- stdout -----
1 syntax-error
1 invalid-syntax
Found 1 error.
----- stderr -----

View File

@@ -612,7 +612,7 @@ fn extend_passed_via_config_argument() {
#[test]
fn nonexistent_extend_file() -> Result<()> {
let tempdir = TempDir::new()?;
let project_dir = tempdir.path().canonicalize()?;
let project_dir = dunce::canonicalize(tempdir.path())?;
fs::write(
project_dir.join("ruff.toml"),
r#"
@@ -653,7 +653,7 @@ extend = "ruff3.toml"
#[test]
fn circular_extend() -> Result<()> {
let tempdir = TempDir::new()?;
let project_path = tempdir.path().canonicalize()?;
let project_path = dunce::canonicalize(tempdir.path())?;
fs::write(
project_path.join("ruff.toml"),
@@ -698,7 +698,7 @@ extend = "ruff.toml"
#[test]
fn parse_error_extends() -> Result<()> {
let tempdir = TempDir::new()?;
let project_path = tempdir.path().canonicalize()?;
let project_path = dunce::canonicalize(tempdir.path())?;
fs::write(
project_path.join("ruff.toml"),
@@ -2130,7 +2130,7 @@ select = ["UP006"]
#[test]
fn requires_python_no_tool() -> Result<()> {
let tempdir = TempDir::new()?;
let project_dir = tempdir.path().canonicalize()?;
let project_dir = dunce::canonicalize(tempdir.path())?;
let ruff_toml = tempdir.path().join("pyproject.toml");
fs::write(
&ruff_toml,
@@ -2441,7 +2441,7 @@ requires-python = ">= 3.11"
#[test]
fn requires_python_no_tool_target_version_override() -> Result<()> {
let tempdir = TempDir::new()?;
let project_dir = tempdir.path().canonicalize()?;
let project_dir = dunce::canonicalize(tempdir.path())?;
let ruff_toml = tempdir.path().join("pyproject.toml");
fs::write(
&ruff_toml,
@@ -2752,7 +2752,7 @@ requires-python = ">= 3.11"
#[test]
fn requires_python_no_tool_with_check() -> Result<()> {
let tempdir = TempDir::new()?;
let project_dir = tempdir.path().canonicalize()?;
let project_dir = dunce::canonicalize(tempdir.path())?;
let ruff_toml = tempdir.path().join("pyproject.toml");
fs::write(
&ruff_toml,
@@ -2797,7 +2797,7 @@ requires-python = ">= 3.11"
#[test]
fn requires_python_ruff_toml_no_target_fallback() -> Result<()> {
let tempdir = TempDir::new()?;
let project_dir = tempdir.path().canonicalize()?;
let project_dir = dunce::canonicalize(tempdir.path())?;
let ruff_toml = tempdir.path().join("ruff.toml");
fs::write(
&ruff_toml,
@@ -3118,7 +3118,7 @@ from typing import Union;foo: Union[int, str] = 1
#[test]
fn requires_python_ruff_toml_no_target_fallback_check() -> Result<()> {
let tempdir = TempDir::new()?;
let project_dir = tempdir.path().canonicalize()?;
let project_dir = dunce::canonicalize(tempdir.path())?;
let ruff_toml = tempdir.path().join("ruff.toml");
fs::write(
&ruff_toml,
@@ -3173,7 +3173,7 @@ from typing import Union;foo: Union[int, str] = 1
#[test]
fn requires_python_pyproject_toml_above() -> Result<()> {
let tempdir = TempDir::new()?;
let project_dir = tempdir.path().canonicalize()?;
let project_dir = dunce::canonicalize(tempdir.path())?;
let outer_pyproject = tempdir.path().join("pyproject.toml");
fs::write(
&outer_pyproject,
@@ -3200,7 +3200,7 @@ from typing import Union;foo: Union[int, str] = 1
"#,
)?;
let testpy_canon = testpy.canonicalize()?;
let testpy_canon = dunce::canonicalize(testpy)?;
insta::with_settings!({
filters => vec![(tempdir_filter(&testpy_canon).as_str(), "[TMP]/foo/test.py"),(tempdir_filter(&project_dir).as_str(), "[TMP]/"),(r"(?m)^foo\\test","foo/test")]
@@ -3499,7 +3499,7 @@ from typing import Union;foo: Union[int, str] = 1
#[test]
fn requires_python_pyproject_toml_above_with_tool() -> Result<()> {
let tempdir = TempDir::new()?;
let project_dir = tempdir.path().canonicalize()?;
let project_dir = dunce::canonicalize(tempdir.path())?;
let outer_pyproject = tempdir.path().join("pyproject.toml");
fs::write(
&outer_pyproject,
@@ -3528,7 +3528,7 @@ from typing import Union;foo: Union[int, str] = 1
"#,
)?;
let testpy_canon = testpy.canonicalize()?;
let testpy_canon = dunce::canonicalize(testpy)?;
insta::with_settings!({
filters => vec![(tempdir_filter(&testpy_canon).as_str(), "[TMP]/foo/test.py"),(tempdir_filter(&project_dir).as_str(), "[TMP]/"),(r"foo\\","foo/")]
@@ -3827,7 +3827,7 @@ from typing import Union;foo: Union[int, str] = 1
#[test]
fn requires_python_ruff_toml_above() -> Result<()> {
let tempdir = TempDir::new()?;
let project_dir = tempdir.path().canonicalize()?;
let project_dir = dunce::canonicalize(tempdir.path())?;
let ruff_toml = tempdir.path().join("ruff.toml");
fs::write(
&ruff_toml,
@@ -3856,7 +3856,7 @@ from typing import Union;foo: Union[int, str] = 1
"#,
)?;
let testpy_canon = testpy.canonicalize()?;
let testpy_canon = dunce::canonicalize(testpy)?;
insta::with_settings!({
filters => vec![(tempdir_filter(&testpy_canon).as_str(), "[TMP]/foo/test.py"),(tempdir_filter(&project_dir).as_str(), "[TMP]/")]
@@ -4441,7 +4441,7 @@ from typing import Union;foo: Union[int, str] = 1
#[test]
fn requires_python_extend_from_shared_config() -> Result<()> {
let tempdir = TempDir::new()?;
let project_dir = tempdir.path().canonicalize()?;
let project_dir = dunce::canonicalize(tempdir.path())?;
let ruff_toml = tempdir.path().join("ruff.toml");
fs::write(
&ruff_toml,
@@ -4479,7 +4479,7 @@ from typing import Union;foo: Union[int, str] = 1
"#,
)?;
let testpy_canon = testpy.canonicalize()?;
let testpy_canon = dunce::canonicalize(testpy)?;
insta::with_settings!({
filters => vec![(tempdir_filter(&testpy_canon).as_str(), "[TMP]/test.py"),(tempdir_filter(&project_dir).as_str(), "[TMP]/")]

View File

@@ -12,10 +12,8 @@ fn display_default_settings() -> anyhow::Result<()> {
// Tempdir path's on macos are symlinks, which doesn't play nicely with
// our snapshot filtering.
let project_dir = tempdir
.path()
.canonicalize()
.context("Failed to canonical tempdir path.")?;
let project_dir =
dunce::canonicalize(tempdir.path()).context("Failed to canonical tempdir path.")?;
std::fs::write(
project_dir.join("pyproject.toml"),

View File

@@ -352,7 +352,7 @@ impl DisplaySet<'_> {
// FIXME: `unicode_width` sometimes disagrees with terminals on how wide a `char`
// is. For now, just accept that sometimes the code line will be longer than
// desired.
let next = unicode_width::UnicodeWidthChar::width(ch).unwrap_or(1);
let next = char_width(ch).unwrap_or(1);
if taken + next > right - left {
was_cut_right = true;
break;
@@ -377,7 +377,7 @@ impl DisplaySet<'_> {
let left: usize = text
.chars()
.take(left)
.map(|ch| unicode_width::UnicodeWidthChar::width(ch).unwrap_or(1))
.map(|ch| char_width(ch).unwrap_or(1))
.sum();
let mut annotations = annotations.clone();
@@ -821,11 +821,7 @@ impl DisplaySourceAnnotation<'_> {
// Length of this annotation as displayed in the stderr output
fn len(&self) -> usize {
// Account for usize underflows
if self.range.1 > self.range.0 {
self.range.1 - self.range.0
} else {
self.range.0 - self.range.1
}
self.range.1.abs_diff(self.range.0)
}
fn takes_space(&self) -> bool {
@@ -1393,6 +1389,7 @@ fn format_body<'m>(
let line_length: usize = line.len();
let line_range = (current_index, current_index + line_length);
let end_line_size = end_line.len();
body.push(DisplayLine::Source {
lineno: Some(current_line),
inline_marks: vec![],
@@ -1452,12 +1449,12 @@ fn format_body<'m>(
let annotation_start_col = line
[0..(start - line_start_index).min(line_length)]
.chars()
.map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
.map(|c| char_width(c).unwrap_or(0))
.sum::<usize>();
let mut annotation_end_col = line
[0..(end - line_start_index).min(line_length)]
.chars()
.map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
.map(|c| char_width(c).unwrap_or(0))
.sum::<usize>();
if annotation_start_col == annotation_end_col {
// At least highlight something
@@ -1499,7 +1496,7 @@ fn format_body<'m>(
let annotation_start_col = line
[0..(start - line_start_index).min(line_length)]
.chars()
.map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
.map(|c| char_width(c).unwrap_or(0))
.sum::<usize>();
let annotation_end_col = annotation_start_col + 1;
@@ -1558,7 +1555,7 @@ fn format_body<'m>(
{
let end_mark = line[0..(end - line_start_index).min(line_length)]
.chars()
.map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0))
.map(|c| char_width(c).unwrap_or(0))
.sum::<usize>()
.saturating_sub(1);
// If the annotation ends on a line-end character, we
@@ -1754,3 +1751,11 @@ fn format_inline_marks(
}
Ok(())
}
fn char_width(c: char) -> Option<usize> {
if c == '\t' {
Some(4)
} else {
unicode_width::UnicodeWidthChar::width(c)
}
}

View File

@@ -0,0 +1,38 @@
<svg width="740px" height="146px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { background: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-red { fill: #FF5555 }
.container {
padding: 0 10px;
line-height: 18px;
}
.bold { font-weight: bold; }
tspan {
font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
white-space: pre;
line-height: 18px;
}
</style>
<rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
<text xml:space="preserve" class="container fg">
<tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">call-non-callable</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> $DIR/main.py:5:9</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">4 |</tspan><tspan> def f():</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan class="fg-bright-blue bold">5 |</tspan><tspan> return (1 == '2')() # Tab indented</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^^^</tspan>
</tspan>
<tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,45 @@
# [crates/ruff_db/src/diagnostic/render.rs:123:47] diag.to_annotate() = Message {
# level: Error,
# id: Some(
# "call-non-callable",
# ),
# title: "Object of type `bool` is not callable",
# snippets: [
# Snippet {
# origin: Some(
# "main.py",
# ),
# line_start: 1,
# source: "def f():\n\treturn (1 == '2')() # Tab indented\n",
# annotations: [
# Annotation {
# range: 17..29,
# label: None,
# level: Error,
# },
# ],
# fold: false,
# },
# ],
# footer: [],
# }
[message]
level = "Error"
id = "E0308"
title = "call-non-callable"
[[message.snippets]]
source = "def f():\n\treturn (1 == '2')() # Tab indented\n"
line_start = 4
origin = "$DIR/main.py"
[[message.snippets.annotations]]
label = ""
level = "Error"
range = [17, 29]
[renderer]
# anonymized_line_numbers = true
color = true

View File

@@ -0,0 +1,36 @@
<svg width="1196px" height="128px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { background: #000000 }
.fg-bright-blue { fill: #5555FF }
.fg-bright-red { fill: #FF5555 }
.container {
padding: 0 10px;
line-height: 18px;
}
.bold { font-weight: bold; }
tspan {
font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;
white-space: pre;
line-height: 18px;
}
</style>
<rect width="100%" height="100%" y="0" rx="4.5" class="bg" />
<text xml:space="preserve" class="container fg">
<tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> $DIR/non-whitespace-trimming.rs:4:6</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
<tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">4 |</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">...</tspan><tspan> s_data['d_dails'] = bb['contacted'][hostip]['ansible_facts']['ansible_devices']['vda']['vendor'] + " " + bb['contacted'][hostip</tspan><tspan class="fg-bright-blue bold">...</tspan>
</tspan>
<tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^^^^</tspan>
</tspan>
<tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,20 @@
[message]
level = "Error"
id = "E0308"
title = "mismatched types"
[[message.snippets]]
source = """
s_data['d_dails'] = bb['contacted'][hostip]['ansible_facts']['ansible_devices']['vda']['vendor'] + " " + bb['contacted'][hostip]['an
"""
line_start = 4
origin = "$DIR/non-whitespace-trimming.rs"
[[message.snippets.annotations]]
label = ""
level = "Error"
range = [5, 11]
[renderer]
# anonymized_line_numbers = true
color = true

View File

@@ -21,7 +21,7 @@
<text xml:space="preserve" class="container fg">
<tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan>
</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> $DIR/non-whitespace-trimming.rs:4:242</tspan>
<tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--&gt;</tspan><tspan> $DIR/non-whitespace-trimming.rs:4:238</tspan>
</tspan>
<tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan>
</tspan>

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -13,12 +13,12 @@ origin = "$DIR/non-whitespace-trimming.rs"
[[message.snippets.annotations]]
label = "expected `()`, found integer"
level = "Error"
range = [241, 243]
range = [237, 239]
[[message.snippets.annotations]]
label = "expected due to this"
level = "Error"
range = [236, 238]
range = [232, 234]
[renderer]

View File

@@ -141,7 +141,7 @@ fn benchmark_incremental(criterion: &mut Criterion) {
&case.file_path,
format!(
"{}\n# A comment\n",
source_text(&case.db, case.file).load(&case.db).as_str()
source_text(&case.db, case.file).as_str()
),
)
.unwrap();
@@ -348,6 +348,99 @@ fn benchmark_many_tuple_assignments(criterion: &mut Criterion) {
});
}
fn benchmark_complex_constrained_attributes_1(criterion: &mut Criterion) {
setup_rayon();
criterion.bench_function("ty_micro[complex_constrained_attributes_1]", |b| {
b.iter_batched_ref(
|| {
// This is a regression benchmark for https://github.com/astral-sh/ty/issues/627.
// Before this was fixed, the following sample would take >1s to type check.
setup_micro_case(
r#"
class C:
def f(self: "C"):
if isinstance(self.a, str):
return
if isinstance(self.b, str):
return
if isinstance(self.b, str):
return
if isinstance(self.b, str):
return
if isinstance(self.b, str):
return
if isinstance(self.b, str):
return
if isinstance(self.b, str):
return
if isinstance(self.b, str):
return
if isinstance(self.b, str):
return
if isinstance(self.b, str):
return
if isinstance(self.b, str):
return
if isinstance(self.b, str):
return
if isinstance(self.b, str):
return
"#,
)
},
|case| {
let Case { db, .. } = case;
let result = db.check();
assert!(!result.is_empty());
},
BatchSize::SmallInput,
);
});
}
fn benchmark_complex_constrained_attributes_2(criterion: &mut Criterion) {
setup_rayon();
criterion.bench_function("ty_micro[complex_constrained_attributes_2]", |b| {
b.iter_batched_ref(
|| {
// This is is similar to the case above, but now the attributes are actually defined.
// https://github.com/astral-sh/ty/issues/711
setup_micro_case(
r#"
class C:
def f(self: "C"):
self.a = ""
self.b = ""
if isinstance(self.a, str):
return
if isinstance(self.b, str):
return
if isinstance(self.b, str):
return
if isinstance(self.b, str):
return
if isinstance(self.b, str):
return
if isinstance(self.b, str):
return
"#,
)
},
|case| {
let Case { db, .. } = case;
let result = db.check();
assert_eq!(result.len(), 0);
},
BatchSize::SmallInput,
);
});
}
struct ProjectBenchmark<'a> {
project: InstalledProject<'a>,
fs: MemoryFileSystem,
@@ -377,8 +470,7 @@ impl<'a> ProjectBenchmark<'a> {
metadata.apply_options(Options {
environment: Some(EnvironmentOptions {
python_version: Some(RangedValue::cli(self.project.config.python_version)),
python: (!self.project.config().dependencies.is_empty())
.then_some(RelativePathBuf::cli(SystemPath::new(".venv"))),
python: Some(RelativePathBuf::cli(SystemPath::new(".venv"))),
..EnvironmentOptions::default()
}),
..Options::default()
@@ -406,11 +498,8 @@ fn bench_project(benchmark: &ProjectBenchmark, criterion: &mut Criterion) {
let diagnostics = result.len();
assert!(
diagnostics > 1 && diagnostics <= max_diagnostics,
"Expected between {} and {} diagnostics but got {}",
1,
max_diagnostics,
diagnostics
diagnostics <= max_diagnostics,
"Expected <={max_diagnostics} diagnostics but got {diagnostics}"
);
}
@@ -478,11 +567,30 @@ fn anyio(criterion: &mut Criterion) {
bench_project(&benchmark, criterion);
}
fn datetype(criterion: &mut Criterion) {
let benchmark = ProjectBenchmark::new(
RealWorldProject {
name: "DateType",
repository: "https://github.com/glyph/DateType",
commit: "57c9c93cf2468069f72945fc04bf27b64100dad8",
paths: vec![SystemPath::new("src")],
dependencies: vec![],
max_dep_date: "2025-07-04",
python_version: PythonVersion::PY313,
},
0,
);
bench_project(&benchmark, criterion);
}
criterion_group!(check_file, benchmark_cold, benchmark_incremental);
criterion_group!(
micro,
benchmark_many_string_assignments,
benchmark_many_tuple_assignments,
benchmark_complex_constrained_attributes_1,
benchmark_complex_constrained_attributes_2,
);
criterion_group!(project, anyio, attrs, hydra);
criterion_group!(project, anyio, attrs, hydra, datetype);
criterion_main!(check_file, micro, project);

View File

@@ -36,8 +36,7 @@ impl<'a> Benchmark<'a> {
metadata.apply_options(Options {
environment: Some(EnvironmentOptions {
python_version: Some(RangedValue::cli(self.project.config.python_version)),
python: (!self.project.config().dependencies.is_empty())
.then_some(RelativePathBuf::cli(SystemPath::new(".venv"))),
python: Some(RelativePathBuf::cli(SystemPath::new(".venv"))),
..EnvironmentOptions::default()
}),
..Options::default()
@@ -204,8 +203,23 @@ static SYMPY: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::new
)
});
#[bench(args=[&*ALTAIR, &*FREQTRADE, &*PYDANTIC], sample_size=2, sample_count=3)]
fn small(bencher: Bencher, benchmark: &Benchmark) {
static TANJUN: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLock::new(|| {
Benchmark::new(
RealWorldProject {
name: "tanjun",
repository: "https://github.com/FasterSpeeding/Tanjun",
commit: "69f40db188196bc59516b6c69849c2d85fbc2f4a",
paths: vec![SystemPath::new("tanjun")],
dependencies: vec!["hikari", "alluka"],
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
100,
)
});
#[track_caller]
fn run_single_threaded(bencher: Bencher, benchmark: &Benchmark) {
bencher
.with_inputs(|| benchmark.setup_iteration())
.bench_local_refs(|db| {
@@ -213,41 +227,55 @@ fn small(bencher: Bencher, benchmark: &Benchmark) {
});
}
#[bench(args=[&*ALTAIR, &*FREQTRADE, &*PYDANTIC, &*TANJUN], sample_size=2, sample_count=3)]
fn small(bencher: Bencher, benchmark: &Benchmark) {
run_single_threaded(bencher, benchmark);
}
#[bench(args=[&*COLOUR_SCIENCE, &*PANDAS], sample_size=1, sample_count=3)]
fn medium(bencher: Bencher, benchmark: &Benchmark) {
bencher
.with_inputs(|| benchmark.setup_iteration())
.bench_local_refs(|db| {
check_project(db, benchmark.max_diagnostics);
});
run_single_threaded(bencher, benchmark);
}
#[bench(args=[&*SYMPY], sample_size=1, sample_count=2)]
fn large(bencher: Bencher, benchmark: &Benchmark) {
run_single_threaded(bencher, benchmark);
}
#[bench(args=[&*PYDANTIC], sample_size=3, sample_count=8)]
fn multithreaded(bencher: Bencher, benchmark: &Benchmark) {
let thread_pool = ThreadPoolBuilder::new().build().unwrap();
bencher
.with_inputs(|| benchmark.setup_iteration())
.bench_local_refs(|db| {
check_project(db, benchmark.max_diagnostics);
.bench_local_values(|db| {
thread_pool.install(|| {
check_project(&db, benchmark.max_diagnostics);
db
})
});
}
fn main() {
let filter =
std::env::var("TY_LOG").unwrap_or("ty_walltime=info,ruff_benchmark=info".to_string());
let _logging = setup_logging_with_filter(&filter).expect("Filter to be valid");
// Disable multithreading for now due to
// https://github.com/salsa-rs/salsa/issues/918.
//
// Salsa has a fast-path for the first db when looking up ingredients.
// It seems that this fast-path becomes extremely slow for all db's other
// than the first one, especially when using multithreading (10x slower than the first run).
ThreadPoolBuilder::new()
.num_threads(1)
.use_current_thread()
.build_global()
.unwrap();
let filter =
std::env::var("TY_LOG").unwrap_or("ty_walltime=info,ruff_benchmark=info".to_string());
let _logging = setup_logging_with_filter(&filter).expect("Filter to be valid");
// Salsa uses an optimized lookup for the ingredient index when using only a single database.
// This optimization results in at least a 10% speedup compared to when using multiple databases.
// To reduce noise, run one benchmark so that all benchmarks take the less optimized "not the first db"
// branch when looking up the ingredient index.
{
let db = TANJUN.setup_iteration();
check_project(&db, TANJUN.max_diagnostics);
}
divan::main();
}

View File

@@ -74,19 +74,17 @@ impl<'a> RealWorldProject<'a> {
};
// Install dependencies if specified
if !checkout.project().dependencies.is_empty() {
tracing::debug!(
"Installing {} dependencies for project '{}'...",
checkout.project().dependencies.len(),
checkout.project().name
);
let start = std::time::Instant::now();
install_dependencies(&checkout)?;
tracing::debug!(
"Dependency installation completed in {:.2}s",
start.elapsed().as_secs_f64()
);
}
tracing::debug!(
"Installing {} dependencies for project '{}'...",
checkout.project().dependencies.len(),
checkout.project().name
);
let start_install = std::time::Instant::now();
install_dependencies(&checkout)?;
tracing::debug!(
"Dependency installation completed in {:.2}s",
start_install.elapsed().as_secs_f64()
);
tracing::debug!("Project setup took: {:.2}s", start.elapsed().as_secs_f64());
@@ -281,6 +279,14 @@ fn install_dependencies(checkout: &Checkout) -> Result<()> {
String::from_utf8_lossy(&output.stderr)
);
if checkout.project().dependencies.is_empty() {
tracing::debug!(
"No dependencies to install for project '{}'",
checkout.project().name
);
return Ok(());
}
// Install dependencies with date constraint in the isolated environment
let mut cmd = Command::new("uv");
cmd.args([

View File

@@ -13,11 +13,12 @@ license = { workspace = true }
[dependencies]
ruff_annotate_snippets = { workspace = true }
ruff_cache = { workspace = true, optional = true }
ruff_diagnostics = { workspace = true }
ruff_notebook = { workspace = true }
ruff_python_ast = { workspace = true }
ruff_python_ast = { workspace = true, features = ["get-size"] }
ruff_python_parser = { workspace = true }
ruff_python_trivia = { workspace = true }
ruff_source_file = { workspace = true }
ruff_source_file = { workspace = true, features = ["get-size"] }
ruff_text_size = { workspace = true }
anstyle = { workspace = true }
@@ -27,17 +28,18 @@ countme = { workspace = true }
dashmap = { workspace = true }
dunce = { workspace = true }
filetime = { workspace = true }
get-size2 = { workspace = true }
glob = { workspace = true }
ignore = { workspace = true, optional = true }
matchit = { workspace = true }
path-slash = { workspace = true }
rustc-hash = { workspace = true }
salsa = { workspace = true }
schemars = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
path-slash = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true, optional = true }
rustc-hash = { workspace = true }
zip = { workspace = true }
[target.'cfg(target_arch="wasm32")'.dependencies]

View File

@@ -1,10 +1,11 @@
use std::{fmt::Formatter, sync::Arc};
use render::{FileResolver, Input};
use ruff_source_file::{SourceCode, SourceFile};
use ruff_diagnostics::Fix;
use ruff_source_file::{LineColumn, SourceCode, SourceFile};
use ruff_annotate_snippets::Level as AnnotateLevel;
use ruff_text_size::{Ranged, TextRange};
use ruff_text_size::{Ranged, TextRange, TextSize};
pub use self::render::DisplayDiagnostic;
use crate::{Db, files::File};
@@ -19,7 +20,7 @@ mod stylesheet;
/// characteristics in the inputs given to the tool. Typically, but not always,
/// a characteristic is a deficiency. An example of a characteristic that is
/// _not_ a deficiency is the `reveal_type` diagnostic for our type checker.
#[derive(Debug, Clone, Eq, PartialEq)]
#[derive(Debug, Clone, Eq, PartialEq, get_size2::GetSize)]
pub struct Diagnostic {
/// The actual diagnostic.
///
@@ -62,10 +63,37 @@ impl Diagnostic {
message: message.into_diagnostic_message(),
annotations: vec![],
subs: vec![],
fix: None,
parent: None,
noqa_offset: None,
secondary_code: None,
});
Diagnostic { inner }
}
/// Creates a `Diagnostic` for a syntax error.
///
/// Unlike the more general [`Diagnostic::new`], this requires a [`Span`] and a [`TextRange`]
/// attached to it.
///
/// This should _probably_ be a method on the syntax errors, but
/// at time of writing, `ruff_db` depends on `ruff_python_parser` instead of
/// the other way around. And since we want to do this conversion in a couple
/// places, it makes sense to centralize it _somewhere_. So it's here for now.
///
/// Note that `message` is stored in the primary annotation, _not_ in the primary diagnostic
/// message.
pub fn syntax_error(
span: impl Into<Span>,
message: impl IntoDiagnosticMessage,
range: impl Ranged,
) -> Diagnostic {
let mut diag = Diagnostic::new(DiagnosticId::InvalidSyntax, Severity::Error, "");
let span = span.into().with_range(range.range());
diag.annotate(Annotation::primary(span).message(message));
diag
}
/// Add an annotation to this diagnostic.
///
/// Annotations for a diagnostic are optional, but if any are added,
@@ -226,6 +254,11 @@ impl Diagnostic {
self.primary_annotation().map(|ann| ann.span.clone())
}
/// Returns a reference to the primary span of this diagnostic.
pub fn primary_span_ref(&self) -> Option<&Span> {
self.primary_annotation().map(|ann| &ann.span)
}
/// Returns the tags from the primary annotation of this diagnostic if it exists.
pub fn primary_tags(&self) -> Option<&[DiagnosticTag]> {
self.primary_annotation().map(|ann| ann.tags.as_slice())
@@ -268,15 +301,180 @@ impl Diagnostic {
pub fn sub_diagnostics(&self) -> &[SubDiagnostic] {
&self.inner.subs
}
/// Returns the fix for this diagnostic if it exists.
pub fn fix(&self) -> Option<&Fix> {
self.inner.fix.as_ref()
}
/// Set the fix for this diagnostic.
pub fn set_fix(&mut self, fix: Fix) {
Arc::make_mut(&mut self.inner).fix = Some(fix);
}
/// Remove the fix for this diagnostic.
pub fn remove_fix(&mut self) {
Arc::make_mut(&mut self.inner).fix = None;
}
/// Returns `true` if the diagnostic contains a [`Fix`].
pub fn fixable(&self) -> bool {
self.fix().is_some()
}
/// Returns the offset of the parent statement for this diagnostic if it exists.
///
/// This is primarily used for checking noqa/secondary code suppressions.
pub fn parent(&self) -> Option<TextSize> {
self.inner.parent
}
/// Set the offset of the diagnostic's parent statement.
pub fn set_parent(&mut self, parent: TextSize) {
Arc::make_mut(&mut self.inner).parent = Some(parent);
}
/// Returns the remapped offset for a suppression comment if it exists.
///
/// Like [`Diagnostic::parent`], this is used for noqa code suppression comments in Ruff.
pub fn noqa_offset(&self) -> Option<TextSize> {
self.inner.noqa_offset
}
/// Set the remapped offset for a suppression comment.
pub fn set_noqa_offset(&mut self, noqa_offset: TextSize) {
Arc::make_mut(&mut self.inner).noqa_offset = Some(noqa_offset);
}
/// Returns the secondary code for the diagnostic if it exists.
///
/// The "primary" code for the diagnostic is its lint name. Diagnostics in ty don't have
/// secondary codes (yet), but in Ruff the noqa code is used.
pub fn secondary_code(&self) -> Option<&SecondaryCode> {
self.inner.secondary_code.as_ref()
}
/// Set the secondary code for this diagnostic.
pub fn set_secondary_code(&mut self, code: SecondaryCode) {
Arc::make_mut(&mut self.inner).secondary_code = Some(code);
}
/// Returns the name used to represent the diagnostic.
pub fn name(&self) -> &'static str {
self.id().as_str()
}
/// Returns `true` if `self` is a syntax error message.
pub fn is_syntax_error(&self) -> bool {
self.id().is_invalid_syntax()
}
/// Returns the message body to display to the user.
pub fn body(&self) -> &str {
self.primary_message()
}
/// Returns the fix suggestion for the violation.
pub fn suggestion(&self) -> Option<&str> {
self.primary_annotation()?.get_message()
}
/// Returns the URL for the rule documentation, if it exists.
pub fn to_url(&self) -> Option<String> {
if self.is_syntax_error() {
None
} else {
Some(format!(
"{}/rules/{}",
env!("CARGO_PKG_HOMEPAGE"),
self.name()
))
}
}
/// Returns the filename for the message.
///
/// Panics if the diagnostic has no primary span, or if its file is not a `SourceFile`.
pub fn expect_ruff_filename(&self) -> String {
self.expect_primary_span()
.expect_ruff_file()
.name()
.to_string()
}
/// Computes the start source location for the message.
///
/// Panics if the diagnostic has no primary span, if its file is not a `SourceFile`, or if the
/// span has no range.
pub fn expect_ruff_start_location(&self) -> LineColumn {
self.expect_primary_span()
.expect_ruff_file()
.to_source_code()
.line_column(self.expect_range().start())
}
/// Computes the end source location for the message.
///
/// Panics if the diagnostic has no primary span, if its file is not a `SourceFile`, or if the
/// span has no range.
pub fn expect_ruff_end_location(&self) -> LineColumn {
self.expect_primary_span()
.expect_ruff_file()
.to_source_code()
.line_column(self.expect_range().end())
}
/// Returns the [`SourceFile`] which the message belongs to.
pub fn ruff_source_file(&self) -> Option<&SourceFile> {
self.primary_span_ref()?.as_ruff_file()
}
/// Returns the [`SourceFile`] which the message belongs to.
///
/// Panics if the diagnostic has no primary span, or if its file is not a `SourceFile`.
pub fn expect_ruff_source_file(&self) -> SourceFile {
self.expect_primary_span().expect_ruff_file().clone()
}
/// Returns the [`TextRange`] for the diagnostic.
pub fn range(&self) -> Option<TextRange> {
self.primary_span()?.range()
}
/// Returns the [`TextRange`] for the diagnostic.
///
/// Panics if the diagnostic has no primary span or if the span has no range.
pub fn expect_range(&self) -> TextRange {
self.range().expect("Expected a range for the primary span")
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
impl Ord for Diagnostic {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.partial_cmp(other).unwrap_or(std::cmp::Ordering::Equal)
}
}
impl PartialOrd for Diagnostic {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(
(self.ruff_source_file()?, self.range()?.start())
.cmp(&(other.ruff_source_file()?, other.range()?.start())),
)
}
}
#[derive(Debug, Clone, Eq, PartialEq, get_size2::GetSize)]
struct DiagnosticInner {
id: DiagnosticId,
severity: Severity,
message: DiagnosticMessage,
annotations: Vec<Annotation>,
subs: Vec<SubDiagnostic>,
fix: Option<Fix>,
parent: Option<TextSize>,
noqa_offset: Option<TextSize>,
secondary_code: Option<SecondaryCode>,
}
struct RenderingSortKey<'a> {
@@ -342,7 +540,7 @@ impl Eq for RenderingSortKey<'_> {}
/// Currently, the order in which sub-diagnostics are rendered relative to one
/// another (for a single parent diagnostic) is the order in which they were
/// attached to the diagnostic.
#[derive(Debug, Clone, Eq, PartialEq)]
#[derive(Debug, Clone, Eq, PartialEq, get_size2::GetSize)]
pub struct SubDiagnostic {
/// Like with `Diagnostic`, we box the `SubDiagnostic` to make it
/// pointer-sized.
@@ -443,7 +641,7 @@ impl SubDiagnostic {
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[derive(Debug, Clone, Eq, PartialEq, get_size2::GetSize)]
struct SubDiagnosticInner {
severity: Severity,
message: DiagnosticMessage,
@@ -471,7 +669,7 @@ struct SubDiagnosticInner {
///
/// Messages attached to annotations should also be as brief and specific as
/// possible. Long messages could negative impact the quality of rendering.
#[derive(Debug, Clone, Eq, PartialEq)]
#[derive(Debug, Clone, Eq, PartialEq, get_size2::GetSize)]
pub struct Annotation {
/// The span of this annotation, corresponding to some subsequence of the
/// user's input that we want to highlight.
@@ -591,7 +789,7 @@ impl Annotation {
///
/// These tags are used to provide additional information about the annotation.
/// and are passed through to the language server protocol.
#[derive(Debug, Clone, Eq, PartialEq)]
#[derive(Debug, Clone, Eq, PartialEq, get_size2::GetSize)]
pub enum DiagnosticTag {
/// Unused or unnecessary code. Used for unused parameters, unreachable code, etc.
Unnecessary,
@@ -605,7 +803,7 @@ pub enum DiagnosticTag {
/// be in kebab case, e.g. `no-foo` (all lower case).
///
/// Rules use kebab case, e.g. `no-foo`.
#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, get_size2::GetSize)]
pub struct LintName(&'static str);
impl LintName {
@@ -645,7 +843,7 @@ impl PartialEq<&str> for LintName {
}
/// Uniquely identifies the kind of a diagnostic.
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, get_size2::GetSize)]
pub enum DiagnosticId {
Panic,
@@ -735,6 +933,9 @@ pub enum DiagnosticId {
/// # no `[overrides.rules]`
/// ```
UselessOverridesSection,
/// Use of a deprecated setting.
DeprecatedSetting,
}
impl DiagnosticId {
@@ -773,6 +974,7 @@ impl DiagnosticId {
DiagnosticId::EmptyInclude => "empty-include",
DiagnosticId::UnnecessaryOverridesSection => "unnecessary-overrides-section",
DiagnosticId::UselessOverridesSection => "useless-overrides-section",
DiagnosticId::DeprecatedSetting => "deprecated-setting",
}
}
@@ -796,7 +998,7 @@ impl std::fmt::Display for DiagnosticId {
///
/// This enum presents a unified interface to these two types for the sake of creating [`Span`]s and
/// emitting diagnostics from both ty and ruff.
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, get_size2::GetSize)]
pub enum UnifiedFile {
Ty(File),
Ruff(SourceFile),
@@ -848,7 +1050,7 @@ impl DiagnosticSource {
/// It consists of a `File` and an optional range into that file. When the
/// range isn't present, it semantically implies that the diagnostic refers to
/// the entire file. For example, when the file should be executable but isn't.
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq, get_size2::GetSize)]
pub struct Span {
file: UnifiedFile,
range: Option<TextRange>,
@@ -893,9 +1095,15 @@ impl Span {
///
/// Panics if the file is a [`UnifiedFile::Ty`] instead of a [`UnifiedFile::Ruff`].
pub fn expect_ruff_file(&self) -> &SourceFile {
self.as_ruff_file()
.expect("Expected a ruff `SourceFile`, found a ty `File`")
}
/// Returns the [`SourceFile`] attached to this [`Span`].
pub fn as_ruff_file(&self) -> Option<&SourceFile> {
match &self.file {
UnifiedFile::Ty(_) => panic!("Expected a ruff `SourceFile`, found a ty `File`"),
UnifiedFile::Ruff(file) => file,
UnifiedFile::Ty(_) => None,
UnifiedFile::Ruff(file) => Some(file),
}
}
}
@@ -920,7 +1128,7 @@ impl From<crate::files::FileRange> for Span {
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, get_size2::GetSize)]
pub enum Severity {
Info,
Warning,
@@ -1083,7 +1291,7 @@ impl std::fmt::Display for ConciseMessage<'_> {
/// In most cases, callers shouldn't need to use this. Instead, there is
/// a blanket trait implementation for `IntoDiagnosticMessage` for
/// anything that implements `std::fmt::Display`.
#[derive(Clone, Debug, Eq, PartialEq)]
#[derive(Clone, Debug, Eq, PartialEq, get_size2::GetSize)]
pub struct DiagnosticMessage(Box<str>);
impl DiagnosticMessage {
@@ -1143,41 +1351,52 @@ impl<T: std::fmt::Display> IntoDiagnosticMessage for T {
}
}
/// Creates a `Diagnostic` from a parse error.
/// A secondary identifier for a lint diagnostic.
///
/// This should _probably_ be a method on `ruff_python_parser::ParseError`, but
/// at time of writing, `ruff_db` depends on `ruff_python_parser` instead of
/// the other way around. And since we want to do this conversion in a couple
/// places, it makes sense to centralize it _somewhere_. So it's here for now.
pub fn create_parse_diagnostic(file: File, err: &ruff_python_parser::ParseError) -> Diagnostic {
let mut diag = Diagnostic::new(DiagnosticId::InvalidSyntax, Severity::Error, "");
let span = Span::from(file).with_range(err.location);
diag.annotate(Annotation::primary(span).message(&err.error));
diag
/// For Ruff rules this means the noqa code.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(transparent))]
pub struct SecondaryCode(String);
impl SecondaryCode {
pub fn new(code: String) -> Self {
Self(code)
}
pub fn as_str(&self) -> &str {
&self.0
}
}
/// Creates a `Diagnostic` from an unsupported syntax error.
///
/// See [`create_parse_diagnostic`] for more details.
pub fn create_unsupported_syntax_diagnostic(
file: File,
err: &ruff_python_parser::UnsupportedSyntaxError,
) -> Diagnostic {
let mut diag = Diagnostic::new(DiagnosticId::InvalidSyntax, Severity::Error, "");
let span = Span::from(file).with_range(err.range);
diag.annotate(Annotation::primary(span).message(err.to_string()));
diag
impl std::fmt::Display for SecondaryCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
/// Creates a `Diagnostic` from a semantic syntax error.
///
/// See [`create_parse_diagnostic`] for more details.
pub fn create_semantic_syntax_diagnostic(
file: File,
err: &ruff_python_parser::semantic_errors::SemanticSyntaxError,
) -> Diagnostic {
let mut diag = Diagnostic::new(DiagnosticId::InvalidSyntax, Severity::Error, "");
let span = Span::from(file).with_range(err.range);
diag.annotate(Annotation::primary(span).message(err.to_string()));
diag
impl std::ops::Deref for SecondaryCode {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl PartialEq<&str> for SecondaryCode {
fn eq(&self, other: &&str) -> bool {
self.0 == *other
}
}
impl PartialEq<SecondaryCode> for &str {
fn eq(&self, other: &SecondaryCode) -> bool {
other.eq(self)
}
}
// for `hashbrown::EntryRef`
impl From<&SecondaryCode> for SecondaryCode {
fn from(value: &SecondaryCode) -> Self {
value.clone()
}
}

View File

@@ -8,11 +8,10 @@ use ruff_source_file::{LineIndex, OneIndexed, SourceCode};
use ruff_text_size::{TextRange, TextSize};
use crate::diagnostic::stylesheet::{DiagnosticStylesheet, fmt_styled};
use crate::source::SourceTextRef;
use crate::{
Db,
files::File,
source::{line_index, source_text},
source::{SourceText, line_index, source_text},
system::SystemPath,
};
@@ -638,6 +637,22 @@ pub trait FileResolver {
fn input(&self, file: File) -> Input;
}
impl<T> FileResolver for T
where
T: Db,
{
fn path(&self, file: File) -> &str {
relativize_path(self.system().current_directory(), file.path(self).as_str())
}
fn input(&self, file: File) -> Input {
Input {
text: source_text(self, file),
line_index: line_index(self, file),
}
}
}
impl FileResolver for &dyn Db {
fn path(&self, file: File) -> &str {
relativize_path(self.system().current_directory(), file.path(*self).as_str())
@@ -645,7 +660,7 @@ impl FileResolver for &dyn Db {
fn input(&self, file: File) -> Input {
Input {
text: source_text(*self, file).load(*self),
text: source_text(*self, file),
line_index: line_index(*self, file),
}
}
@@ -658,7 +673,7 @@ impl FileResolver for &dyn Db {
/// line index for efficiently querying its contents.
#[derive(Clone, Debug)]
pub struct Input {
pub(crate) text: SourceTextRef,
pub(crate) text: SourceText,
pub(crate) line_index: LineIndex,
}
@@ -709,7 +724,6 @@ fn relativize_path<'p>(cwd: &SystemPath, path: &'p str) -> &'p str {
#[cfg(test)]
mod tests {
use crate::Upcast;
use crate::diagnostic::{Annotation, DiagnosticId, Severity, Span};
use crate::files::system_path_to_file;
use crate::system::{DbWithWritableSystem, SystemPath};
@@ -2159,7 +2173,7 @@ watermelon
let span = self.path(path);
let file = span.expect_ty_file();
let text = source_text(&self.db, file).load(&self.db);
let text = source_text(&self.db, file);
let line_index = line_index(&self.db, file);
let source = SourceCode::new(text.as_str(), &line_index);
@@ -2222,7 +2236,7 @@ watermelon
///
/// (This will set the "printed" flag on `Diagnostic`.)
fn render(&self, diag: &Diagnostic) -> String {
diag.display(&self.db.upcast(), &self.config).to_string()
diag.display(&self.db, &self.config).to_string()
}
}

View File

@@ -263,12 +263,23 @@ impl Files {
impl fmt::Debug for Files {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut map = f.debug_map();
if f.alternate() {
let mut map = f.debug_map();
for entry in self.inner.system_by_path.iter() {
map.entry(entry.key(), entry.value());
for entry in self.inner.system_by_path.iter() {
map.entry(entry.key(), entry.value());
}
map.finish()
} else {
f.debug_struct("Files")
.field("system_by_path", &self.inner.system_by_path.len())
.field(
"system_virtual_by_path",
&self.inner.system_virtual_by_path.len(),
)
.field("vendored_by_path", &self.inner.vendored_by_path.len())
.finish()
}
map.finish()
}
}
@@ -308,6 +319,9 @@ pub struct File {
count: Count<File>,
}
// The Salsa heap is tracked separately.
impl get_size2::GetSize for File {}
impl File {
/// Reads the content of the file into a [`String`].
///

View File

@@ -36,12 +36,6 @@ pub trait Db: salsa::Database {
fn python_version(&self) -> PythonVersion;
}
/// Trait for upcasting a reference to a base trait object.
pub trait Upcast<T: ?Sized> {
fn upcast(&self) -> &T;
fn upcast_mut(&mut self) -> &mut T;
}
/// Returns the maximum number of tasks that ty is allowed
/// to process in parallel.
///
@@ -76,11 +70,11 @@ pub trait RustDoc {
mod tests {
use std::sync::{Arc, Mutex};
use crate::Db;
use crate::files::Files;
use crate::system::TestSystem;
use crate::system::{DbWithTestSystem, System};
use crate::vendored::VendoredFileSystem;
use crate::{Db, Upcast};
type Events = Arc<Mutex<Vec<salsa::Event>>>;
@@ -153,15 +147,6 @@ mod tests {
}
}
impl Upcast<dyn Db> for TestDb {
fn upcast(&self) -> &(dyn Db + 'static) {
self
}
fn upcast_mut(&mut self) -> &mut (dyn Db + 'static) {
self
}
}
impl DbWithTestSystem for TestDb {
fn test_system(&self) -> &TestSystem {
&self.system

View File

@@ -2,6 +2,7 @@ use std::fmt::Formatter;
use std::sync::Arc;
use arc_swap::ArcSwapOption;
use get_size2::GetSize;
use ruff_python_ast::{AnyRootNodeRef, ModModule, NodeIndex};
use ruff_python_parser::{ParseOptions, Parsed, parse_unchecked};
@@ -20,7 +21,7 @@ use crate::source::source_text;
/// reflected in the changed AST offsets.
/// The other reason is that Ruff's AST doesn't implement `Eq` which Salsa requires
/// for determining if a query result is unchanged.
#[salsa::tracked(returns(ref), no_eq)]
#[salsa::tracked(returns(ref), no_eq, heap_size=get_size2::GetSize::get_heap_size)]
pub fn parsed_module(db: &dyn Db, file: File) -> ParsedModule {
let _span = tracing::trace_span!("parsed_module", ?file).entered();
@@ -30,7 +31,7 @@ pub fn parsed_module(db: &dyn Db, file: File) -> ParsedModule {
}
pub fn parsed_module_impl(db: &dyn Db, file: File) -> Parsed<ModModule> {
let source = source_text(db, file).load(db);
let source = source_text(db, file);
let ty = file.source_type(db);
let target_version = db.python_version();
@@ -44,9 +45,10 @@ pub fn parsed_module_impl(db: &dyn Db, file: File) -> Parsed<ModModule> {
///
/// This type manages instances of the module AST. A particular instance of the AST
/// is represented with the [`ParsedModuleRef`] type.
#[derive(Clone)]
#[derive(Clone, get_size2::GetSize)]
pub struct ParsedModule {
file: File,
#[get_size(size_fn = arc_swap_size)]
inner: Arc<ArcSwapOption<indexed::IndexedModule>>,
}
@@ -142,6 +144,18 @@ impl std::ops::Deref for ParsedModuleRef {
}
}
/// Returns the heap-size of the currently stored `T` in the `ArcSwap`.
fn arc_swap_size<T>(arc_swap: &Arc<ArcSwapOption<T>>) -> usize
where
T: GetSize,
{
if let Some(value) = &*arc_swap.load() {
T::get_heap_size(value)
} else {
0
}
}
mod indexed {
use std::sync::Arc;
@@ -150,7 +164,7 @@ mod indexed {
use ruff_python_parser::Parsed;
/// A wrapper around the AST that allows access to AST nodes by index.
#[derive(Debug)]
#[derive(Debug, get_size2::GetSize)]
pub struct IndexedModule {
index: Box<[AnyRootNodeRef<'static>]>,
pub parsed: Parsed<ModModule>,

View File

@@ -1,7 +1,6 @@
use std::ops::Deref;
use std::sync::Arc;
use arc_swap::ArcSwapOption;
use countme::Count;
use ruff_notebook::Notebook;
@@ -12,17 +11,8 @@ use crate::Db;
use crate::files::{File, FilePath};
/// Reads the source text of a python text file (must be valid UTF8) or notebook.
#[salsa::tracked(no_eq)]
#[salsa::tracked(heap_size=get_size2::GetSize::get_heap_size)]
pub fn source_text(db: &dyn Db, file: File) -> SourceText {
let source_text = source_text_impl(db, file);
SourceText {
file,
inner: Arc::new(ArcSwapOption::new(Some(Arc::new(source_text)))),
}
}
fn source_text_impl(db: &dyn Db, file: File) -> SourceTextInner {
let path = file.path(db);
let _span = tracing::trace_span!("source_text", file = %path).entered();
let mut read_error = None;
@@ -47,10 +37,12 @@ fn source_text_impl(db: &dyn Db, file: File) -> SourceTextInner {
.into()
};
SourceTextInner {
kind,
read_error,
_count: Count::new(),
SourceText {
inner: Arc::new(SourceTextInner {
kind,
read_error,
count: Count::new(),
}),
}
}
@@ -73,41 +65,12 @@ fn is_notebook(path: &FilePath) -> bool {
/// The file containing the source text can either be a text file or a notebook.
///
/// Cheap cloneable in `O(1)`.
#[derive(Clone)]
#[derive(Clone, Eq, PartialEq, get_size2::GetSize)]
pub struct SourceText {
file: File,
inner: Arc<ArcSwapOption<SourceTextInner>>,
}
impl PartialEq for SourceText {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.inner, &other.inner)
}
}
impl Eq for SourceText {}
impl SourceText {
pub fn load(&self, db: &dyn Db) -> SourceTextRef {
SourceTextRef {
inner: self
.inner
.load_full()
.unwrap_or_else(|| Arc::new(source_text_impl(db, self.file))),
}
}
pub fn clear(&self) {
self.inner.store(None);
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct SourceTextRef {
inner: Arc<SourceTextInner>,
}
impl SourceTextRef {
impl SourceText {
/// Returns the python code as a `str`.
pub fn as_str(&self) -> &str {
match &self.inner.kind {
@@ -135,7 +98,7 @@ impl SourceTextRef {
}
}
impl Deref for SourceTextRef {
impl Deref for SourceText {
type Target = str;
fn deref(&self) -> &str {
@@ -143,7 +106,7 @@ impl Deref for SourceTextRef {
}
}
impl std::fmt::Debug for SourceTextRef {
impl std::fmt::Debug for SourceText {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut dbg = f.debug_tuple("SourceText");
@@ -160,18 +123,12 @@ impl std::fmt::Debug for SourceTextRef {
}
}
#[derive(Eq, PartialEq, get_size2::GetSize)]
struct SourceTextInner {
#[get_size(ignore)]
count: Count<SourceText>,
kind: SourceTextKind,
read_error: Option<SourceTextError>,
_count: Count<SourceText>,
}
impl Eq for SourceTextInner {}
impl PartialEq for SourceTextInner {
fn eq(&self, other: &Self) -> bool {
self.kind.eq(&other.kind) && self.read_error.eq(&other.read_error)
}
}
#[derive(Eq, PartialEq)]
@@ -180,6 +137,19 @@ enum SourceTextKind {
Notebook(Box<Notebook>),
}
impl get_size2::GetSize for SourceTextKind {
fn get_heap_size(&self) -> usize {
match self {
SourceTextKind::Text(text) => text.get_heap_size(),
// TODO: The `get-size` derive does not support ignoring enum variants.
//
// Jupyter notebooks are not very relevant for memory profiling, and contain
// arbitrary JSON values that do not implement the `GetSize` trait.
SourceTextKind::Notebook(_) => 0,
}
}
}
impl From<String> for SourceTextKind {
fn from(value: String) -> Self {
SourceTextKind::Text(value)
@@ -192,7 +162,7 @@ impl From<Notebook> for SourceTextKind {
}
}
#[derive(Debug, thiserror::Error, PartialEq, Eq, Clone)]
#[derive(Debug, thiserror::Error, PartialEq, Eq, Clone, get_size2::GetSize)]
pub enum SourceTextError {
#[error("Failed to read notebook: {0}`")]
FailedToReadNotebook(String),
@@ -201,11 +171,11 @@ pub enum SourceTextError {
}
/// Computes the [`LineIndex`] for `file`.
#[salsa::tracked]
#[salsa::tracked(heap_size=get_size2::GetSize::get_heap_size)]
pub fn line_index(db: &dyn Db, file: File) -> LineIndex {
let _span = tracing::trace_span!("line_index", ?file).entered();
let source = source_text(db, file).load(db);
let source = source_text(db, file);
LineIndex::from_source_text(&source)
}
@@ -232,11 +202,11 @@ mod tests {
let file = system_path_to_file(&db, path).unwrap();
assert_eq!(source_text(&db, file).load(&db).as_str(), "x = 10");
assert_eq!(source_text(&db, file).as_str(), "x = 10");
db.write_file(path, "x = 20").unwrap();
assert_eq!(source_text(&db, file).load(&db).as_str(), "x = 20");
assert_eq!(source_text(&db, file).as_str(), "x = 20");
Ok(())
}
@@ -250,13 +220,13 @@ mod tests {
let file = system_path_to_file(&db, path).unwrap();
assert_eq!(source_text(&db, file).load(&db).as_str(), "x = 10");
assert_eq!(source_text(&db, file).as_str(), "x = 10");
// Change the file permission only
file.set_permissions(&mut db).to(Some(0o777));
db.clear_salsa_events();
assert_eq!(source_text(&db, file).load(&db).as_str(), "x = 10");
assert_eq!(source_text(&db, file).as_str(), "x = 10");
let events = db.take_salsa_events();
@@ -278,7 +248,7 @@ mod tests {
let file = system_path_to_file(&db, path).unwrap();
let index = line_index(&db, file);
let source = source_text(&db, file).load(&db);
let source = source_text(&db, file);
assert_eq!(index.line_count(), 2);
assert_eq!(
@@ -320,7 +290,7 @@ mod tests {
)?;
let file = system_path_to_file(&db, path).unwrap();
let source = source_text(&db, file).load(&db);
let source = source_text(&db, file);
assert!(source.is_notebook());
assert_eq!(source.as_str(), "x = 10\n");

View File

@@ -124,6 +124,11 @@ pub trait System: Debug {
/// Returns `None` if no such convention exists for the system.
fn user_config_directory(&self) -> Option<SystemPathBuf>;
/// Returns the directory path where cached files are stored.
///
/// Returns `None` if no such convention exists for the system.
fn cache_dir(&self) -> Option<SystemPathBuf>;
/// Iterate over the contents of the directory at `path`.
///
/// The returned iterator must have the following properties:
@@ -186,6 +191,9 @@ pub trait System: Debug {
Err(std::env::VarError::NotPresent)
}
/// Returns a handle to a [`WritableSystem`] if this system is writeable.
fn as_writable(&self) -> Option<&dyn WritableSystem>;
fn as_any(&self) -> &dyn std::any::Any;
fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
@@ -226,11 +234,52 @@ impl fmt::Display for CaseSensitivity {
/// System trait for non-readonly systems.
pub trait WritableSystem: System {
/// Creates a file at the given path.
///
/// Returns an error if the file already exists.
fn create_new_file(&self, path: &SystemPath) -> Result<()>;
/// Writes the given content to the file at the given path.
fn write_file(&self, path: &SystemPath, content: &str) -> Result<()>;
/// Creates a directory at `path` as well as any intermediate directories.
fn create_directory_all(&self, path: &SystemPath) -> Result<()>;
/// Reads the provided file from the system cache, or creates the file if necessary.
///
/// Returns `Ok(None)` if the system does not expose a suitable cache directory.
fn get_or_cache(
&self,
path: &SystemPath,
read_contents: &dyn Fn() -> Result<String>,
) -> Result<Option<SystemPathBuf>> {
let Some(cache_dir) = self.cache_dir() else {
return Ok(None);
};
let cache_path = cache_dir.join(path);
// The file has already been cached.
if self.is_file(&cache_path) {
return Ok(Some(cache_path));
}
// Read the file contents.
let contents = read_contents()?;
// Create the parent directory.
self.create_directory_all(cache_path.parent().unwrap())?;
// Create and write to the file on the system.
//
// Note that `create_new_file` will fail if the file has already been created. This
// ensures that only one thread/process ever attempts to write to it to avoid corrupting
// the cache.
self.create_new_file(&cache_path)?;
self.write_file(&cache_path, &contents)?;
Ok(Some(cache_path))
}
}
#[derive(Clone, Debug, Eq, PartialEq)]

View File

@@ -1,4 +1,5 @@
use std::collections::BTreeMap;
use std::collections::{BTreeMap, btree_map};
use std::io;
use std::iter::FusedIterator;
use std::sync::{Arc, RwLock, RwLockWriteGuard};
@@ -153,6 +154,26 @@ impl MemoryFileSystem {
virtual_files.contains_key(&path.to_path_buf())
}
pub(crate) fn create_new_file(&self, path: &SystemPath) -> Result<()> {
let normalized = self.normalize_path(path);
let mut by_path = self.inner.by_path.write().unwrap();
match by_path.entry(normalized) {
btree_map::Entry::Vacant(entry) => {
entry.insert(Entry::File(File {
content: String::new(),
last_modified: file_time_now(),
}));
Ok(())
}
btree_map::Entry::Occupied(_) => Err(io::Error::new(
io::ErrorKind::AlreadyExists,
"File already exists",
)),
}
}
/// Stores a new file in the file system.
///
/// The operation overrides the content for an existing file with the same normalized `path`.
@@ -278,14 +299,14 @@ impl MemoryFileSystem {
let normalized = fs.normalize_path(path);
match by_path.entry(normalized) {
std::collections::btree_map::Entry::Occupied(entry) => match entry.get() {
btree_map::Entry::Occupied(entry) => match entry.get() {
Entry::File(_) => {
entry.remove();
Ok(())
}
Entry::Directory(_) => Err(is_a_directory()),
},
std::collections::btree_map::Entry::Vacant(_) => Err(not_found()),
btree_map::Entry::Vacant(_) => Err(not_found()),
}
}
@@ -345,14 +366,14 @@ impl MemoryFileSystem {
}
match by_path.entry(normalized.clone()) {
std::collections::btree_map::Entry::Occupied(entry) => match entry.get() {
btree_map::Entry::Occupied(entry) => match entry.get() {
Entry::Directory(_) => {
entry.remove();
Ok(())
}
Entry::File(_) => Err(not_a_directory()),
},
std::collections::btree_map::Entry::Vacant(_) => Err(not_found()),
btree_map::Entry::Vacant(_) => Err(not_found()),
}
}

View File

@@ -160,6 +160,39 @@ impl System for OsSystem {
None
}
/// Returns an absolute cache directory on the system.
///
/// On Linux and macOS, uses `$XDG_CACHE_HOME/ty` or `.cache/ty`.
/// On Windows, uses `C:\Users\User\AppData\Local\ty\cache`.
#[cfg(not(target_arch = "wasm32"))]
fn cache_dir(&self) -> Option<SystemPathBuf> {
use etcetera::BaseStrategy as _;
let cache_dir = etcetera::base_strategy::choose_base_strategy()
.ok()
.map(|dirs| dirs.cache_dir().join("ty"))
.map(|cache_dir| {
if cfg!(windows) {
// On Windows, we append `cache` to the LocalAppData directory, i.e., prefer
// `C:\Users\User\AppData\Local\ty\cache` over `C:\Users\User\AppData\Local\ty`.
cache_dir.join("cache")
} else {
cache_dir
}
})
.and_then(|path| SystemPathBuf::from_path_buf(path).ok())
.unwrap_or_else(|| SystemPathBuf::from(".ty_cache"));
Some(cache_dir)
}
// TODO: Remove this feature gating once `ruff_wasm` no longer indirectly depends on `ruff_db` with the
// `os` feature enabled (via `ruff_workspace` -> `ruff_graph` -> `ruff_db`).
#[cfg(target_arch = "wasm32")]
fn cache_dir(&self) -> Option<SystemPathBuf> {
None
}
/// Creates a builder to recursively walk `path`.
///
/// The walker ignores files according to [`ignore::WalkBuilder::standard_filters`]
@@ -192,6 +225,10 @@ impl System for OsSystem {
})
}
fn as_writable(&self) -> Option<&dyn WritableSystem> {
Some(self)
}
fn as_any(&self) -> &dyn Any {
self
}
@@ -310,6 +347,10 @@ impl OsSystem {
}
impl WritableSystem for OsSystem {
fn create_new_file(&self, path: &SystemPath) -> Result<()> {
std::fs::File::create_new(path).map(drop)
}
fn write_file(&self, path: &SystemPath, content: &str) -> Result<()> {
std::fs::write(path.as_std_path(), content)
}

View File

@@ -503,6 +503,12 @@ impl ToOwned for SystemPath {
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct SystemPathBuf(#[cfg_attr(feature = "schemars", schemars(with = "String"))] Utf8PathBuf);
impl get_size2::GetSize for SystemPathBuf {
fn get_heap_size(&self) -> usize {
self.0.capacity()
}
}
impl SystemPathBuf {
pub fn new() -> Self {
Self(Utf8PathBuf::new())

View File

@@ -102,6 +102,10 @@ impl System for TestSystem {
self.system().user_config_directory()
}
fn cache_dir(&self) -> Option<SystemPathBuf> {
self.system().cache_dir()
}
fn read_directory<'a>(
&'a self,
path: &SystemPath,
@@ -123,6 +127,10 @@ impl System for TestSystem {
self.system().glob(pattern)
}
fn as_writable(&self) -> Option<&dyn WritableSystem> {
Some(self)
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
@@ -149,6 +157,10 @@ impl Default for TestSystem {
}
impl WritableSystem for TestSystem {
fn create_new_file(&self, path: &SystemPath) -> Result<()> {
self.system().create_new_file(path)
}
fn write_file(&self, path: &SystemPath, content: &str) -> Result<()> {
self.system().write_file(path, content)
}
@@ -335,6 +347,10 @@ impl System for InMemorySystem {
self.user_config_directory.lock().unwrap().clone()
}
fn cache_dir(&self) -> Option<SystemPathBuf> {
None
}
fn read_directory<'a>(
&'a self,
path: &SystemPath,
@@ -357,6 +373,10 @@ impl System for InMemorySystem {
Ok(Box::new(iterator))
}
fn as_writable(&self) -> Option<&dyn WritableSystem> {
Some(self)
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
@@ -377,6 +397,10 @@ impl System for InMemorySystem {
}
impl WritableSystem for InMemorySystem {
fn create_new_file(&self, path: &SystemPath) -> Result<()> {
self.memory_fs.create_new_file(path)
}
fn write_file(&self, path: &SystemPath, content: &str) -> Result<()> {
self.memory_fs.write_file(path, content)
}

View File

@@ -212,7 +212,7 @@ impl Display for Error {
path: Some(path),
err,
} => {
write!(f, "IO error for operation on {}: {}", path, err)
write!(f, "IO error for operation on {path}: {err}")
}
ErrorKind::Io { path: None, err } => err.fmt(f),
ErrorKind::NonUtf8Path { path } => {

View File

@@ -4,12 +4,12 @@ use std::fmt::{self, Debug};
use std::io::{self, Read, Write};
use std::sync::{Arc, Mutex, MutexGuard};
use crate::file_revision::FileRevision;
use zip::result::ZipResult;
use zip::write::FileOptions;
use zip::{CompressionMethod, ZipArchive, ZipWriter, read::ZipFile};
pub use self::path::{VendoredPath, VendoredPathBuf};
use crate::file_revision::FileRevision;
mod path;

View File

@@ -87,6 +87,12 @@ impl ToOwned for VendoredPath {
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
pub struct VendoredPathBuf(Utf8PathBuf);
impl get_size2::GetSize for VendoredPathBuf {
fn get_heap_size(&self) -> usize {
self.0.capacity()
}
}
impl Default for VendoredPathBuf {
fn default() -> Self {
Self::new()

View File

@@ -114,6 +114,7 @@ fn generate_set(output: &mut String, set: Set, parents: &mut Vec<Set>) {
parents.pop();
}
#[derive(Debug)]
enum Set {
Toplevel(OptionSet),
Named { name: String, set: OptionSet },
@@ -136,7 +137,7 @@ impl Set {
}
fn emit_field(output: &mut String, name: &str, field: &OptionField, parents: &[Set]) {
let header_level = if parents.is_empty() { "###" } else { "####" };
let header_level = "#".repeat(parents.len() + 1);
let _ = writeln!(output, "{header_level} `{name}`");

View File

@@ -73,12 +73,20 @@ fn generate_markdown() -> String {
for lint in lints {
let _ = writeln!(&mut output, "## `{rule_name}`\n", rule_name = lint.name());
// Increase the header-level by one
// Reformat headers as bold text
let mut in_code_fence = false;
let documentation = lint
.documentation_lines()
.map(|line| {
if line.starts_with('#') {
Cow::Owned(format!("#{line}"))
// Toggle the code fence state if we encounter a boundary
if line.starts_with("```") {
in_code_fence = !in_code_fence;
}
if !in_code_fence && line.starts_with('#') {
Cow::Owned(format!(
"**{line}**\n",
line = line.trim_start_matches('#').trim_start()
))
} else {
Cow::Borrowed(line)
}
@@ -87,21 +95,15 @@ fn generate_markdown() -> String {
let _ = writeln!(
&mut output,
r#"**Default level**: {level}
<details>
<summary>{summary}</summary>
r#"<small>
Default level: [`{level}`](../rules.md#rule-levels "This lint has a default level of '{level}'.") ·
[Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20{encoded_name}) ·
[View source](https://github.com/astral-sh/ruff/blob/main/{file}#L{line})
</small>
{documentation}
### Links
* [Related issues](https://github.com/astral-sh/ty/issues?q=sort%3Aupdated-desc%20is%3Aissue%20is%3Aopen%20{encoded_name})
* [View source](https://github.com/astral-sh/ruff/blob/main/{file}#L{line})
</details>
"#,
level = lint.default_level(),
// GitHub doesn't support markdown in `summary` headers
summary = replace_inline_code(lint.summary()),
encoded_name = url::form_urlencoded::byte_serialize(lint.name().as_str().as_bytes())
.collect::<String>(),
file = url::form_urlencoded::byte_serialize(lint.file().replace('\\', "/").as_bytes())
@@ -113,25 +115,6 @@ fn generate_markdown() -> String {
output
}
/// Replaces inline code blocks (`code`) with `<code>code</code>`
fn replace_inline_code(input: &str) -> String {
let mut output = String::new();
let mut parts = input.split('`');
while let Some(before) = parts.next() {
if let Some(between) = parts.next() {
output.push_str(before);
output.push_str("<code>");
output.push_str(between);
output.push_str("</code>");
} else {
output.push_str(before);
}
}
output
}
#[cfg(test)]
mod tests {
use anyhow::Result;

View File

@@ -16,5 +16,6 @@ doctest = false
[dependencies]
ruff_text_size = { workspace = true }
get-size2 = { workspace = true }
is-macro = { workspace = true }
serde = { workspace = true, optional = true, features = [] }

View File

@@ -7,7 +7,7 @@ use ruff_text_size::{Ranged, TextRange, TextSize};
/// A text edit to be applied to a source file. Inserts, deletes, or replaces
/// content at a given location.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[derive(Clone, Debug, PartialEq, Eq, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Edit {
/// The start location of the edit.

View File

@@ -6,7 +6,9 @@ use ruff_text_size::{Ranged, TextSize};
use crate::edit::Edit;
/// Indicates if a fix can be applied.
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, is_macro::Is)]
#[derive(
Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, is_macro::Is, get_size2::GetSize,
)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
pub enum Applicability {
@@ -30,7 +32,7 @@ pub enum Applicability {
}
/// Indicates the level of isolation required to apply a fix.
#[derive(Default, Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[derive(Default, Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum IsolationLevel {
/// The fix should be applied as long as no other fixes in the same group have been applied.
@@ -41,7 +43,7 @@ pub enum IsolationLevel {
}
/// A collection of [`Edit`] elements to be applied to a source file.
#[derive(Debug, PartialEq, Eq, Clone)]
#[derive(Debug, PartialEq, Eq, Clone, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Fix {
/// The [`Edit`] elements to be applied, sorted by [`Edit::start`] in ascending order.

View File

@@ -1,15 +1,15 @@
use anyhow::Result;
use anyhow::{Context, Result};
use std::sync::Arc;
use zip::CompressionMethod;
use ruff_db::Db as SourceDb;
use ruff_db::files::{File, Files};
use ruff_db::system::{OsSystem, System, SystemPathBuf};
use ruff_db::vendored::{VendoredFileSystem, VendoredFileSystemBuilder};
use ruff_db::{Db as SourceDb, Upcast};
use ruff_python_ast::PythonVersion;
use ty_python_semantic::lint::{LintRegistry, RuleSelection};
use ty_python_semantic::{
Db, Program, ProgramSettings, PythonPath, PythonPlatform, PythonVersionSource,
Db, Program, ProgramSettings, PythonEnvironment, PythonPlatform, PythonVersionSource,
PythonVersionWithSource, SearchPathSettings, SysPrefixPathOrigin, default_lint_registry,
};
@@ -35,38 +35,37 @@ impl ModuleDb {
python_version: PythonVersion,
venv_path: Option<SystemPathBuf>,
) -> Result<Self> {
let mut search_paths = SearchPathSettings::new(src_roots);
if let Some(venv_path) = venv_path {
search_paths.python_path =
PythonPath::sys_prefix(venv_path, SysPrefixPathOrigin::PythonCliFlag);
}
let db = Self::default();
let mut search_paths = SearchPathSettings::new(src_roots);
// TODO: Consider calling `PythonEnvironment::discover` if the `venv_path` is not provided.
if let Some(venv_path) = venv_path {
let environment =
PythonEnvironment::new(venv_path, SysPrefixPathOrigin::PythonCliFlag, db.system())?;
search_paths.site_packages_paths = environment
.site_packages_paths(db.system())
.context("Failed to discover the site-packages directory")?
.into_vec();
}
let search_paths = search_paths
.to_search_paths(db.system(), db.vendored())
.context("Invalid search path settings")?;
Program::from_settings(
&db,
ProgramSettings {
python_version: Some(PythonVersionWithSource {
python_version: PythonVersionWithSource {
version: python_version,
source: PythonVersionSource::default(),
}),
},
python_platform: PythonPlatform::default(),
search_paths,
},
)?;
);
Ok(db)
}
}
impl Upcast<dyn SourceDb> for ModuleDb {
fn upcast(&self) -> &(dyn SourceDb + 'static) {
self
}
fn upcast_mut(&mut self) -> &mut (dyn SourceDb + 'static) {
self
}
}
#[salsa::db]
impl SourceDb for ModuleDb {
fn vendored(&self) -> &VendoredFileSystem {

View File

@@ -36,14 +36,20 @@ impl fmt::Display for AnalyzeSettings {
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, CacheKey)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "kebab-case")
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
pub enum Direction {
/// Construct a map from module to its dependencies (i.e., the modules that it imports).
#[default]
#[cfg_attr(feature = "serde", serde(alias = "Dependencies"))]
Dependencies,
/// Construct a map from module to its dependents (i.e., the modules that import it).
#[cfg_attr(feature = "serde", serde(alias = "Dependents"))]
Dependents,
}

View File

@@ -14,6 +14,7 @@ license = { workspace = true }
doctest = false
[dependencies]
get-size2 = { workspace = true }
ruff_macros = { workspace = true }
salsa = { workspace = true, optional = true }

View File

@@ -6,7 +6,7 @@ use std::marker::PhantomData;
use std::ops::{Deref, DerefMut, RangeBounds};
/// An owned sequence of `T` indexed by `I`
#[derive(Clone, PartialEq, Eq, Hash)]
#[derive(Clone, PartialEq, Eq, Hash, get_size2::GetSize)]
#[repr(transparent)]
pub struct IndexVec<I, T> {
pub raw: Vec<T>,
@@ -191,6 +191,6 @@ where
#[expect(unsafe_code)]
unsafe fn maybe_update(old_pointer: *mut Self, new_value: Self) -> bool {
let old_vec: &mut IndexVec<I, T> = unsafe { &mut *old_pointer };
unsafe { salsa::Update::maybe_update(&mut old_vec.raw, new_value.raw) }
unsafe { salsa::Update::maybe_update(&raw mut old_vec.raw, new_value.raw) }
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_linter"
version = "0.12.0"
version = "0.12.2"
publish = false
authors = { workspace = true }
edition = { workspace = true }
@@ -15,7 +15,7 @@ license = { workspace = true }
[dependencies]
ruff_annotate_snippets = { workspace = true }
ruff_cache = { workspace = true }
ruff_db = { workspace = true }
ruff_db = { workspace = true, features = ["serde"] }
ruff_diagnostics = { workspace = true, features = ["serde"] }
ruff_notebook = { workspace = true }
ruff_macros = { workspace = true }
@@ -38,6 +38,7 @@ colored = { workspace = true }
fern = { workspace = true }
glob = { workspace = true }
globset = { workspace = true }
hashbrown = { workspace = true }
imperative = { workspace = true }
is-macro = { workspace = true }
is-wsl = { workspace = true }

View File

@@ -165,3 +165,15 @@ async def test_trio_async115_helpers():
await trio.sleep(seconds=0) # ASYNC115
await trio.sleep(delay=0) # OK
# https://github.com/astral-sh/ruff/issues/18740
# The autofix for this is unsafe due to the comments.
async def func():
import trio
await (
trio # comment
.sleep( # comment
0 # comment
)
)

View File

@@ -128,3 +128,11 @@ async def test_trio_async116_helpers():
await trio.sleep(seconds=86401) # ASYNC116
await trio.sleep(delay=86401) # OK
async def _():
import trio
from trio import sleep
await sleep(18446744073709551616)
await trio.sleep(99999999999999999999)

View File

@@ -1,29 +1,30 @@
try:
pass
except Exception:
continue
for _ in []:
try:
pass
except Exception:
continue
try:
pass
except:
continue
try:
pass
except:
continue
try:
pass
except (Exception,):
continue
try:
pass
except (Exception,):
continue
try:
pass
except (Exception, ValueError):
continue
try:
pass
except (Exception, ValueError):
continue
try:
pass
except ValueError:
continue
try:
pass
except ValueError:
continue
try:
pass
except (ValueError,):
continue
try:
pass
except (ValueError,):
continue

View File

@@ -29,3 +29,13 @@ def this_is_fine():
o = object()
if callable(o):
print("Ooh, this is actually callable.")
# https://github.com/astral-sh/ruff/issues/18741
# The autofix for this is unsafe due to the comments.
hasattr(
# comment 1
obj, # comment 2
# comment 3
"__call__", # comment 4
# comment 5
)

View File

@@ -185,38 +185,45 @@ for _section, section_items in groupby(items, key=lambda p: p[1]):
collect_shop_items(shopper, section_items)
# Shouldn't trigger the warning when there is a return statement.
for _section, section_items in groupby(items, key=lambda p: p[1]):
if _section == "greens":
def foo():
for _section, section_items in groupby(items, key=lambda p: p[1]):
if _section == "greens":
collect_shop_items(shopper, section_items)
return
elif _section == "frozen items":
return section_items
collect_shop_items(shopper, section_items)
return
elif _section == "frozen items":
return section_items
collect_shop_items(shopper, section_items)
# Should trigger the warning for duplicate access, even if is a return statement after.
for _section, section_items in groupby(items, key=lambda p: p[1]):
if _section == "greens":
collect_shop_items(shopper, section_items)
collect_shop_items(shopper, section_items)
return
def foo():
from itertools import groupby
for _section, section_items in groupby(items, key=lambda p: p[1]):
if _section == "greens":
collect_shop_items(shopper, section_items)
collect_shop_items(shopper, section_items)
return
# Should trigger the warning for duplicate access, even if is a return in another branch.
for _section, section_items in groupby(items, key=lambda p: p[1]):
if _section == "greens":
collect_shop_items(shopper, section_items)
return
elif _section == "frozen items":
collect_shop_items(shopper, section_items)
collect_shop_items(shopper, section_items)
def foo():
from itertools import groupby
for _section, section_items in groupby(items, key=lambda p: p[1]):
if _section == "greens":
collect_shop_items(shopper, section_items)
return
elif _section == "frozen items":
collect_shop_items(shopper, section_items)
collect_shop_items(shopper, section_items)
# Should trigger, since only one branch has a return statement.
for _section, section_items in groupby(items, key=lambda p: p[1]):
if _section == "greens":
collect_shop_items(shopper, section_items)
return
elif _section == "frozen items":
collect_shop_items(shopper, section_items)
collect_shop_items(shopper, section_items) # B031
def foo():
from itertools import groupby
for _section, section_items in groupby(items, key=lambda p: p[1]):
if _section == "greens":
collect_shop_items(shopper, section_items)
return
elif _section == "frozen items":
collect_shop_items(shopper, section_items)
collect_shop_items(shopper, section_items) # B031
# Let's redefine the `groupby` function to make sure we pick up the correct one.
# NOTE: This should always be at the end of the file.

View File

@@ -36,9 +36,19 @@ set(
# some more
)
# t-strings
print(t"Hello {set(f(a) for a in 'abc')} World")
print(t"Hello { set(f(a) for a in 'abc') } World")
small_nums = t"{set(a if a < 6 else 0 for a in range(3))}"
print(t"Hello {set(a for a in range(3))} World")
print(t"{set(a for a in 'abc') - set(a for a in 'ab')}")
print(t"{ set(a for a in 'abc') - set(a for a in 'ab') }")
# Not built-in set.
def set(*args, **kwargs):
return None
set(2 * x for x in range(3))
set(x for x in range(3))

View File

@@ -34,4 +34,16 @@ s = set( # outer set comment
))))
# Test trailing comma case
s = set([x for x in range(3)],)
s = set([x for x in range(3)],)
s = t"{set([x for x in 'ab'])}"
s = t'{set([x for x in "ab"])}'
def f(x):
return x
s = t"{set([f(x) for x in 'ab'])}"
s = t"{ set([x for x in 'ab']) | set([x for x in 'ab']) }"
s = t"{set([x for x in 'ab']) | set([x for x in 'ab'])}"

View File

@@ -24,3 +24,12 @@ f"{set(['a', 'b']) - set(['a'])}"
f"{ set(['a', 'b']) - set(['a']) }"
f"a {set(['a', 'b']) - set(['a'])} b"
f"a { set(['a', 'b']) - set(['a']) } b"
t"{set([1,2,3])}"
t"{set(['a', 'b'])}"
t'{set(["a", "b"])}'
t"{set(['a', 'b']) - set(['a'])}"
t"{ set(['a', 'b']) - set(['a']) }"
t"a {set(['a', 'b']) - set(['a'])} b"
t"a { set(['a', 'b']) - set(['a']) } b"

View File

@@ -27,3 +27,13 @@ dict(
tuple( # comment
)
t"{dict(x='y')}"
t'{dict(x="y")}'
t"{dict()}"
t"a {dict()} b"
t"{dict(x='y') | dict(y='z')}"
t"{ dict(x='y') | dict(y='z') }"
t"a {dict(x='y') | dict(y='z')} b"
t"a { dict(x='y') | dict(y='z') } b"

View File

@@ -70,3 +70,8 @@ list(map(lambda: 1, "xyz"))
list(map(lambda x, y: x, [(1, 2), (3, 4)]))
list(map(lambda: 1, "xyz"))
list(map(lambda x, y: x, [(1, 2), (3, 4)]))
# When inside t-string, then the fix should be surrounded by whitespace
_ = t"{set(map(lambda x: x % 2 == 0, nums))}"
_ = t"{dict(map(lambda v: (v, v**2), nums))}"

View File

@@ -90,3 +90,14 @@ def func():
def func():
{(a, b): a + b for (a, b) in [(1, 2), (3, 4)]} # OK
# https://github.com/astral-sh/ruff/issues/18764
{ # 1
a # 2
: # 3
None # 4
for # 5
a # 6
in # 7
iterable # 8
} # 9

View File

@@ -0,0 +1,5 @@
foo or{x: None for x in bar}
# C420 fix must make sure to insert a leading space if needed,
# See https://github.com/astral-sh/ruff/issues/18599

View File

@@ -0,0 +1,6 @@
def f_byte():
raise RuntimeError(b"This is an example exception")
def f_byte_empty():
raise RuntimeError(b"")

View File

@@ -0,0 +1,2 @@
#!/usr/bin/env -S uv tool run ruff check --isolated --select EXE003
print("hello world")

View File

@@ -0,0 +1,2 @@
#!/usr/bin/env -S uvx ruff check --isolated --select EXE003
print("hello world")

View File

@@ -32,6 +32,12 @@ except ...:
### No errors
logging.info("", exc_info=ValueError())
logger.info("", exc_info=ValueError())
logging.info("", exc_info=(exc_type, exc_value, exc_traceback))
logger.info("", exc_info=(exc_type, exc_value, exc_traceback))
logging.info("", exc_info=a)
logger.info("", exc_info=a)

View File

@@ -26,8 +26,9 @@ abc(**{'a': b}, **{'a': c}) # PIE804
abc(a=1, **{'a': c}, **{'b': c}) # PIE804
# Some values need to be parenthesized.
abc(foo=1, **{'bar': (bar := 1)}) # PIE804
abc(foo=1, **{'bar': (yield 1)}) # PIE804
def foo():
abc(foo=1, **{'bar': (bar := 1)}) # PIE804
abc(foo=1, **{'bar': (yield 1)}) # PIE804
# https://github.com/astral-sh/ruff/issues/18036
# The autofix for this is unsafe due to the comments inside the dictionary.

View File

@@ -14,3 +14,6 @@ range(0, 10, 1)
range(0, 10, step=1)
range(start=0, stop=10)
range(0, stop=10)
# regression test for https://github.com/astral-sh/ruff/pull/18805
range((0), 42)

View File

@@ -119,4 +119,26 @@ field35: "int | str | int" # Error
# Technically, this falls into the domain of the rule but it is an unlikely edge case,
# only works if you have from `__future__ import annotations` at the top of the file,
# and stringified annotations are discouraged in stub files.
field36: "int | str" | int # Ok
field36: "int | str" | int # Ok
# https://github.com/astral-sh/ruff/issues/18546
# Expand Optional[T] to Union[T, None]
# OK
field37: typing.Optional[int]
field38: typing.Union[int, None]
# equivalent to None
field39: typing.Optional[None]
# equivalent to int | None
field40: typing.Union[typing.Optional[int], None]
field41: typing.Optional[typing.Union[int, None]]
field42: typing.Union[typing.Optional[int], typing.Optional[int]]
field43: typing.Optional[int] | None
field44: typing.Optional[int | None]
field45: typing.Optional[int] | typing.Optional[int]
# equivalent to int | dict | None
field46: typing.Union[typing.Optional[int], typing.Optional[dict]]
field47: typing.Optional[int] | typing.Optional[dict]
# avoid reporting twice
field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex]
field49: typing.Optional[complex | complex] | complex

View File

@@ -111,3 +111,25 @@ field33: typing.Union[typing.Union[int | int] | typing.Union[int | int]] # Error
# Test case for mixed union type
field34: typing.Union[list[int], str] | typing.Union[bytes, list[int]] # Error
# https://github.com/astral-sh/ruff/issues/18546
# Expand Optional[T] to Union[T, None]
# OK
field37: typing.Optional[int]
field38: typing.Union[int, None]
# equivalent to None
field39: typing.Optional[None]
# equivalent to int | None
field40: typing.Union[typing.Optional[int], None]
field41: typing.Optional[typing.Union[int, None]]
field42: typing.Union[typing.Optional[int], typing.Optional[int]]
field43: typing.Optional[int] | None
field44: typing.Optional[int | None]
field45: typing.Optional[int] | typing.Optional[int]
# equivalent to int | dict | None
field46: typing.Union[typing.Optional[int], typing.Optional[dict]]
field47: typing.Optional[int] | typing.Optional[dict]
# avoid reporting twice
field48: typing.Union[typing.Optional[typing.Union[complex, complex]], complex]
field49: typing.Optional[complex | complex] | complex

View File

@@ -1,4 +1,5 @@
from typing import (
TYPE_CHECKING,
Union,
)
@@ -90,3 +91,22 @@ class Foo:
def bad5(self, arg: int | (float | complex)) -> None:
...
# https://github.com/astral-sh/ruff/issues/18298
# fix must not yield runtime `None | None | ...` (TypeError)
class Issue18298:
def f1(self, arg: None | int | None | float = None) -> None: # PYI041 - no fix
pass
if TYPE_CHECKING:
def f2(self, arg: None | int | None | float = None) -> None: ... # PYI041 - with fix
else:
def f2(self, arg=None) -> None:
pass
def f3(self, arg: None | float | None | int | None = None) -> None: # PYI041 - with fix
pass

View File

@@ -70,3 +70,11 @@ class Foo:
def bad4(self, arg: Union[float | complex, int]) -> None: ... # PYI041
def bad5(self, arg: int | (float | complex)) -> None: ... # PYI041
# https://github.com/astral-sh/ruff/issues/18298
# fix must not yield runtime `None | None | ...` (TypeError)
class Issue18298:
def f1(self, arg: None | int | None | float = None) -> None: ... # PYI041 - with fix
def f3(self, arg: None | float | None | int | None = None) -> None: ... # PYI041 - with fix

View File

@@ -0,0 +1,8 @@
from __future__ import annotations
# https://github.com/astral-sh/ruff/issues/18298
# fix must not yield runtime `None | None | ...` (TypeError)
class Issue18298:
def f1(self, arg: None | int | None | float = None) -> None:
pass

View File

@@ -76,3 +76,20 @@ def aliased_parentheses_with_params():
)
def aliased_parentheses_no_params_multiline():
return 42
# https://github.com/astral-sh/ruff/issues/18770
@pytest.fixture(
# TODO: use module scope
# scope="module"
)
def my_fixture(): ...
@(pytest.fixture())
def outer_paren_fixture_no_params():
return 42
@(fixture())
def outer_paren_imported_fixture_no_params():
return 42

View File

@@ -1,3 +1,8 @@
@pytest.mark.foo(scope="module")
def ok_due_to_missing_import():
pass
import pytest
@@ -72,3 +77,25 @@ class TestClass:
@pytest.mark.foo()
def test_something():
pass
# https://github.com/astral-sh/ruff/issues/18770
@pytest.mark.parametrize(
# TODO: fix later
# ("param1", "param2"),
# (
# (1, 2),
# (3, 4),
# ),
)
def test_bar(param1, param2): ...
@(pytest.mark.foo())
def test_outer_paren_mark_function():
pass
class TestClass:
@(pytest.mark.foo())
def test_method_outer_paren():
pass

View File

@@ -105,3 +105,8 @@ if future.exception():
future = executor.submit(float, "a")
if future.exception():
raise future.Exception()
raise TypeError(
# comment
)

View File

@@ -49,3 +49,13 @@ class Baz:
def prop4(self) -> None:
print("I've run out of things to say")
return None
# https://github.com/astral-sh/ruff/issues/18774
class _:
def foo(bar):
if not bar:
return
return (
None # comment
)

View File

@@ -128,3 +128,16 @@ def write_models(directory, Models):
pass; \
\
#
# Regression tests for: https://github.com/astral-sh/ruff/issues/18209
try:
1 / 0
except ():
pass
BaseException = ValueError
try:
int("a")
except BaseException:
pass

View File

@@ -27,8 +27,9 @@ with contextlib.ExitStack() as stack:
close_files = stack.pop_all().close
# OK
with contextlib.AsyncExitStack() as exit_stack:
f = await exit_stack.enter_async_context(open("filename"))
async def foo():
with contextlib.AsyncExitStack() as exit_stack:
f = await exit_stack.enter_async_context(open("filename"))
# OK (false negative)
with contextlib.ExitStack():
@@ -275,9 +276,10 @@ class ExampleClassTests(TestCase):
cls.enterClassContext(open("filename"))
# OK
class ExampleAsyncTests(IsolatedAsyncioTestCase):
async def test_something(self):
await self.enterAsyncContext(open("filename"))
async def foo():
class ExampleAsyncTests(IsolatedAsyncioTestCase):
async def test_something(self):
await self.enterAsyncContext(open("filename"))
# OK
class ExampleTests(TestCase):

View File

@@ -1,98 +1,99 @@
# Errors
a = "hello"
def foo():
# Errors
a = "hello"
# SIM116
if a == "foo":
return "bar"
elif a == "bar":
return "baz"
elif a == "boo":
return "ooh"
else:
return 42
# SIM116
if a == 1:
return (1, 2, 3)
elif a == 2:
return (4, 5, 6)
elif a == 3:
return (7, 8, 9)
else:
return (10, 11, 12)
# SIM116
if a == 1:
return (1, 2, 3)
elif a == 2:
return (4, 5, 6)
elif a == 3:
return (7, 8, 9)
# SIM116
if a == "hello 'sir'":
return (1, 2, 3)
elif a == 'goodbye "mam"':
return (4, 5, 6)
elif a == """Fairwell 'mister'""":
return (7, 8, 9)
else:
return (10, 11, 12)
# SIM116
if a == b"one":
return 1
elif a == b"two":
return 2
elif a == b"three":
return 3
# SIM116
if a == "hello 'sir'":
return ("hello'", 'hi"', 3)
elif a == 'goodbye "mam"':
return (4, 5, 6)
elif a == """Fairwell 'mister'""":
return (7, 8, 9)
else:
return (10, 11, 12)
# OK
if a == "foo":
return "bar"
elif a == "bar":
return baz()
elif a == "boo":
return "ooh"
else:
return 42
# OK
if a == b"one":
return 1
elif b == b"two":
return 2
elif a == b"three":
return 3
# SIM116
if func_name == "create":
return "A"
elif func_name == "modify":
return "M"
elif func_name == "remove":
return "D"
elif func_name == "move":
return "MV"
# OK
def no_return_in_else(platform):
if platform == "linux":
return "auditwheel repair -w {dest_dir} {wheel}"
elif platform == "macos":
return "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}"
elif platform == "windows":
return ""
# SIM116
if a == "foo":
return "bar"
elif a == "bar":
return "baz"
elif a == "boo":
return "ooh"
else:
msg = f"Unknown platform: {platform!r}"
raise ValueError(msg)
return 42
# SIM116
if a == 1:
return (1, 2, 3)
elif a == 2:
return (4, 5, 6)
elif a == 3:
return (7, 8, 9)
else:
return (10, 11, 12)
# SIM116
if a == 1:
return (1, 2, 3)
elif a == 2:
return (4, 5, 6)
elif a == 3:
return (7, 8, 9)
# SIM116
if a == "hello 'sir'":
return (1, 2, 3)
elif a == 'goodbye "mam"':
return (4, 5, 6)
elif a == """Fairwell 'mister'""":
return (7, 8, 9)
else:
return (10, 11, 12)
# SIM116
if a == b"one":
return 1
elif a == b"two":
return 2
elif a == b"three":
return 3
# SIM116
if a == "hello 'sir'":
return ("hello'", 'hi"', 3)
elif a == 'goodbye "mam"':
return (4, 5, 6)
elif a == """Fairwell 'mister'""":
return (7, 8, 9)
else:
return (10, 11, 12)
# OK
if a == "foo":
return "bar"
elif a == "bar":
return baz()
elif a == "boo":
return "ooh"
else:
return 42
# OK
if a == b"one":
return 1
elif b == b"two":
return 2
elif a == b"three":
return 3
# SIM116
if func_name == "create":
return "A"
elif func_name == "modify":
return "M"
elif func_name == "remove":
return "D"
elif func_name == "move":
return "MV"
# OK
def no_return_in_else(platform):
if platform == "linux":
return "auditwheel repair -w {dest_dir} {wheel}"
elif platform == "macos":
return "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}"
elif platform == "windows":
return ""
else:
msg = f"Unknown platform: {platform!r}"
raise ValueError(msg)

View File

@@ -49,3 +49,9 @@ def foo(some_other: object):
# OK
def foo(some_other):
a = some_other.get('a', None)
# https://github.com/astral-sh/ruff/issues/18777
def foo():
dict = {"Tom": 23, "Maria": 23, "Dog": 11}
age = dict.get("Cat", None)

View File

@@ -23,3 +23,14 @@ for k, v in zip(d2.keys(), d2.values()): # SIM911
items = zip(x.keys(), x.values()) # OK
items.bar = zip(x.keys(), x.values()) # OK
# https://github.com/astral-sh/ruff/issues/18777
def foo():
dict = {}
for country, stars in zip(dict.keys(), dict.values()):
...
# https://github.com/astral-sh/ruff/issues/18776
flag_stars = {}
for country, stars in(zip)(flag_stars.keys(), flag_stars.values()):...

View File

@@ -50,3 +50,23 @@ class Baz:
class Nested:
a: TypeAlias = 'Baz' # OK
type A = 'Baz' # TC008
# O should have parenthesis added
o: TypeAlias = """int
| None"""
type O = """int
| None"""
# P, Q, and R should not have parenthesis added
p: TypeAlias = ("""int
| None""")
type P = ("""int
| None""")
q: TypeAlias = """(int
| None)"""
type Q = """(int
| None)"""
r: TypeAlias = """int | None"""
type R = """int | None"""

View File

@@ -2,13 +2,81 @@ import os.path
from pathlib import Path
from os.path import getsize
filename = "filename"
filename1 = __file__
filename2 = Path("filename")
os.path.getsize("filename")
os.path.getsize(b"filename")
os.path.getsize(Path("filename"))
os.path.getsize(__file__)
os.path.getsize(filename)
os.path.getsize(filename1)
os.path.getsize(filename2)
os.path.getsize(filename="filename")
os.path.getsize(filename=b"filename")
os.path.getsize(filename=Path("filename"))
os.path.getsize(filename=__file__)
getsize("filename")
getsize(b"filename")
getsize(Path("filename"))
getsize(__file__)
getsize(filename="filename")
getsize(filename=b"filename")
getsize(filename=Path("filename"))
getsize(filename=__file__)
getsize(filename)
getsize(filename1)
getsize(filename2)
os.path.getsize(
"filename", # comment
)
os.path.getsize(
# comment
"filename"
,
# comment
)
os.path.getsize(
# comment
b"filename"
# comment
)
os.path.getsize( # comment
Path(__file__)
# comment
) # comment
getsize( # comment
"filename")
getsize( # comment
b"filename",
#comment
)
os.path.getsize("file" + "name")
getsize \
\
\
( # comment
"filename",
)
getsize(Path("filename").resolve())
import pathlib
os.path.getsize(pathlib.Path("filename"))

View File

@@ -0,0 +1,5 @@
import os
os.path.getsize(filename="filename")
os.path.getsize(filename=b"filename")
os.path.getsize(filename=__file__)

View File

@@ -1,4 +1,4 @@
import os.path
import os.path, pathlib
from pathlib import Path
from os.path import getatime
@@ -10,3 +10,26 @@ os.path.getatime(Path("filename"))
getatime("filename")
getatime(b"filename")
getatime(Path("filename"))
file = __file__
os.path.getatime(file)
os.path.getatime(filename="filename")
os.path.getatime(filename=Path("filename"))
os.path.getatime( # comment 1
# comment 2
"filename" # comment 3
# comment 4
, # comment 5
# comment 6
) # comment 7
os.path.getatime("file" + "name")
getatime(Path("filename").resolve())
os.path.getatime(pathlib.Path("filename"))
getatime(Path("dir") / "file.txt")

View File

@@ -85,3 +85,19 @@ import builtins
for i in builtins.list(nested_tuple): # PERF101
pass
# https://github.com/astral-sh/ruff/issues/18783
items = (1, 2, 3)
for i in(list)(items):
print(i)
# https://github.com/astral-sh/ruff/issues/18784
items = (1, 2, 3)
for i in ( # 1
list # 2
# 3
)( # 4
items # 5
# 6
):
print(i)

View File

@@ -163,4 +163,32 @@ def foo():
for k, v in src:
if lambda: 0:
dst[k] = v
dst[k] = v
# https://github.com/astral-sh/ruff/issues/18859
def foo():
v = {}
for o,(x,)in():
v[x,]=o
# https://github.com/astral-sh/ruff/issues/19005
def issue_19005_1():
c = {}
a = object()
for a.b in ():
c[a.b] = a.b
def issue_19005_2():
a = object()
c = {}
for a.k, a.v in ():
c[a.k] = a.v
def issue_19005_3():
a = [None, None]
c = {}
for a[0], a[1] in ():
c[a[0]] = a[1]

View File

@@ -81,4 +81,5 @@ match(foo):
# https://github.com/astral-sh/ruff/issues/12094
pass;
yield, x
def foo():
yield, x

View File

@@ -69,3 +69,11 @@ def func():
Returns:
the value
"""
def func():
("""Docstring.
Raises:
ValueError: An error.
""")

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